야간 정밀도 회귀 테스트

야간 정밀도 회귀 테스트 (Nightly Precision Regression Testing)

이 문서는 SGLang 서빙 엔진에서 조용한 수치 회귀(silent numerical regression)를 감지하는 야간 정밀도 회귀 프레임워크를 설명해요. 연속 실행 사이의 레이어별 hidden state를 비교해서 감지하고, 8×H200 GPU에서 야간 CI 작업으로 실행돼요. 개발과 디버깅을 위해 로컬에서도 호출할 수 있어요.

출처: 문서

본문

개요 (Overview)

야간 정밀도 회귀 프레임워크는 연속 실행 사이의 레이어별 hidden states를 비교해 SGLang 서빙 엔진의 조용한 수치 회귀를 감지해요. 8×H200 GPU에서 야간 CI 작업으로 실행되며 개발과 디버깅을 위해 로컬에서도 호출할 수 있어요.

프레임워크는 롤링 기준선(rolling-baseline) 모델로 동작해요:

  1. 기준선 생성 또는 비교: 서버를 실행하고 고정 프롬프트를 보내며 레이어별 hidden states를 디스크에 덤프해요. 이전 기준선이 있으면 SGLang 텐서 비교기로 새 텐서를 비교해요. 비교가 통과하면 새 텐서가 업데이트된 기준선이 돼요.
  2. 첫 실행(또는 캡처 형태가 바뀔 때)에서는 비교 없이 덤프된 텐서가 새 기준선으로 저장돼요.

기준선은 로컬 디스크에 저장되고 HuggingFace 데이터셋에 동기화되어 CI 러너를 넘어 생존하고 머신 간에 공유될 수 있어요. HF 데이터셋 저장소는 필수이며, SGLANG_PRECISION_HF_REPO가 설정되지 않으면 테스트가 오류를 냅니다.


동작 방식 (How It Works)

단계별 흐름 (Step-by-step flow)

┌──────────────────────────────────────────────────────────────┐
│  1. Resolve model config (layer count, capture layers)        │
│     ↓                                                         │
│  2. Compute capture_signature (schema, layers, TP, filter)    │
│     ↓                                                         │
│  3. Fetch baseline from HF dataset (signature-matched)        │
│     ↓                                                         │
│  4. Launch SGLang server with DUMPER enabled                   │
│     ↓                                                         │
│  5. POST /dumper/configure  (set layer filter + cleanup)      │
│     ↓                                                         │
│  6. POST /v1/chat/completions  (fixed prompt, 2 tokens,       │
│     ignore_eos=true to force decode path)                      │
│     ↓                                                         │
│  7. Kill server; assert decode tensors were captured           │
│     ↓                                                         │
│  8. Baseline exists (with matching signature)?                 │
│     ├── YES → Run comparator → pass/fail                      │
│     │         ├── PASS  → update baseline, push to HF         │
│     │         └── FAIL  → push diagnostics to HF              │
│     └── NO  → copy today's tensors as initial baseline        │
│                → push to HF as "baseline_established"          │
│     ↓                                                         │
│  9. Report summary (stdout + GitHub Step Summary)             │
└──────────────────────────────────────────────────────────────┘

핵심 구성 요소 (Key components)

Component File Purpose
테스트 진입점 test/registered/debug_utils/test_nightly_precision_regression.py 서버 실행, 덤프, 비교, 보고를 조정
HF 기준선 저장소 python/sglang/test/precision_baseline_store.py HuggingFace 데이터셋에서 기준선 push / fetch / prune
텐서 비교기 python/sglang/srt/debug_utils/comparator/ 두 디렉터리의 .pt 텐서를 비교, JSONL 보고서 생성
Dumper 인프라 python/sglang/srt/debug_utils/dumper.py 런타임에 레이어별 hidden states 캡처
CI 워크플로우 .github/workflows/nightly-test-nvidia.yml 8×H200에서 야간 작업 스케줄링

덤프되고 비교되는 것 (What Gets Dumped and Compared)

Strided 레이어 캡처

모든 레이어가 덤프되지는 않아요. 프레임워크는 I/O와 저장 오버헤드를 줄이기 위해 strided capture를 사용해요. 기본적으로 다음을 캡처해요:

  • Layer 0 (항상)
  • 마지막 레이어 (항상)
  • 그 사이의 8번째 레이어마다 (LAYER_CAPTURE_STRIDE로 구성 가능)

레이어 수는 모델의 HuggingFace config.json(num_hidden_layers 또는 num_layers)에서 자동 해석돼요. 해석이 실패하면 안전 폴백으로 모든 레이어가 캡처돼요.

Dumper 필터는 선택된 레이어 인덱스만 일치하는 정규식으로 동적으로 구성돼요. 예:

match(r'^non_intrusive__model\.layers\.(0|7|15|23)\.(inputs\.1|self_attn\.inputs\.hidden_states)$', name)

Decode 경로 검증

테스트는 ignore_eos=True2 토큰을 생성해 모델의 decode 경로가 실행되도록 보장해요. 덤프 후 _assert_decode_captured()가 decode 스텝의 텐서가 실제로 캡처됐는지(prefill만이 아닌) 검증해요. prefill 텐서만 있으면 --max-total-tokens이 decode 루프가 실행되기엔 너무 낮은 설정 오류를 잡기 위해 테스트가 즉시 실패해요.

비교기 (Comparator)

비교기는 각 텐서에 대해 상대 차이(rel_diff)를 계산하고 구성 가능한 임계값(기본 1e-3)에 대해 확인해요. 비교는 post-fusion 어텐션 입력으로 제한되며, --override-dims가 TP 랭크에 걸쳐 복제된다고 선언해요:

--filter self_attn\.inputs\.hidden_states
--override-dims ^non_intrusive__model\.layers\.\d+\.self_attn\.inputs\.hidden_states$:bs h # tp:replicated

축이 복제된 것으로 선언하면 비교기가 각 랭크가 부분 기여를 합산하는 대신 모든 랭크가 같은 값을 보유하는지 확인해요. 레이어 진입 inputs.1 텐서는 여전히 캡처되지만 --filter로 비교에서 제외돼요(아래 참고).

비교기가 exit code 0을 반환하지만 0 레이어를 비교한 경우(기준선/대상 이름 불일치), 테스트는 조용히 통과하는 대신 진단 메시지로 실패해요.

캡처 시그니처 (Capture signature)

capture_signature(스키마 버전, max_tokens, ignore_eos, TP 크기, dumper 필터, 비교기 필터, fusion 백엔드의 SHA-1 해시)가 실행마다 계산돼요. HF 저장소는 fetch 중 이 시그니처를 사용해 동일한 캡처 및 비교 계약을 가진 기준선만 고려되도록 해요. 시그니처가 바뀌면(예: 캡처 집합에 레이어 추가 또는 TP 변경) 호환되지 않는 텐서에서 오류를 내는 대신 새 기준선을 수립해요.

Fusion과 무엇을 비교하는지

Harness는 FlashInfer all-reduce fusion을 두는데(--flashinfer-allreduce-fusion-backend trtllm; SM90 자동 활성화는 #23402에서 제거됨) 레이어당 두 개의 이름을 캡처해요:

  • inputs.1 — 레이어로 들어가는 hidden states. LayerCommunicator는 크로스-레이어 all-reduce를 지연시키므로, fusion 활성화 시 이들은 rank-local TP-partial sum이에요. CPU 측 감소는 커널의 의미적 출력이 아니고 self-hosted 러너에 걸쳐 드리프트하므로 비교되지 않아요. _assert_fused_tp_layout()는 이들을 가드로만 유지해요. 복제된 형태로 돌아오면 fusion이 조용히 폴백된 것이므로 테스트가 실패해요.
  • self_attn.inputs.hidden_statesprepare_attn() 후 어텐션이 소비하는 값, 즉 fused all-reduce와 residual RMSNorm 이후. 랭크에 걸쳐 복제되며 비교기가 실제로 diff하는 대상이에요.

이것이 fusion이 초기화되지 않은 상태로 캡처된 낡은 기준선이 호환되지 않는 이유예요. 대상이 TP-partial 텐서를 보유한 곳에 복제된 레이어 진입 텐서를 보유하게 되어 대략 tp_size배 불일치로 읽히기 때문이에요. Fusion 초기화 실패는 FlashInfer #3676SGLang #30875에서 추적돼요.


환경 변수 (Environment Variables)

Variable Default Description
SGLANG_PRECISION_MODELS zai-org/GLM-5.1-FP8 테스트할 쉼표로 구분된 HuggingFace 모델 ID
SGLANG_PRECISION_BASELINE_DIR /tmp/sglang_precision_baselines 기준선 텐서용 로컬 디렉터리
SGLANG_PRECISION_DIFF_THRESHOLD 1e-3 텐서당 상대 diff 임계값
SGLANG_PRECISION_FORCE_UPDATE 0 1로 설정하면 비교를 건너뛰고 기준선을 무조건 새로 고침
SGLANG_PRECISION_COMMIT (git에서 자동 감지) push 시 태그되는 sglang commit SHA 오버라이드
SGLANG_PRECISION_HF_REPO (필수) 크로스 러너 기준선 저장용 HuggingFace 데이터셋 repo
SGLANG_PRECISION_HF_REVISION main HF 데이터셋의 브랜치/리비전
SGLANG_PRECISION_HF_TOKEN (CI에서 필수) 데이터셋에 쓰기 권한이 있는 HuggingFace 토큰. 러너의 gated-model 읽기 토큰을 담은 HF_TOKEN과 분리해 유지
SGLANG_PRECISION_HF_READ_ONLY 0 1로 설정하면 공유 기준선 저장소를 업데이트하지 않고 fetch 및 비교만

CI 통합 (CI Integration)

워크플로우 작업 (Workflow job)

테스트는 .github/workflows/nightly-test-nvidia.ymlnightly-8-gpu-h200 스테이지에 등록돼요. 이는 다른 모든 CUDA 스테이지처럼 _pr-test-stage.yml을 통해 실행돼요. 아래 기준선 env는 모든 예약 스테이지에서 내보내지며, 이 테스트만 읽어요.

주요 CI 구성:

- name: Export precision baseline env
  if: inputs.scheduled
  env:
    BASELINE_HF_TOKEN: ${{ secrets.HF_TOKEN_PRECISION_STORE }}
  run: |
    {
      echo "SGLANG_PRECISION_BASELINE_DIR=/tmp/sglang_precision_baselines"
      echo "SGLANG_PRECISION_HF_REPO=${{ vars.SGLANG_PRECISION_HF_REPO }}"
      echo "SGLANG_PRECISION_HF_REVISION=${{ vars.SGLANG_PRECISION_HF_REVISION || 'main' }}"
      echo "SGLANG_PRECISION_COMMIT=${{ github.sha }}"
      echo "SGLANG_PRECISION_HF_TOKEN=${BASELINE_HF_TOKEN}"
    } >> "$GITHUB_ENV"

HF_TOKEN이 아닌 SGLANG_PRECISION_HF_TOKEN을 사용해요. 후자는 이미 러너의 gated-model 읽기 토큰을 담고 있으며, 이를 덮어쓰면 작업의 모든 gated 모델이 401이 돼요.

독립형 /rerun-test 실행은 야간 기준선을 변경하지 않고 fetch해요. 유지 관리자는 in-repo PR에서 /rerun-test --refresh-precision-baseline test/registered/debug_utils/test_nightly_precision_regression.py로 새로 고칠 수 있어요.

필수 GitHub secrets/variables

Name Type Purpose
SGLANG_PRECISION_HF_REPO Repository variable HF 데이터셋 repo ID(예: org/sglang-precision-baselines) — 필수, 미설정 시 테스트 오류
SGLANG_PRECISION_HF_REVISION Repository variable (optional) 데이터셋 브랜치(기본값 main)
HF_TOKEN_PRECISION_STORE Repository secret 데이터셋에 쓰기 권한이 있는 HF 토큰. SGLANG_PRECISION_HF_TOKEN으로 작업에 내보냄

GitHub 스텝 요약 (GitHub Step Summary)

CI에서 실행할 때 테스트는 GitHub Actions 작업 요약에 각 모델의 상태(PASSED, FAILED, BASELINE_ESTABLISHED, ERROR)를 보여주는 Markdown 표를 씁니다.


HF 데이터셋 저장 레이아웃 (HF Dataset Storage Layout)

기준선은 HF 데이터셋에서 다음으로 구성됩니다:

<model_sanitized>/<YYYY>/<MM>/<DD>/run-<sha7>/
├── meta.json                    # Run metadata (model, commit, hardware, thresholds, stats)
├── comparator_report.jsonl      # Per-tensor comparison results
└── tensors/
    ├── layer_0_inputs_1.pt
    ├── layer_7_inputs_1.pt
    └── ...

최상위 manifest.jsonl은 모든 실행을 행당 하나의 JSON 객체로 추적해요. 각 manifest 행은 capture_signature 필드를 담아 fetch가 일치하는 캡처 형태를 가진 기준선만 선택하도록 해요.

prune_old_runs() 함수(수동 호출 가능)는 일일 실행을 30일 유지하고 그 창을 넘어서는 주간 실행을 하나씩 유지해요.


새 모델 추가 방법 (How to Add a New Model)

옵션 A: 기본 모델 목록에 추가 (CI)

test/registered/debug_utils/test_nightly_precision_regression.py의 기본값을 편집해요:

DEFAULT_MODELS_FOR_NIGHTLY_PRECISION = "zai-org/GLM-5.1-FP8,your-org/your-model"

또는 CI 워크플로우에서 SGLANG_PRECISION_MODELS 환경 변수를 설정해 기본값을 재정의해요.

옵션 B: 특정 모델로 로컬 실행

export SGLANG_PRECISION_MODELS="your-org/your-model"
export SGLANG_PRECISION_BASELINE_DIR="/tmp/my_precision_baselines"
export SGLANG_PRECISION_DIFF_THRESHOLD="1e-3"
export SGLANG_PRECISION_HF_REPO="your-org/sglang-precision-baselines"
export SGLANG_PRECISION_HF_TOKEN="hf_..."

cd test
python3 -m pytest registered/debug_utils/test_nightly_precision_regression.py -v

단계별: 야간 CI에 모델 추가

  1. 모델이 dumper와 동작하는지 검증. 먼저 로컬에서 실행해 hidden states가 올바르게 캡처되는지 확인:

    export SGLANG_PRECISION_MODELS="your-org/your-model"
    export SGLANG_PRECISION_BASELINE_DIR="/tmp/test_baselines"
    export SGLANG_PRECISION_HF_REPO="your-org/sglang-precision-baselines"
    export SGLANG_PRECISION_HF_TOKEN="hf_..."
    export SGLANG_PRECISION_FORCE_UPDATE="1"  # first run: establish baseline
    
    cd test
    python3 -m pytest registered/debug_utils/test_nightly_precision_regression.py -v -k test_precision
    
  2. 비교 패스 실행(FORCE_UPDATE 제거):

    unset SGLANG_PRECISION_FORCE_UPDATE
    python3 -m pytest registered/debug_utils/test_nightly_precision_regression.py -v -k test_precision
    

    엔진이 그 모델에 대해 수치적으로 안정적이면 PASSED를 보고해야 해요.

  3. 텐서 병렬 크기 설정. 모델이 TP > 1을 요구하면 테스트 harness는 모든 모델에 기본 tp_size=8을 사용해요. 커스터마이즈하려면 테스트에서 ModelLaunchSettings 구성을 수정하거나 추가 서버 인자를 전달해요:

    # In setUpClass or via env-driven logic
    cls.models = [ModelLaunchSettings("your-org/your-model", tp_size=4)]
    
  4. 필요 시 diff 임계값 조정. FP8 또는 양자화 모델은 더 큰 수치 차이를 보일 수 있어요. SGLANG_PRECISION_DIFF_THRESHOLD를 적절한 값(예: FP8용 1e-2)으로 설정해요.

  5. 기본 모델 목록에 추가하거나 CI 워크플로우에서 SGLANG_PRECISION_MODELS 구성.

모델별 조정 고려 사항

Concern How to handle
TP 크기 != 8 ModelLaunchSettings에서 tp_size 오버라이드 또는 모델별 로직 추가
양자화 모델 (FP8, GPTQ) SGLANG_PRECISION_DIFF_THRESHOLD 완화 (예: 1e-2)
모델이 추가 서버 인자 필요 ModelLaunchSettings(model, extra_args=["--quantization", "fp8"])로 전달
모델이 다른 프롬프트 필요 PROMPT 상수 수정 또는 모델 구성 가능하게 만들기
TP partial sums를 가진 MoE 모델 --override-dims(bs h[tp:partial])로 이미 처리됨
더 적거나 많은 캡처 레이어 LAYER_CAPTURE_STRIDE(기본 8) 조정; 작은 모델에는 더 낮게 설정
Decode가 캡처되지 않음 --max-total-tokens이 스케줄러의 decode 예약(기본 512)보다 훨씬 높은지 확인; 테스트는 4096 사용

로컬 실행 (Running Locally)

전제 조건 (Prerequisites)

  • 개발 모드로 설치된 SGLang
  • 모델 요구 사항과 일치하는 GPU
  • huggingface_hub 설치
  • 기준선 저장용 HuggingFace 데이터셋 및 쓰기 가능한 SGLANG_PRECISION_HF_TOKEN. HF 저장소는 필수SGLANG_PRECISION_HF_REPO가 설정되어야 하며, 그렇지 않으면 시작 시 테스트가 오류를 내요. 야간 CI 러너는 임시(영구 로컬 디스크 없음)이므로 기준선이 HF 데이터셋을 통해 실행 간에 생존해야 하기 때문이에요. 현재 로컬 전용 폴백은 없어요.

빠른 로컬 테스트

# All three are required — the test errors if SGLANG_PRECISION_HF_REPO is unset.
export SGLANG_PRECISION_MODELS="Qwen/Qwen2.5-0.5B-Instruct"
export SGLANG_PRECISION_BASELINE_DIR="/tmp/precision_baselines"
export SGLANG_PRECISION_HF_REPO="your-org/sglang-precision-baselines"
export SGLANG_PRECISION_HF_TOKEN="hf_..."

# First run: establish baseline
cd test
python3 -m pytest registered/debug_utils/test_nightly_precision_regression.py -v

# Second run: compare against baseline
python3 -m pytest registered/debug_utils/test_nightly_precision_regression.py -v

기준선 강제 새로 고침

export SGLANG_PRECISION_FORCE_UPDATE="1"
python3 -m pytest registered/debug_utils/test_nightly_precision_regression.py -v

결과 해석 (Interpreting Results)

상태 코드 (Status codes)

Status Meaning
BASELINE_ESTABLISHED 일치하는 시그니처의 이전 기준선이 없었음; 오늘의 텐서가 새 기준선으로 저장됨
PASSED 모든 레이어별 hidden states가 diff 임계값 안에 있음; 기준선 업데이트
FAILED 하나 이상의 레이어가 diff 임계값을 초과하거나, 0개 레이어가 비교됨(기준선/대상 불일치); 진단 데이터를 HF로 push
ERROR 서버 실행, 추론 또는 비교에서 예상치 못한 오류 발생

출력 예시

============================================================
Nightly Precision Regression Summary
============================================================
Model                                          Status                   Details
------------------------------------------------------------
zai-org/GLM-5.1-FP8                           PASSED                   comparison ok, baseline updated
Qwen/Qwen2.5-0.5B-Instruct                    FAILED                   tensor=layer_23.inputs_1 rel_diff=0.0152
============================================================

실패 감지 시 (When a failure is detected)

  1. 비교기 출력은 /tmp/nightly_precision_<model>_*.log에 저장
  2. 실패한 텐서와 비교기 보고서는 pass_label="failed"로 HF 데이터셋에 push되어 오프라인 진단 가능
  3. GitHub 스텝 요약에 실패 세부 사항 포함
  4. CI 작업이 0이 아닌 상태로 종료

기준선 관리 (Baseline Management)

로컬 기준선

기준선은 다음에 저장:

$SGLANG_PRECISION_BASELINE_DIR/<model_sanitized>/nightly_precision/*.pt

텐서 옆의 baseline_meta.json이 기준선을 생성한 타임스탬프와 commit을 기록해요.

HF 데이터셋 기준선

  • Fetch: 테스트 시작 시 로컬 기준선이 없으면 최신 시그니처 일치 기준선을 HF 데이터셋에서 내려받아요.
  • Push: 각 실행 후 텐서와 메타데이터가 데이터셋에 업로드돼요.
  • Prune: prune_old_runs()로 오래된 기준선을 가비지 수집(일일 실행 30일 유지, 그 후 주간 하나).

낡은 기준선 새로 고침

의도된 수치 변경(예: 커널 최적화, 모델 리팩터)이 비교 실패를 일으키면:

  1. 변경이 의도적임을 확인
  2. SGLANG_PRECISION_FORCE_UPDATE=1을 설정하고 테스트를 한 번 실행해 새 기준선 수립
  3. 필요한 임계값 조정을 커밋

캡처 구성(스트라이드, TP 크기 등)을 바꾸면 capture_signature가 달라지고 프레임워크가 자동으로 새 기준선을 수립해요 — 수동 조정 불필요.


알려진 제한 사항 (Known Limitations)

기준선 드리프트 (Baseline drift)

프레임워크는 롤링 기준선을 사용해요. 모든 성공적인 비교가 기준선을 현재 실행의 텐서로 업데이트해요. 즉 참조가 매일 앞으로 이동해요. 개별 일별 diff가 구성된 임계값 안에 있어도, 사소한 수치 차이가 시간이 지나면서 누적되어 기준선이 원래 golden 값에서 조용히 드리프트할 수 있어요.

시사점:

  • 프레임워크는 회귀(연속 실행 사이의 갑작스러운 큰 수치 변화)를 감지하지, 고정된 참조에 대한 절대 정확도를 감지하지 않아요.
  • 몇 주/몇 달에 걸쳐 누적 드리프트가 충분히 커져 점진적으로 발생한 진짜 회귀를 가릴 수 있거나, 드리프트가 결국 임계값을 넘을 때 잘못된 양성 실패를 일으킬 수 있어요.

완화 전략(아직 미구현):

  • 알려진 양호한 참조 commit에서 주기적으로 새 앵커 기준선을 재수립.
  • 매니페스트 메타데이터의 누적 드리프트를 추적하고 장기 예산을 초과할 때 경고.
  • 롤링 기준선 외에 고정 "epoch" 기준선과 비교.

로컬 전용 모드 없음 (No local-only mode)

테스트는 HuggingFace 데이터셋(SGLANG_PRECISION_HF_REPO)과 쓰기 가능한 SGLANG_PRECISION_HF_TOKEN이 필요해요. 로컬 전용 폴백은 없어요. 이는 의도적이에요. CI 러너는 영구 로컬 디스크가 없으므로 HF 데이터셋이 실행 간 기준선을 옮길 유일한 방법이에요. 로컬로 테스트해야 한다면 HF 데이터셋(비공개라도)을 설정하고 해당 토큰을 제공해야 해요.


파일 참조 (File Reference)

File Role
test/registered/debug_utils/test_nightly_precision_regression.py 메인 테스트 — 서버 수명 주기, 덤프, 비교, 보고
python/sglang/test/precision_baseline_store.py HF 데이터셋 저장소 — push, fetch, prune 기준선
python/sglang/srt/debug_utils/comparator/ 텐서 비교 엔진
python/sglang/srt/debug_utils/dumper.py 런타임 hidden-state 캡처
.github/workflows/nightly-test-nvidia.yml CI 워크플로우 정의
test/run_suite.py 테스트 스위트 등록(nightly-test-8-gpu-h200 포함)

더 알아보기