CoolFace
Modelpublic

kimseunguk/converge-slot-extractor

sourceHugging Faceapache-2.0updated 4d agoView on Hugging Face
0likes178downloads
Model Card

converge-slot-extractor

한국어 현장 집결 조율 문장에서 슬롯 정보를 추출해 JSON으로 출력하는 GGUF 모델 3종이다. 도메인은 함정 정비(ship_maintenance)와 SI 배포(si_deploy) 두 가지다. Colab T4에서 QLoRA로 학습한 뒤 llama.cpp로 Q4KM 양자화했다.

파일

파일크기
qwen3_0.6b-Q4_K_M.gguf0.37 GiB
kanana_2.1b-Q4_K_M.gguf1.30 GiB
qwen3_1.7b-Q4_K_M.gguf1.03 GiB

실행에는 각 GGUF와 짝인 .prompt.json(프롬프트 앞뒤 문자열)과 grammar.gbnf(출력 JSON 문법)가 함께 필요하다. 두 파일은 학습 저장소의 Drive 경로(runs/<모델>/gguf/, finetune/export/grammar.gbnf)에 있다.

하는 일

입력 문장 예: "오후 2시 마곡 고객사 미팅. 대리님이 차 있어서 부천 지하철역 앞에서 나 태우고 감. 장비 있어서 대리님 성북구, 나 부천. 각자 언제 나가?"

출력 예 (학습 데이터 실제 정답):

json
{"preset": "si_deploy", "datetime": "화요일 14:00", "purpose": "고객사 미팅", "destination": "마곡 고객사", "participants": [{"name": "대리님", "origin": "성북구", "transport": "car", "pickup": null, "pickup_at": null, "unfamiliar": false, "role": "colleague"}, {"name": "나", "origin": "부천", "transport": "car", "pickup": "대리님", "pickup_at": "부천 지하철역 앞", "unfamiliar": false, "role": "site"}], "rendezvous": null, "access": {"required": true, "applied": null, "lead_min": null}, "schedule_confirmed": true, "constraints": ["luggage"], "missing": [], "out_of_scope": false}

출력 스키마

최상위 키 11개: preset, datetime, purpose, destination, participants, rendezvous, access, schedule_confirmed, constraints, missing, out_of_scope.

  • preset: ship_maintenance 또는 si_deploy. 범위 밖 요청(out_of_scope true)은 null
  • participants 각 항목 키 7개: name, origin, transport, pickup, pickup_at, unfamiliar, role
  • transport: car, transit, walk 또는 null
  • pickup: 태워주는 운전자 name, 없으면 null
  • pickup_at: 태우는 지점 (예: "부천 지하철역 앞"). 전원이 모이는 집결지가 아니라 개별 픽업 장소다. 없으면 null
  • role: site, vendor, colleague, client
  • rendezvous: 전원이 모이는 집결지 {"name", "transport_after"} 또는 null
  • access: {"required", "applied", "lead_min"} 또는 null
  • constraints: 문자열 목록 (없으면 []). 장비 있는 날은 luggage가 들어간다
  • missing: 알 수 없는 값의 위치 목록 (없으면 [])

학습 데이터

합성 데이터 591건을 템플릿과 어휘 사전 조합으로 생성했다 (gen_dataset.py, LLM 생성 아님). 입력 문장 중복 없이 train 551건 / valid 40건으로 나눴다.

원본 591건 카테고리 분포:

카테고리건수
sideploynight70
ship_regular70
ship_emergency60
ship_reschedule53
follow_up50
outofscope50
sihqgather50
siclientmeeting49
missing44
pickup40
kakao35
unfamiliar20

학습 설정

  • 방식: QLoRA 4bit (nf4, double quant, 계산 fp16), gradient checkpointing
  • LoRA: rank 16, alpha 32, dropout 0.05, 대상 q/k/v/o/gate/up/down_proj
  • epoch 3, lr 2.0e-4, warmup 3%, linear 스케줄러, 유효 배치 16, maxseqlength 1152, 시드 42
  • 환경: Colab T4 GPU, torch 2.11.0+cu128, transformers 5.16.1
  • 베이스 revision 고정: Qwen3-0.6B c1899de2, kanana-1.5-2.1b-instruct-2505 7df4bc35, Qwen3-1.7B 70d244cc

결과 (valid 40건, 2026-09-18 측정)

latency는 T4 GPU fp16 generate 기준 건당 생성 시간이다. GGUF CPU 속도가 아니다.

모델단계exact_matchslot_f1field_acc 평균latency_p50 (s)latency_p95 (s)
Qwen3-0.6Bbase (학습 전)0.0%32.5%31.2%10.1813.83
Qwen3-0.6Blora (학습 후)47.5%95.3%94.8%9.3111.85
Kanana-1.5-2.1B-Instructbase (학습 전)7.5%47.8%53.8%8.9612.52
Kanana-1.5-2.1B-Instructlora (학습 후)85.0%99.8%98.8%8.6511.95
Qwen3-1.7Bbase (학습 전)7.5%38.1%49.7%7.9314.94
Qwen3-1.7Blora (학습 후)45.0%96.4%92.0%8.8011.98

슬롯별 lora F1 (pickup_at 포함):

모델pickuppickup_at
Qwen3-0.6B100.0%100.0%
Kanana-1.5-2.1B-Instruct100.0%85.7%
Qwen3-1.7B100.0%100.0%

GGUF CPU 스모크 테스트 (스레드 2)

모델파일스키마 검사CPU 생성 시간 (s)
Qwen3-0.6Bqwen3_0.6b-Q4_K_M.gguf통과43.45
Kanana-1.5-2.1B-Instructkanana_2.1b-Q4_K_M.gguf통과109.62
Qwen3-1.7Bqwen3_1.7b-Q4_K_M.gguf통과82.18

위 시간은 Colab CPU 1건 측정값이다. 실제 서빙 환경에서는 반드시 재측정해야 한다.

베이스 모델 라이선스

  • Qwen3-0.6B, Qwen3-1.7B: Apache 2.0 (Qwen/Qwen3-0.6B, Qwen/Qwen3-1.7B)
  • Kanana-1.5-2.1B-Instruct: Apache 2.0 (kakaocorp/kanana-1.5-2.1b-instruct-2505)

사용 예시

python
import json
from llama_cpp import Llama, LlamaGrammar

llm = Llama(model_path="kanana_2.1b-Q4_K_M.gguf", n_ctx=2048)
pf = json.load(open("kanana_2.1b-Q4_K_M.prompt.json", encoding="utf-8"))
prompt = pf["prefix"] + "오후 2시 마곡 고객사 미팅. 대리님이 차 있어서 부천 지하철역 앞에서 나 태우고 감. 장비 있어서 대리님 성북구, 나 부천. 각자 언제 나가?" + pf["suffix"]
tokens = llm.tokenize(prompt.encode("utf-8"), add_bos=pf["add_bos"], special=True)
grammar = LlamaGrammar.from_file("grammar.gbnf", verbose=False)
out = llm.create_completion(
    prompt=tokens,
    max_tokens=pf["max_new_tokens"],
    temperature=pf["temperature"],
    grammar=grammar,
    stop=pf["stop"],
)
print(out["choices"][0]["text"])