AI가 코드를 작성한다. 하지만 누가 검증하는가?
요약
AI가 생성한 최적화 코드가 겉보기에는 완벽해 보이지만, 수학적 모순과 치명적인 버그를 포함할 수 있음을 실제 사례를 통해 경고합니다. 차량 경로 문제(VRP) 해결 과정에서 발견된 QAOA 구현 오류를 통해 AI 코드 검증의 중요성을 강조합니다.
핵심 포인트
- AI 생성 코드는 린터나 아키텍처 규칙을 통과해도 논리적 오류가 있을 수 있음
- QAOA 구현 시 페널티 가중치 설정 오류로 인한 제약 조건 위반 사례 분석
- 의사결정 중심 머신러닝(SPO+)을 통한 현금 수송 최적화 성능 개선
- AI가 생성한 결과물의 수학적 정합성 검증 필요성
실제 최적화 프로젝트에서 세 가지 치명적인 버그를 추적하며, 우리는 현대 소프트웨어 개발의 가장 큰 사각지대를 발견했고 해결책을 찾아냈습니다.
2026년 6월 · stochastic-VRP-decision-focused-learning · github/spec-kit
상상해 보세요. 당신이 복잡한 최적화 문제를 AI에게 맡깁니다. 몇 시간 만에 17개의 파일이 푸시됩니다. 표, 차트, 숫자 — 모든 것이 세련되고 완벽해 보입니다.
하지만 그 안에 세 가지 치명적인 버그가 숨겨져 있다면 어떨까요? 그리고 그중 어느 것도 코드 리뷰(code review), 린터(linter), 또는 그 어떤 아키텍처 규칙(architectural rule)에 의해 발견되지 않았다면요?
이것이 바로 우리가 겪은 이야기입니다. 더 중요한 것은, 이 버그들이 현대 소프트웨어 개발이 여전히 해결하지 못한 문제를 드러냈다는 점입니다. 그리고 우리는 이를 해결하기 위한 무언가를 제안하고자 합니다.
프로젝트가 실제로 수행하는 작업
우리는 현금 수송(Cash-in-Transit) 회사를 위한 차량 경로 문제(vehicle routing problem)를 해결하고 있습니다. 20대의 ATM, 소수의 차량, 그리고 각 ATM은 날짜마다 서로 다른 현금 수요를 가집니다. 전통적인 방식은 평균값을 사용하지만, 이는 처참하게 실패합니다. ATM의 42%가 현금 부족 상태에 빠지며, 이는 막대한 비용 손실을 초래합니다.
이 프로젝트는 두 가지 계층으로 이 문제를 다룹니다. 결정론적 베이스라인(deterministic baseline, CVRP), 그리고 수요의 불확실성을 고려하도록 학습하는 의사결정 중심 머신러닝(decision-focused machine learning) 모델(SPO+)입니다. 결과적으로, 품절률(stockout rate)이 42%에서 25%로 감소했으며, 이는 완벽한 정보를 가진 이론적 오라클(oracle)과 거의 일치하는 수준입니다.
| 📉 품절률 (전통적 방식) | 42% |
| ... | |
| 강력한 수치입니다. 하지만 진짜 이야기는 여기서부터 시작됩니다. |
양자 실험: 좋은 아이디어, 오해의 소지가 있는 결과
차량 경로 문제는 이론적으로 양자 컴퓨팅(quantum computing)이 목표로 하는 조합 최적화 문제(combinatorial optimization problems) 클래스에 속합니다. 그래서 우리는 자연스러운 질문을 던졌습니다. "여기서 Q#과 QAOA를 사용하면 어떻게 될까?"
우리는 이 질문을 AI에게 던졌습니다. 몇 시간 후, 완벽해 보이는 구현체가 도착했습니다:
# QAOA 비교 표 — 첫 번째 버전
Method <H> Energy Constraints
...
잠깐. QAOA가 제약 조건(constraints)을 위반하고 있는데, MILP와 **정확히 동일한 에너지(energy)**를 반환한다고? 그리고 왜 서로 다른 회로 깊이(circuit depths)를 가진 p=1과 p=3이 소수점 넷째 자리까지 일치하는 거지?
이것은 수학적으로 모순된다. 제약 조건을 위반하는 솔루션은 더 낮은 점수를 받아야 한다. 그것이 페널티 항(penalty terms)을 사용하는 핵심 이유이기 때문이다. 무언가 잘못되었다. 우리는 깊이 파고들었다.
세 가지 버그, 세 가지 다른 모습
버그 1 — 페널티 가중치(penalty weight)가 보이지 않을 정도로 작았다
# phase2c_qaoa_simulator.py, line 56
LAMBDA_C = 0.5 # ← 너무 작음
...
QAOA는 용량 제약 조건(capacity constraint)을 위반하고 있었지만, 이를 "느낄" 수 없었다. 위반에 대한 페널티가 위반하지 않았을 때와 거의 차이가 없었기 때문이다. 페널티 항이 비용 함수(cost function) 속으로 녹아 없어져 버린 것이다.
버그 2 — MILP가 스스로 모순되었으나 조용히 "최적(Optimal)"이라고 불렀다
MILP 모델에는 두 가지 제약 조건이 있었다: "모든 ATM을 방문할 것"과 "차량 용량을 초과하지 말 것"이다. 하지만 총 수요(330k TL)가 이미 용량 제한(250k TL)을 초과했다. 즉, 어떤 실행 가능한 솔루션(feasible solution)도 두 조건을 동시에 만족할 수 없음을 의미한다.
PuLP/CBC는 **"Optimal"**을 반환했다. 조용히 말이다. 경고도 없었다. 숫자들이 표에 나타났고, 아무도 그것에 의문을 제기하지 않았다.
버그 3 — 잘못된 지표(metric)를 측정하고 있었다
p=1과 p=3이 동일한 결과를 보인 이유는, 비교 코드가 QAOA의 기대 에너지(expectation energy) 대신 argmax-bit 솔루션을 측정했기 때문이다. 모든 p 값에 대해 argmax는 [1,1,1,1,1]을 선택했고, 매번 동일한 숫자를 산출했다. 회로는 실제로 다르게 작동하고 있었지만, 측정 방식이 이를 구분해내지 못한 것이다.
수정된 결과
| Method | <H> Energy | Bit Energy | Gap | Constraints |
|---|---|---|---|---|
| Brute Force | — | −34.856 | 0% | ✅ |
| ... |
수정된 결과는 실제로 훨씬 더 설득력이 있다: QAOA는 작동하며, 회로 깊이(circuit depth)는 진정으로 중요하다. 하지만 이 작은 규모에서도 최적해(optimal)보다 2~4% 뒤처진다. 이는 기존 문헌과 물리적으로 예상되는 동작과 일치한다.
왜 아무도 이것을 잡아내지 못했을까?
세 가지 버그의 공통점을 살펴보십시오. 코드는 **구조적으로 정확(structurally correct)**했습니다. 구문 오류(syntax error)도 없었고, 변수 이름도 합리적이었으며, 함수가 함수를 호출하고 차트도 정상적으로 렌더링되었습니다. 어떤 린터(linter)도 이를 지적하지 않았을 것입니다. 어떤 아키텍처 규칙(architecture rule)도 작동하지 않았을 것입니다. 코드 리뷰(code review)에서도
- 버그 1 (Bug 1): 만약 명세(spec)에 _"패널티 항(penalty term)은 최대 경로 비용을 초과해야 한다"_라고 명시되어 있었다면, 이는 테스트 벡터(test vector)가 됩니다.
lambda=0.5인 경우, 병합(merge) 시점에 즉시 실패하게 됩니다. - 버그 2 (Bug 2): _"최적(Optimal)으로 반환된 솔루션은 모든 제약 조건(constraints)을 충족해야 한다"_라는 조건은 솔버(solver)의 출력을 자동으로 검증할 것이며, 조용한 실행 불가능성(silent infeasibility)을 방지할 것입니다.
- 버그 3 (Bug 3): _"회로 깊이(circuit depth)가 증가함에 따라 기대 에너지(expectation energy)가 개선되어야 한다"_라는 조건은 두 p 값이 동일한 숫자를 반환하는 즉시 테스트를 중단시킬 것입니다.
이 중 그 어떤 것도 사람이 당일에 이를 확인하도록 생각할 필요가 없습니다. 이들은 명세(spec)에 한 번 인코딩(encoded)되면, 매번 자동으로 검증됩니다.
한 가지 더: 양자(quantum)에 관한 질문
직접 다룰 가치가 있는 타당한 질문이 있습니다: "만약 양자 컴퓨터가 모든 솔루션을 한 번에 시도한다면, 왜 그냥 이길 수 없는가?"
그 답은 **중첩(superposition)**과 **얽힘(entanglement)**의 차이에 있습니다.
중첩(superposition)은 큐비트(qubit)가 측정 전 0과 1을 동시에 유지함을 의미하며, 이론적으로 전체 솔루션 공간(solution space)에 걸친 병렬 탐색을 가능하게 합니다. 얽힘(entanglement)은 다릅니다. 이는 큐비트들을 상관(correlate)시켜 하나를 측정하는 즉시 다른 큐비트들에 대해 무언가를 알 수 있게 합니다. 이것은 가변성(variability)이 아니라 강력한 의존성(dependency)입니다. 최적화(optimization)에서 진정한 마법은 중첩(superposition)입니다. 얽힘(entanglement)은 이를 지원합니다.
문제는 측정(measurement)입니다. 중첩(superposition)은 단일한 고전적 결과(classical result)로 붕괴(collapse)합니다. QAOA의 역할은 파동이 서로를 강화하거나 상쇄하는 방식처럼, 간섭(interference)을 통해 정답의 확률을 증폭시키는 것입니다. 오늘날의 NISQ 하드웨어에서는 이러한 간섭 제어가 너무 노이즈(noisy)가 심해 신뢰성 있게 작동하기 어렵습니다.
우리의 20-ATM 문제는 대략 **120만 개의 물리적 큐비트(physical qubits)**를 필요로 합니다. 현재 최고의 하드웨어는 1,000개에서 10,000개 사이 어딘가에 머물러 있습니다. 결함 허용(fault-tolerant) 하드웨어가 성숙해질 때까지, 즉 아마도 10년에서 15년 뒤가 될 때까지는 이 규모에서 실질적인 이점은 존재하지 않습니다.
그것이 "여기서 양자 컴퓨팅이 쓸모없다"는 뜻은 아닙니다. 이 프로젝트의 자원 추정 (resource estimation) 분석에 따르면, 문제 규모가 커짐에 따라 (20 → 50 → 100 → 200 ATMs), 고전적인 MILP (Mixed-Integer Linear Programming)가 어려움을 겪고 양자 컴퓨팅이 이론적으로 기여할 수 있는 임계값 (threshold)이 나타납니다. 그 임계값에 오늘날에는 도달할 수 없다는 사실을 아는 것이 중단해야 할 이유는 아닙니다. 오히려 적절한 순간을 위해 준비해야 할 이유입니다.
우리가 현재 하고 있는 일
이 프로젝트는 우리가 GitHub Spec Kit 생태계를 위해 제안하고 있는 Golden Demo + Behavioral Drift 확장이 왜 존재해야 하는지에 대한 실시간 사례 연구 (live case study)가 되었습니다. 이는 추상적인 아이디어가 아니라, 세 가지 실제 버그를 증거로 입증된 필요성입니다.
우리는 spec-kit Discussions에 RFC (Request for Comments)를 게시했습니다. v1 범위는 의도적으로 좁게 설정되었습니다: 순수 함수 (pure functions)만 허용하며, 명시적인 입출력 관계를 가지며, 부작용 (side effects)이 없어야 합니다. 새로운 검증 도구의 실제 위험은 노이즈가 많은 거짓 양성 (false positives)을 생성하여, 사용자들이 이틀 만에 모두 기능을 비활성화하게 만드는 것입니다.
만약 이 문제가 익숙하게 느껴진다면 — 즉, AI가 생성한 코드가 맞아 보인다는 이유로 승인했다가 몇 주 후에 동작 버그 (behavioral bug)를 발견하는 상황이라면 — RFC에 대한 여러분의 의견을 듣고 싶습니다.
링크
- github.com/jasstt/stochastic-VRP-decision-focused-learning
- Spec Kit Discussions · https://github.com/github/spec-kit/discussions
제가 실수한 부분이 있다면 아래에 언급해 주세요 :)
스택: Python · Q# · PuLP · Microsoft QDK · Azure Quantum Resource Estimator
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기