생성 대신 채점 — SGLang /v1/score로 오픈 LLM을 분류 엔진으로 쓰기

생성 대신 채점 — SGLang /v1/score로 오픈 LLM을 분류 엔진으로 쓰기

2026, Sep 28    

Avi Chawla(@_avichawla)가 트윗으로 소개한 글 「Build your own Jev (100% local)」을 읽고 정리했다. 분류처럼 답이 정해진 작업에서 LLM에게 텍스트를 만들게 하지 않고, 첫 출력 벡터의 로짓만 읽어 확률 분포를 얻는 방식이다. 재학습이 필요 없고 SGLang과 오픈 Qwen 모델만 있으면 된다. (이 글은 Claude가 원문을 직접 확인하고 작성했다.)

TL;DR: 선택지가 고정된 작업에서는 생성 대신 채점(scoring) 을 쓸 수 있다. SGLang의 /v1/score에 프롬프트와 레이블 토큰 ID를 넘기면, 모델은 포워드 패스 한 번을 돌고 지정한 어휘 위치의 로짓을 읽어 softmax를 씌운 확률 분포를 돌려준다. 자기회귀 디코딩 루프가 없다. 제약은 레이블이 단일 토큰이어야 한다는 것(Qwen2.5 기준 A=32, B=33, C=34). 한계도 분명하다. 이건 Jev의 추론 경로만 재현한 것이고 학습·보정은 빠져 있으며, 보정 없는 확률값은 정확도가 아니다.

1. 무엇을 하는 방식인가

원문이 재현 대상으로 삼은 Jev는 애플리케이션이 질의와 함께 허용된 답 목록을 넘기면 모델이 각 답의 점수를 돌려주는 구조다. 티켓 분류라면 티켓 본문과 세 개의 허용 답을 같이 주고, 모델은 세 답 각각에 점수를 매긴다.

원문 근거
"the application provides the ticket/query and the three allowed answers to Jev. The model then returns a score for each answer."

여기서 중요한 건 생성을 하지 않는다는 점이다. 표준 생성은 없던 텍스트를 만들어 내지만, 채점은 미리 정한 위치의 어휘 벡터를 읽어 고정된 집합에서 고를 뿐이다.

2. 구조화 출력과 무엇이 다른가

JSON 스키마를 강제하는 구조화 출력(structured output)과 헷갈리기 쉽다. 둘의 차이는 결정적이다. 구조화 출력은 여전히 토큰을 순차 생성한다. 중괄호, 필드명, 값이 모두 디코딩 단계를 거친다. 채점은 자기회귀 생성 없이 특정 토큰 위치의 로짓을 읽는다.

원문 근거
"Structured output still generates tokens sequentially (braces, field names, values). Scoring reads logits at specific token positions without autoregressive generation."

정의역이 레이블 집합으로 닫혀 있다는 점도 실무에서는 크다. 생성 방식은 카테고리명을 모델이 직접 써야 해서 표기가 흔들리거나 목록에 없는 값이 나올 수 있고, 그때마다 파싱과 후처리가 필요하다. 채점에는 그 실패 경로 자체가 없다.

3. SGLang /v1/score 사용법

엔드포인트가 하는 일은 네 단계다.

  1. 프롬프트를 모델에 통과시킨다
  2. 지정한 토큰 ID들의 로짓을 읽는다
  3. 그 위치들에 대해서만 softmax를 적용한다
  4. 확률 분포를 반환한다
requests.post(f"{BASE_URL}/v1/score", json={
    "model": MODEL,
    "query": prompt,
    "items": [""],
    "label_token_ids": [32, 33, 34],   # A, B, C
    "apply_softmax": True,
})

query에는 답이 들어갈 자리 직전까지의 프롬프트를 넣는다. 모델이 그다음에 무엇을 낼지 예측한 첫 벡터에서 label_token_ids에 해당하는 위치만 읽기 때문이다.

4. 걸리는 제약: 레이블은 단일 토큰

모든 선택지가 같은 출력 위치의 어휘 항목 하나로 표현돼야 한다. 여러 토큰으로 쪼개지는 레이블은 한 위치에서 점수를 매길 수 없다.

원문 근거
"single-token labels avoid that problem. Every option is represented by one vocabulary entry at the same output position."

원문은 Qwen2.5-0.5B-Instruct로 토큰 ID를 확인한다. A → 32, B → 33, C → 34다. 실제로 쓸 때는 쓰려는 모델의 토크나이저로 직접 확인해야 한다. 모델마다 어휘가 다르므로 이 숫자를 그대로 옮기면 안 된다.

한국어 업무에 붙일 때 이 제약이 바로 문제가 된다. “주차 민원”이나 “소음” 같은 카테고리명은 단일 토큰이 아니다. 그래서 실제 구성은 이렇게 된다.

  • 레이블은 A/B/C 같은 단일 토큰 기호로 둔다
  • 기호와 카테고리의 매핑은 프롬프트 안에 적는다
  • 응답으로 받은 확률 분포를 애플리케이션에서 카테고리로 되돌린다

선택지가 많아지면 단일 토큰으로 남는 기호를 확보할 수 있는지부터 확인해야 한다.

5. 얼마나 빨라지나

원문은 Qwen3-4B-Instruct-2507로, 요청당 최대 32토큰을 생성하는 표준 생성과 채점을 동시 배치로 비교한다. 영상으로는 채점이 눈에 띄게 먼저 끝난다고 설명하지만, 지연 시간이나 처리량의 구체적인 수치는 제시하지 않는다. 속도 우위의 근거는 벤치마크 수치가 아니라 구조다. 생성은 토큰 수만큼 포워드 패스를 반복하지만 채점은 한 번으로 끝난다.

직접 적용을 검토한다면 자기 환경에서 측정해 보는 편이 낫다. 모델 크기, 프롬프트 길이, 배치 크기에 따라 이득의 폭이 달라진다.

6. 한계 — 원문이 직접 밝힌 것

원문은 두 가지를 명시한다. 이 방식을 실무에 올릴 때 가장 중요한 대목이다.

원문 근거
"this article recreates the inference path, not the complete Jev system. Jev also includes training and calibration work that a scoring endpoint does not provide."

첫째, 추론 경로만 재현했다. Jev의 학습과 보정(calibration) 작업은 스코어링 엔드포인트가 주지 않는다. 둘째, 라벨링된 평가 데이터 없이는 확률값이 실제 정확도를 뜻하지 않는다. 0.9가 나왔다고 90% 맞는다는 보장이 없다.

이 두 가지를 합치면 “재학습 없이”라는 말의 범위가 분명해진다. 분류기를 새로 학습시킬 필요가 없다는 뜻이지, 평가셋 없이 바로 운영할 수 있다는 뜻이 아니다.

7. 적용 관점 — 고정 분류 업무

민원 분류처럼 분류 체계가 고정돼 있고 라우팅·에스컬레이션 판단에 확신도가 필요한 업무에 형태가 잘 맞는다. 확률 분포가 나오므로 “상위 확률이 임계값 미만이면 사람에게 넘긴다” 같은 운영 규칙을 바로 얹을 수 있다.

다만 순서를 틀리면 안 된다. 임계값을 정하려면 그 확률이 얼마나 믿을 만한지 알아야 하고, 그건 라벨링된 평가셋으로 보정한 뒤에야 나온다. 실제 작업량의 대부분은 스코어링 연동이 아니라 평가셋 구축과 보정이 될 가능성이 크다. 벤치마크 점수와 실제 운영 성능이 어긋나는 문제는 토스의 자체 LLM 벤치마크 사례에서도 같은 형태로 나타났다.

로컬 실행 쪽 준비는 RTX PC 로컬 LLM 가이드나 오픈소스 에이전트 스택 정리를 참고할 수 있다.

8. 참고 자료