SeaWolf-AI commited on
Commit
59cede3
·
verified ·
1 Parent(s): 2c28967

VERITAS 부착: 답+마지막층 은닉 동시 반환 핸들러

Browse files
Files changed (1) hide show
  1. handler.py +104 -0
handler.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """VERITAS 부착 핸들러 — JGOS-398B-fp8 (9-팀장 2026-09-17)
3
+
4
+ 무엇을 하나
5
+ 한 번의 호출로 **답과 속 신호를 같이** 돌려준다.
6
+ 속 신호 = 마지막 층·마지막 토큰의 은닉벡터(가공 안 된 원본값).
7
+ 이것이 VERITAS 가 읽는 바로 그 값이며, **토큰을 한 개도 더 쓰지 않는다**
8
+ (이미 계산된 것을 꺼내 올 뿐이다).
9
+
10
+ 두 가지 모드
11
+ mode="generate" : 답 생성 + 그 답까지 포함한 은닉 (문항 풀이용)
12
+ mode="embed" : 주어진 글자열의 은닉만 (이미 뽑아 둔 답을 채점할 때)
13
+
14
+ 🔴 규율
15
+ - 잘림(length)을 결과에 반드시 표시한다. 자원 소진을 오답으로 세지 않기 위해서다.
16
+ - 은닉은 정규화하지 않는다. 정규화하면 VERITAS 가 쓰는 크기 정보가 날아간다.
17
+ - 생성분이 reasoning 쪽으로 빠지는 템플릿이면 그것까지 합쳐 돌려준다(버리지 않는다).
18
+ """
19
+ from __future__ import annotations
20
+
21
+ from typing import Any, Dict, List
22
+
23
+ import torch
24
+ from transformers import AutoModelForCausalLM, AutoTokenizer
25
+
26
+
27
+ class EndpointHandler:
28
+ def __init__(self, path: str = ""):
29
+ self.tok = AutoTokenizer.from_pretrained(path)
30
+ if self.tok.pad_token is None:
31
+ self.tok.pad_token = self.tok.eos_token
32
+ self.tok.padding_side = "left" # 🔴 디코더 모델 배치의 필수 조건
33
+ self.model = AutoModelForCausalLM.from_pretrained(
34
+ path, dtype="auto", device_map="auto"
35
+ ).eval()
36
+ cfg = self.model.config
37
+ cfg = cfg.get_text_config() if hasattr(cfg, "get_text_config") else cfg
38
+ self.n_layers = cfg.num_hidden_layers
39
+
40
+ # ---- 내부: 마지막 층·마지막 토큰 은닉 ------------------------------
41
+ @torch.no_grad()
42
+ def _hidden(self, ids) -> List[List[float]]:
43
+ out = self.model(**ids, output_hidden_states=True, use_cache=False)
44
+ h = out.hidden_states[-1] # 마지막 층
45
+ mask = ids["attention_mask"]
46
+ last = mask.sum(dim=1) - 1 # 좌패딩이라 실제 마지막 위치
47
+ picked = h[torch.arange(h.size(0), device=h.device), last, :]
48
+ return picked.float().cpu().tolist()
49
+
50
+ def __call__(self, data: Dict[str, Any]) -> Dict[str, Any]:
51
+ inputs = data.get("inputs")
52
+ if isinstance(inputs, str):
53
+ inputs = [inputs]
54
+ p = data.get("parameters") or {}
55
+ mode = p.get("mode", "generate")
56
+
57
+ # ── 은닉만 ──────────────────────────────────────────────────
58
+ if mode == "embed":
59
+ ids = self.tok(inputs, return_tensors="pt", padding=True,
60
+ truncation=True, max_length=p.get("max_length", 32768))
61
+ ids = {k: v.to(self.model.device) for k, v in ids.items()}
62
+ return {"hidden": self._hidden(ids), "layer": self.n_layers,
63
+ "normalized": False}
64
+
65
+ # ── 생성 + 은닉 ─────────────────────────────────────────────
66
+ max_new = int(p.get("max_new_tokens", 16384))
67
+ n = int(p.get("n", 1))
68
+ temp = float(p.get("temperature", 0.0))
69
+ texts = []
70
+ for s in inputs:
71
+ if p.get("chat", True):
72
+ msgs = [{"role": "user", "content": s}]
73
+ kw = {}
74
+ if p.get("enable_thinking") is not None:
75
+ kw["enable_thinking"] = p["enable_thinking"]
76
+ try:
77
+ texts.append(self.tok.apply_chat_template(
78
+ msgs, tokenize=False, add_generation_prompt=True, **kw))
79
+ except TypeError:
80
+ texts.append(self.tok.apply_chat_template(
81
+ msgs, tokenize=False, add_generation_prompt=True))
82
+ else:
83
+ texts.append(s)
84
+ enc = self.tok(texts, return_tensors="pt", padding=True)
85
+ enc = {k: v.to(self.model.device) for k, v in enc.items()}
86
+ gen_kw = dict(max_new_tokens=max_new, pad_token_id=self.tok.pad_token_id)
87
+ if temp > 0:
88
+ gen_kw.update(do_sample=True, temperature=temp, num_return_sequences=n)
89
+ else:
90
+ gen_kw.update(do_sample=False)
91
+ with torch.no_grad():
92
+ g = self.model.generate(**enc, **gen_kw)
93
+ plen = enc["input_ids"].shape[1]
94
+ new = g[:, plen:]
95
+ outs = self.tok.batch_decode(new, skip_special_tokens=True)
96
+ ntok = (new != self.tok.pad_token_id).sum(dim=1).tolist()
97
+ # 🔴 잘림 표시 — 자원 소진을 오답으로 세지 않기 위해 결과에 남긴다
98
+ hit_cap = [int(t >= max_new - 2) for t in ntok]
99
+
100
+ att = torch.ones_like(g)
101
+ att[g == self.tok.pad_token_id] = 0
102
+ hid = self._hidden({"input_ids": g, "attention_mask": att})
103
+ return {"generated": outs, "hidden": hid, "n_tokens": ntok,
104
+ "hit_cap": hit_cap, "layer": self.n_layers, "normalized": False}