- Published on
CoT 노출 없이 추론력 높이기 - SCR·Rationale Distill
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서빙 환경에서 LLM의 추론력을 올리고 싶을 때, 가장 먼저 떠오르는 방법은 Chain-of-Thought(이하 CoT) 프롬프트입니다. 하지만 CoT를 그대로 사용자에게 노출하면 다음 문제가 곧바로 생깁니다.
- 보안: 내부 정책, 규칙, 프롬프트 템플릿이 역으로 학습되거나 유출될 수 있음
- 안전: 모델이 불필요한 상세 추론을 길게 내보내며 환각도 함께 “그럴듯하게” 포장됨
- 비용: 토큰이 늘어 지연과 비용이 증가
- 제품 품질: 사용자는 “답”을 원하지 “생각 과정”을 읽고 싶어하지 않음
그래서 최근 실무에서는 추론은 하되, 그 추론을 사용자에게 그대로 보여주지 않는 방향으로 최적화가 진행됩니다. 이 글에서는 그 중심에 있는 두 축, SCR(Self-Consistency with Rationales 계열 접근) 과 Rationale Distillation(근거 증류) 를 정리하고, API 설계와 평가까지 연결해 보겠습니다.
또한 CoT가 막히는 상황에서 디버깅 관점이 필요하다면, 먼저 이 글도 함께 보는 것을 추천합니다: Chain-of-Thought 막히면? Self-Ask+ReAct 디버깅
CoT를 “숨기는 것”과 “없애는 것”은 다르다
먼저 용어를 정리해야 합니다.
- 숨기는 것: 모델 내부적으로는 단계적 추론을 수행하지만, 출력에는 요약된 답만 내보냄
- 없애는 것: 단계적 추론 자체를 유도하지 않고도 정답률이 나오도록 학습/증류/검색/도구 사용으로 보완
SCR과 Rationale Distillation은 보통 “숨기는 것”에 가깝지만, 최종적으로는 “없애는 것”에 가까운 서빙 형태(짧은 답변)로 수렴시킵니다.
실무에서 중요한 점은 추론 토큰을 출력에서 제거하는 것만으로는 부족하다는 것입니다. 모델이 추론을 했다는 사실이 품질에 기여했다면, 그 추론을 어떤 형태로든 학습/평가/서빙 파이프라인에 반영해야 합니다.
SCR: 한 번의 CoT가 아니라, 여러 번의 “일관성”으로 정답을 고른다
SCR은 구현과 문헌에 따라 이름이 조금씩 섞여 쓰이지만, 실무적으로는 다음 패턴으로 이해하면 됩니다.
- 동일 질문에 대해 여러 개의 추론 경로(샘플) 를 생성
- 각 샘플의 최종 답을 모아서 다수결 또는 스코어링
- 선택된 답만 사용자에게 제공(추론 텍스트는 숨김)
이 방식의 핵심은 “한 번의 긴 CoT”가 아니라, 여러 번의 짧은 시도에서 안정적인 답을 선택한다는 점입니다. 특히 수학, 논리, 규칙 기반 문제에서 강합니다.
SCR이 효과적인 이유
- 단일 샘플에서 발생하는 환각을 다수 샘플의 합의로 상쇄
- 한 경로가 막혀도 다른 경로에서 풀릴 확률이 증가
- 모델이 스스로 오류를 정정할 가능성이 올라감
단, 비용이 늘어납니다. 따라서 SCR은 “항상”이 아니라 조건부로 켜는 전략이 중요합니다.
- 난이도 분류기(간단 질문은 1샷, 어려운 질문은 SCR)
- 실패 감지(자기모순, 규칙 위반, 낮은 confidence) 시에만 SCR
- SLA가 허용되는 특정 엔드포인트에만 적용
OpenAI/호환 API에서의 SCR 스케치
아래는 “여러 번 뽑아서 합의로 답을 선택”하는 최소 예시입니다. 중요한 점은 응답에서 reasoning 혹은 내부 추론 텍스트를 사용자에게 그대로 전달하지 않는다는 것입니다.
import OpenAI from "openai";
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
type Sample = { answer: string; raw: string };
function majorityVote(answers: string[]): string {
const m = new Map<string, number>();
for (const a of answers) m.set(a, (m.get(a) ?? 0) + 1);
return [...m.entries()].sort((x, y) => y[1] - x[1])[0][0];
}
export async function scrAnswer(question: string) {
const k = 5;
const samples: Sample[] = [];
for (let i = 0; i < k; i++) {
const resp = await client.chat.completions.create({
model: "gpt-4.1-mini",
temperature: 0.8,
messages: [
{
role: "system",
content:
"문제를 풀되, 최종 답만 한 줄로 출력하라. 중간 추론은 출력하지 말라.",
},
{ role: "user", content: question },
],
});
const raw = resp.choices[0].message.content ?? "";
// 방어적으로 파싱: 모델이 규칙을 어길 수 있으니
const answer = raw.trim().split("\n")[0];
samples.push({ answer, raw });
}
const finalAnswer = majorityVote(samples.map((s) => s.answer));
return {
answer: finalAnswer,
// raw는 로깅/평가 용도로만 저장하고 사용자에게는 미노출
debug: { samples },
};
}
이 예시는 “출력에서 CoT를 금지”하는 방식이지만, 실제로는 모델이 내부적으로 추론을 했을 수도 있고 안 했을 수도 있습니다. 중요한 건 합의 기반으로 정답을 고르는 외부 알고리즘이 성능을 보강한다는 점입니다.
SCR의 실무 함정
- 다수결이 항상 정답은 아니다: 모델이 동일한 편향으로 같은 오답을 반복할 수 있음
- 파싱 취약성: 답을 한 줄로 제한해도 형식을 어길 수 있음
- 비용 폭증:
k를 올리면 선형으로 비용과 지연이 증가
따라서 SCR은 “정답 선택 로직”을 고도화하는 편이 좋습니다.
- 다수결 대신 검증기(verifier) 모델로 스코어링
- 규칙 기반 검사(단위, 범위, 포맷)로 탈락 처리
- 외부 툴(계산기, 검색, DB)로 사후 검증
Rationale Distillation: 긴 추론을 “교사 데이터”로만 쓰고, 학생은 짧게 답한다
Rationale Distillation은 한마디로 긴 CoT는 학습용으로만 쓰고, 서빙 모델은 짧게 답하도록 만드는 증류 전략입니다.
일반적인 파이프라인은 다음과 같습니다.
- 교사(Teacher) 모델에 CoT를 허용해 고품질 rationales를 생성
- 그 rationales를 이용해 학생(Student) 모델을 학습
- 서빙 시 학생 모델은 짧은 답만 출력 (또는 “짧은 근거 요약”만 출력)
여기서 포인트는 “rationale을 사용자에게 숨긴다”가 아니라, 학생 모델이 rationale 없이도 정답을 내는 방향으로 파라미터가 이동한다는 것입니다.
왜 증류가 필요한가
- SCR은 서빙 비용이 높아지기 쉽고, 지연이 늘어남
- CoT를 금지하면 성능이 떨어지는 작업이 존재
- 결국 “답만 짧게 잘하는 모델”이 제품에는 더 유리
증류는 이 딜레마를 완화합니다.
데이터 스키마 예시
증류 데이터를 만들 때는 “정답”과 “근거”를 분리해 저장하는 것이 좋습니다. 근거는 학습에만 쓰고, 평가나 서빙 로그에서는 접근 통제를 걸 수 있습니다.
{
"id": "ex_001",
"prompt": "정수 n이 주어질 때 1부터 n까지 합을 구하라.",
"rationale": "(학습용) 등차수열 합 공식을 적용해 ...",
"answer": "n(n+1)/2"
}
MDX/프론트엔드 관점에서도 rationale 는 관리자 화면에서만 볼 수 있게 하고, 일반 유저 응답에는 포함하지 않는 식으로 설계합니다.
간단한 증류 학습 프롬프트 전략
증류에서 흔히 쓰는 방식은 “입력은 동일, 출력은 정답만”입니다. 즉, 학습 시에는 교사가 만든 rationale로 학생이 내적 패턴을 배우게 하되, 학생의 타깃 출력은 짧게 둡니다.
- 입력: 문제 + (선택) 교사 rationale
- 타깃: 정답만
또는 “짧은 근거 요약”까지만 허용할 수도 있습니다.
- 타깃:
정답: ...+근거(한 문장): ...
이때도 주의점은 같습니다. 긴 CoT를 그대로 노출하는 순간, 제품 정책/안전 요구사항과 충돌할 수 있습니다.
“CoT 미노출”을 제품에서 안전하게 구현하는 패턴
추론 텍스트를 숨긴다는 건 단순히 UI에서 감추는 문제가 아닙니다. 로그, APM, 에러 리포팅, CS 티켓에 섞여 나가면 동일하게 유출입니다.
권장 아키텍처
- 모델 응답을
answer와debug로 분리 debug는 별도 저장소(접근 제어) 또는 짧은 TTL로만 보관- 관측성은 토큰/지연/에러 타입 중심으로 수집하고, 원문 텍스트는 최소화
서빙 인프라 비용과 지연이 민감하다면, 콜드스타트와 503을 줄이는 튜닝도 함께 봐야 합니다. SCR처럼 멀티샘플링은 지연을 늘리기 쉬워서 인프라 세팅 영향이 큽니다: GCP Cloud Run 503·콜드스타트 줄이는 설정 7가지
응답 포맷 강제와 검증
모델이 규칙을 어기면 CoT가 새어 나갈 수 있으므로, 출력 포맷을 강제하고 서버에서 검증합니다.
type ModelReply = { answer: string };
function sanitizeToAnswerOnly(text: string): string {
// 1) 첫 줄만 채택
const firstLine = text.trim().split("\n")[0] ?? "";
// 2) 금지 패턴 방어(예: "생각:", "추론:")
const banned = [/^\s*(생각|추론|chain|cot)\s*[::]/i];
if (banned.some((re) => re.test(firstLine))) {
// 포맷 위반이면 빈 값 처리하거나 재시도
return "";
}
return firstLine;
}
export function toSafeReply(raw: string): ModelReply {
const answer = sanitizeToAnswerOnly(raw);
if (!answer) throw new Error("MODEL_FORMAT_VIOLATION");
return { answer };
}
실무에서는 여기서 한 단계 더 나아가
- 포맷 위반 시 재시도(temperature 낮추기)
- 그래도 실패하면 더 보수적인 모델/룰 기반 fallback
같은 정책을 둡니다.
평가: “추론력↑”을 숫자로 확인하는 방법
SCR과 증류는 감으로 도입하면 실패합니다. 최소한 아래 지표를 같이 봐야 합니다.
- 정답률(accuracy) 또는 태스크별 success rate
- 비용: 요청당 토큰, 요청당 과금
- 지연: p50/p95 latency
- 안정성: 포맷 위반률, 재시도율
- 안전: 금지 텍스트(추론/정책) 노출률
특히 SCR은 k 를 올리면 accuracy가 오르다가 어느 시점 이후 수렴하는데, 비용은 계속 증가합니다. 그래서 보통은
k=3또는k=5정도에서 시작- 어려운 문제에서만 켜기
- verifier를 붙여
k를 낮추기
같은 최적화가 현실적입니다.
실무 추천 조합: SCR로 데이터 만들고, 증류로 서빙을 가볍게
가장 많이 쓰이는 조합은 다음입니다.
- 초기에는 SCR로 품질을 끌어올려 서비스 안정화
- SCR 과정에서 생성된 고품질 샘플을 축적(정답, 실패 케이스, 선택된 답)
- 필요하면 교사 모델로 rationales를 추가 생성
- Rationale Distillation로 학생 모델을 학습해, 서빙은 1샷으로 회귀
이 흐름의 장점은
- 빠른 시간 내 품질 개선(SCR)
- 장기적으로 비용/지연 절감(증류)
- CoT 노출 위험을 구조적으로 낮춤
입니다.
마무리: “보여주지 않는 추론”을 시스템으로 만든다
CoT는 강력하지만, 제품에서는 그대로 노출하기 어렵습니다. SCR은 합의로 정답을 고르며 단기 성능을 올리고, Rationale Distillation은 그 성능을 짧은 답변 모델로 이전해 장기 비용을 낮춥니다.
정리하면 다음 체크리스트로 시작하면 시행착오를 줄일 수 있습니다.
- CoT를 UI에서만 숨기지 말고, 로그와 저장소까지 포함해 차단
- SCR은 조건부로 켜서 비용을 통제
- 출력 포맷 검증과 재시도 정책을 서버에서 강제
- SCR로 만든 고품질 샘플을 모아 증류로 1샷 서빙으로 복귀
이렇게 하면 “추론력은 높이고, 노출은 줄이는” 방향으로 LLM을 제품에 안전하게 넣을 수 있습니다.