본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 08. 19:06

동일한 가중치, 동일한 프롬프트, 그러나 다른 분류 단계

요약

동일한 4비트 양자화 모델과 프롬프트를 사용하더라도 GPU와 CPU 환경에서 추론 결과가 달라질 수 있음을 실험을 통해 보여줍니다. 의료 분류 시스템 Aegis-MD 구축 과정에서 발생한 하드웨어별 연산 차이와 그 메커니즘을 다룹니다.

핵심 포인트

  • 동일한 모델과 프롬프트라도 하드웨어(GPU vs CPU)에 따라 결과가 다를 수 있음
  • 양자화된 4비트 모델 사용 시 부동 소수점 연산 방식의 차이가 영향을 미침
  • 민감한 의료 데이터를 위해 온디바이스(On-device) 및 로컬 실행 환경의 중요성 강조
  • 하드웨어별 추론 결과의 불일치는 수치적 정밀도 차이에서 기인함

저는 4비트 의료 분류 (medical-triage) 모델을 노트북 GPU와 CPU에서 각각 실행해 보았습니다. 한 환자에 대해 GPU는 '긴급 (urgent)'이라고 답했고, CPU는 '응급 (emergency)'이라고 답했습니다. 동일한 모델 파일, 동일한 프롬프트, 동일한 입력값이었습니다. 그 메커니즘과 왜 "하드웨어 X에서 검증됨"이라는 말이 여러분이 기대하는 의미와 다를 수 있는지에 대해 설명하겠습니다.

저는 로컬 우선 방식의 응급실 분류 콘솔인 Aegis-MD를 구축해 왔습니다. 이 시스템에 주소 호소 (chief complaint), 활력 징후 (vitals), 연령, 통증 점수 (pain score), 몇 가지 위험 수정 요인 (risk modifiers)과 같은 구조화된 임상 상황을 제공하면, 호주-뉴질랜드 분류 척도 (Australasian Triage Scale, ATS 1–5)에 따른 긴급도 범주를 반환합니다. 여기서 ATS-1은 _지금 즉시 소생술 (resuscitate now)_을 의미하며, ATS-5는 _두 시간 정도 기다려도 됨_을 의미합니다. 전체 시스템은 온디바이스 (on-device)로 작동합니다. 즉, Ollama를 통해 서빙되는 양자화된 (quantized) MedGemma 4B 모델, 공개 가이드라인을 기반으로 한 작은 RAG (Retrieval-Augmented Generation) 레이어, 그리고 모델 하단에 위치한 결정론적인 규칙 기반 (deterministic rule-based) 계층으로 구성됩니다.

부동 소수점 산술 (floating-point arithmetic)에 대해 글을 쓰려고 시작한 것은 아니었습니다. 하지만 두 대의 기기에서 평가 세트를 실행하던 중 저를 멈춰 세운 결과가 나타났고, 그 설명은 대부분의 사람들이 찾는 교과서적인 답변보다 훨씬 더 흥미롭고 최신 트렌드에 부합했습니다.

설정, 그리고 왜 4비트 모델인가

Aegis-MD의 설계 중 이 이야기와 관련하여 중요한 두 가지 사항이 있습니다.

첫째, 설계 단계부터 로컬 (local)을 지향합니다. 분류 (triage) 데이터는 매우 민감한 정보이므로, 어떤 데이터도 기기 외부로 나가지 않습니다. 그 대가로 저는 프론티어 API (frontier API) 대신, Q4_K_XL 방식의 약 3.4 GB 크기를 가진 매우 높게 양자화된 (quantized) 소형 모델인 MedGemma 1.5 4B를 실행하고 있습니다. 4비트 가중치 (four-bit weights)는 소비자용 하드웨어에서 오프라인으로 실행하기 위해 지불해야 하는 비용입니다.

둘째, 의도적으로 두 가지 구성에서 테스트했습니다. 의도된 배포 환경은 로컬 GPU 추론 (RTX 5070 Ti Mobile, 12 GB)입니다. 하지만 공개 데모는 Cloud Run에서 CPU 전용으로 실행되는데, 이는 GPU 인스턴스에 필요한 유료 할당량 (paid quota)이 저에게 없기 때문입니다. 따라서 저는 동일한 모델, 동일한 코드, 동일한 프롬프트를 사용하여 GPU 빌드와 CPU 빌드 모두에 대해 동일한 평가를 수행했습니다.

평가는 심정지부터 의료 진단서 요청에 이르기까지 5가지 ATS 단계 전체를 아우르는 17개의 수기 사례(hand-written cases)로 구성되었습니다. (17개는 스모크 테스트(smoke test)일 뿐 검증(validation)은 아닙니다. 이렇게 작은 샘플로 퍼센트를 인용하지는 않겠습니다.) 결과는 평범했습니다. GPU에서는 17개 중 16개, CPU에서는 17개 중 15개를 맞췄습니다. 저를 괴롭혔던 것은 어떤 사례가 갈렸느냐 하는 점이었습니다.

일어나지 말아야 할 일

사례 8은 신장 산통(renal colic), 즉 요로결석이었습니다. 통증이 심하고 긴급하지만 즉각적으로 생명을 위협하지는 않는 상태, 즉 ATS-3 단계였습니다.

사례 8 — 신장 산통 (예상 ATS-3)

...

동일한 가중치(weights). 동일한 프롬프트(prompt). 동일한 환자 JSON. 동일한 Ollama. 유일한 변수는 순전파(forward pass)가 실행되는 하드웨어뿐이었고, 모델은 다른 임상 범주를 반환했습니다.

이번에는 안전한 쪽으로 오류가 발생했습니다. ATS-2는 ATS-3보다 긴급하므로, 실제 환자라면 더 늦게가 아니라 더 빨리 진료를 받았을 것입니다. 하지만

만약 왜 LLM의 두 실행 결과가 서로 다른지 묻는다면, 흔히 다음과 같은 답변을 듣게 됩니다: 부동 소수점 덧셈 (floating-point addition)은 결합 법칙 (associative)이 성립하지 않으며, GPU는 수천 개의 스레드를 동시에 실행하고, 이들이 완료되는 순서는 예측할 수 없으므로 반올림 (rounding) 결과가 달라지며, 이러한 작은 수치적 노이즈가 눈덩이처럼 불어나 서로 다른 토큰을 생성하게 된다.

(a + b) + c ≠ a + (b + c)는 참이며, 부동 소수점 (floats)은 각 단계에서 반올림되므로, 합산 순서가 마지막 비트의 결과를 변화시킵니다. 저 또한 그렇게 이해하고 있었습니다.

문제는 LLM의 비결정성 (nondeterminism)에 대한 주요 설명으로서, 이것이 대체로 틀렸다는 점이며, 최근의 한 연구가 그 이유를 명확히 밝혀냈습니다.

실제로 일어나고 있는 일

2025년 9월, Horace He와 Thinking Machines Lab은 _Defeating Nondeterminism in LLM Inference_를 발표하며 이 문제의 프레임을 완전히 재구성했습니다. 그들의 주장을 제 방식대로 설명하자면 다음과 같습니다: 일반적인 순전파 (forward pass)의 핫 패스 (hot path)는 원자적 덧셈 (atomic adds)이나 스레드 경합 (racing threads)에 의존하지 않으므로, 고정된 형태 (fixed shape)와 고정된 스케줄 (fixed schedule) 하에서 개별 커널 (kernels)은 실행 시마다 결정론적 (deterministic)입니다. Temperature-0 엔드포인트가 여전히 다른 답변을 내놓는 이유는 스레드 경합 때문이 아니라, 프로덕션 서버가 사용자의 요청을 다른 사람들의 요청과 함께 배치 (batch) 처리하며, 트래픽에 따라 배치 크기 (batch size)가 변동되고, 많은 커널들 (matmul, RMSNorm, attention)이 **배치 불변성 (batch-invariant)**을 갖지 않기 때문입니다. 즉, 계산되는 배치에 따라 수치적 출력이 달라집니다. 배치가 바뀌면 리덕션 (reduction)이 바뀌고, 결과가 바뀝니다.

이는 _동일한 엔드포인트_에서의 비결정성에 대한 탁월한 진단입니다. 하지만 이것이 제가 처한 상황을 설명하지 않는다는 점에 주목하십시오. 저는 부하 변동에 따라 한 서버에서 서로 다른 답변을 얻은 것이 아니었습니다. 저는 완전히 다른 두 개의 백엔드 (backends), 즉 GPU 상의 llama.cpp CUDA 경로와 CPU 경로 사이에서 서로 다른 답변을 얻고 있었던 것입니다.

백엔드 간 차이(Cross-backend)는 완전히 다른 축의 문제이며, 여기서 부동 소수점(floating-point) 이야기는 스레드 경합(thread races) 수준은 아니더라도 적절한 관점이 맞습니다. 두 백엔드는 동일한 수학 연산을 구현한 두 개의 별개 구현체입니다. 이들은 행렬 곱셈(matrix multiplies)을 타일링(tile)하고 블록화(block)하는 방식이 다르고, 축소(reduce)하는 순서가 다르며, 누적(accumulate)하는 정밀도(precision)가 다릅니다(예를 들어, CPU가 fp32를 사용하는 반면 GPU 커널은 fp16으로 누적할 수 있습니다). 또한 연산 융합(fuse operations) 방식도 다릅니다. 이와 같은 두 코드 경로 사이에서 비트 단위의 일치(Bitwise agreement)를 기대하는 것은 애초에 고려 대상이 아니었습니다. 이들은 부하(load) 상황에서 반올림 방식만 다르게 적용되는 동일한 연산이 아니라, 동일한 이상향을 근사하는 서로 다른 연산입니다.

따라서 정직한 프레임워크는 다음과 같습니다. _동일 하드웨어(same-hardware)_에서의 temp-0 비결정론(nondeterminism)은 주로 배치 불변성(batch-invariance) 문제이지, 부동 소수점 문제가 아닙니다. 제가 목격한 하드웨어 간(Cross-hardware) 발산은 두 백엔드 사이의 간극에 존재하는 부동 소수점 및 구현(floating-point-and-implementation) 문제입니다. 두 경우 모두 동일하게 불편한 결론에 도달합니다. 즉, 순전파(forward pass)는 입력값에만 의존하는 안정적인 함수가 아니라는 점입니다.

양자화(quantization)가 문제를 악화시키는 이유

4비트 가중치는 두 가지 독립적인 측면에서 이 문제를 더욱 악화시킵니다.

재구성(reconstruction)이 백엔드별로 특화되어 있습니다. Q4_K 가중치는 단순히 읽어들이는 숫자가 아닙니다. 이는 4비트 코드와 런타임에 사용 가능한 정밀도로 다시 확장되는 블록별 스케일(per-block scales)의 조합입니다. 이 역양자화(dequantization) 산술 연산은 백엔드별로 구현되어 있으므로, 단 한 번의 곱셈이 일어나기도 전에 _재구성된 가중치 자체_가 CPU와 GPU 사이에서 미세하게 달라집니다. 당신은 두 경로에 동일한 숫자조차 입력하고 있지 않은 것입니다.

로짓 지형(logit landscape)이 더 평탄해집니다. 4비트(4 bits)로 양자화(Quantizing)하는 것은 단순히 노이즈를 추가하는 것에 그치지 않고, 모델을 흐릿하게(blur) 만듭니다. 더 흐릿해진 모델은 결단력이 떨어지며, 다음 토큰(next-token)에 대한 결정 중 더 많은 부분이 거의 동일한 로짓(logits)을 가진 두 후보 사이의 접전(near-ties) 상태로 머물게 됩니다. 그리고 이 접전 상태야말로 서브 비트(sub-bit) 단위의 수치적 차이가 결과를 뒤바꿀 수 있는 바로 그 지점입니다. 다음 토큰에 대해 _확신(confident)_을 가진 전정밀도(full-precision) 모델은 로짓이 소수점 일곱째 자리에서 흔들리더라도 동일한 토큰을 선택할 것입니다. 하지만 두 토큰 사이에서 망설이는 4비트 모델은 그렇지 않을 것입니다.

반올림 오차가 어떻게 다른 진단으로 이어지는가

이것이 단순한 상식을 넘어 왜 중요한 문제인지에 대한 부분입니다.

탐욕적 디코딩(Greedy decoding)은 매 단계에서 아르그맥스(argmax) 로짓을 선택합니다. 아르그맥스는 계단 함수(step function)입니다. 즉, 두 로짓이 교차하기 _전까지_는 완벽하게 안정적이지만, 동점(tie)이 발생하는 순간 불연속적(discontinuous)이 되어 아주 미세한 변화만으로도 승자가 뒤바뀝니다. 하나의 결정 지점을 상상해 보십시오:


token A logit:  8.4012   ← GPU가 A를 선택

...

백엔드 간의 만 분의 일(one part in ten thousand) 수준의 차이만으로도 어떤 토큰이 승리할지를 바꿀 수 있습니다. 그리고 생성은 자기회귀적(autoregressive)입니다. 그 하나의 다른 토큰은 그 이후의 모든 것에 대한 컨텍스트(context)의 일부가 됩니다. 초기에 등장한 다른 단어 하나가 전체 완성 문장을 재구성하며, 제 파서(parser)는 이를 다른 ATS 카테고리로 매핑하게 됩니다. 단일 숫자에서는 인지조차 할 수 없을 만큼 미미한 섭동(perturbation)이 하나의 접전 지점에 주입되면, 범주적으로 완전히 다른 임상 결과로 폭포수처럼 이어집니다(cascades).

그 연쇄 과정은 다음과 같습니다: 4비트 가중치와 두 개의 백엔드가 약간 다른 로짓을 생성함 → 접전 상태에서 아르그맥스(argmax)가 뒤바뀜 → 자기회귀적 디코딩이 하나의 토큰을 다른 문장으로 증폭시킴 → 그 문장이 ATS-3 대신 ATS-2로 파싱됨.

(저는 이 정확한 사례에 대한 로짓 수준(logit-level)의 부검을 수행하는 것이 아니라, 관찰된 현상에 부합하는 메커니즘을 설명하고 있는 것입니다. 발산 지점을 계측하고 두 백엔드가 갈라지는 로짓 마진(logit margin)을 측정하여 엄격하게 확인하는 것이 제가 다음에 수행할 실험이며, 이는 "이것이 거의 확실히 일어난 일이다"라는 추측을 "이곳이 바로 그 일이 일어난 토큰이다"라는 사실로 바꾸는 올바른 방법입니다.)

챗봇보다 제가 더 신경 쓰는 이유

대부분의 LLM 제품에서 이것은 문제가 되지 않습니다. 만약 챗봇이 GPU에 따라 답변을 똑같이 훌륭한 두 가지 방식으로 표현한다면, 누구도 피해를 입지 않습니다.

하지만 분류(Triage)는 특정한 이유로 인해 그 안락함을 깨뜨립니다. 출력 공간이 이산적(discrete)이고, 서열적(ordinal)이며, 하중을 견뎌야 하는(load-bearing) 성격을 띠기 때문입니다. ATS-2와 ATS-3는 단순히 말을 바꾸는(paraphrase) 것이 아니라, 서로 다른 치료 목표 시간(time-to-treatment targets)을 의미합니다. 카테고리 자체가 곧 제품입니다. 하드웨어에 따라 카테고리가 변한다면, 당신은 정확도보다 훨씬 더 근본적인 무언가를 잃은 것입니다.

당신은 **재현성(reproducibility)**을 잃은 것입니다. 그리고 임상 소프트웨어에서 재현성은 단순히 있으면 좋은 기능이 아니라, 그러한 소프트웨어를 신뢰할 수 있게 만드는 모든 것의 전제 조건입니다. 답변이 기계에 따라 달라지는 시스템은 의미 있게 검증할 수 없습니다. 워크스테이션에서 올바르게 분류한다는 것을 보여주는 연구는, 해당 시스템이 배포될 CPU 노드에 대해 그 어떤 확실한 것도 말해주지 않습니다. 또한 이상 사례(adverse event)를 감사(audit)할 수도 없습니다. 동일한 입력이 다른 곳에서 다른 출력을 생성한다면, "왜 이 환자를 과소 분류(under-triage)했는가?"라는 질문에 안정적인 답변을 내놓을 수 없기 때문입니다. 그리고 규제 기관에 "보통은 일관된 결과를 냅니다"라는 말로 통할 수 있는 것은 아무것도 없습니다. 양자화된 모델(quantized model)이 개입되는 순간, "하드웨어 X에서 검증됨"이라는 말은 사람들이 생각하는 것보다 훨씬 더 좁은 의미의 주장임이 드러납니다.

제가 변경한 사항

실질적으로 도움이 되는 정도가 낮은 순서부터 높은 순서대로 몇 가지 조치를 취했습니다.

탐욕적(Greedy) 방식 사용, 그리고 확인. 온도를 0으로 설정하는 것은 필수적입니다. 분류 결정에 샘플링을 사용하는 일은 결코 없겠지만, 이것만으로는 충분하지 않습니다. 온도를 0으로 만드는 것은 샘플링 노이즈(sampling noise)는 제거하지만, 백엔드 간의 격차(cross-backend gap)에 대해서는 아무런 조치도 취하지 못합니다. (고정된 시드(Fixed seeds) 역시 구원책이 되지 못합니다. 시드는 단일 백엔드의 난수 생성기(RNG) 순서만 결정할 뿐, 두 백엔드 사이에서는 아무런 의미가 없습니다.)

하드웨어를 모델 아티팩트 (Model Artifact)의 일부로 취급하십시오. 저는 이제 백엔드 (Backend), 양자화 (Quant), llama.cpp/Ollama 버전, 그리고 대상 하드웨어 전체 스택을 고정하고 이를 함께 버전 관리합니다. 모델은 단순히 medgemma-q4가 아니라, 이 백엔드와 이 장치에서 실행되는 medgemma-q4입니다. 만약 데모는 CPU에서 실행되고 실제 서비스는 GPU에서 실행된다면, 이는 두 개의 배포 대상(Deployment targets)을 가진 하나의 모델이 아니라, 별도로 평가해야 하는 두 개의 아티팩트입니다.

정확도뿐만 아니라 하드웨어 간의 일관성을 테스트하십시오. 저의 평가 러너 (Eval runner)는 이미 실행 간 안정성을 측정하기 위한 반복 실행을 지원합니다. 여기서 명백한 확장 방향은 구성 (Configurations) 간의 출력을 비교 (Diff)하고, 오답과 마찬가지로 카테고리 분류의 불일치를 최우선적인 실패 (First-class failure)로 취급하는 것입니다.

그리고 가장 중요한 것: 확률적 요소 (Stochastic component)가 안전에 직결된 결정을 내리게 하지 마십시오. 이는 이번 사건을 통해 사후적으로 정당화된 설계 결정입니다. Aegis-MD는 단순히 가드레일 (Guardrails)을 덧붙인 LLM이 아닙니다. 이는 LLM이 '에스컬레이션 (Escalate)'은 할 수 있지만 '무시 (Override)'할 수는 없는, 결정론적인 규칙 기반의 하한선 (Rule floor), 키워드 및 활력 징후 임계값 (Vitals-threshold) 로직입니다. 이러한 규칙은 모든 기기에서 매번 동일한 답을 제공합니다. 모델은 뉘앙스와 읽기 쉬운 근거를 추가하며

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0