Feature Extraction
Transformers
Safetensors
Korean
sysone
decision-model
classification
router
korean
custom_code
Instructions to use leapshared/nari-router-4b with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use leapshared/nari-router-4b with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("feature-extraction", model="leapshared/nari-router-4b", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("leapshared/nari-router-4b", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
nari-router-4b: nari-decision-4b v3.2 + ์ฐ๋ฆฌ ๋ง LoRA(ํฉ์นจ), ์จ์ 1 ยท 3์ํฌํฌ ยท lm_weight 0.5 ยท ์จ๋ 1.86
5451004 verified Download modeling_sysone.py from leapshared/nari-router-4b: direct link, hf CLI and curl.
- Browser
- Download file 20.2 kB
-
https://huggingface.co/leapshared/nari-router-4b/resolve/main/modeling_sysone.py
- Command line
-
hf download hf://leapshared/nari-router-4b/modeling_sysone.py
-
curl -L -o modeling_sysone.py https://huggingface.co/leapshared/nari-router-4b/resolve/main/modeling_sysone.py
20.2 kB
| """System One ๊ฒฐ์ ๋ชจ๋ธ: Qwen3.5 ํ ์คํธ ๋ชจ๋ธ(LoRA ๋ณํฉ) + ๊ฒฐ์ ํค๋ (Hugging Face `trust_remote_code`). | |
| ์ํ(state)์ ํ์ ์ด ์ ํด์ง ์ง๋ฌธ(noulยทchoiceยทscore)์ ๋ฐ์, ๊ธ์ ์์ฑํ์ง ์๊ณ ํ ๋ฒ์ forward๋ก | |
| ์ง๋ฌธ๋ง๋ค ์ ํ์ง๋ณ ๋ณด์ ๋ ํ๋ฅ ์ ๋ธ๋ค. | |
| from transformers import AutoModel, AutoTokenizer | |
| tok = AutoTokenizer.from_pretrained(REPO, trust_remote_code=True) | |
| model = AutoModel.from_pretrained(REPO, trust_remote_code=True).eval() | |
| model.decide( | |
| tok, | |
| state="๊ณ ๊ฐ: ๊ฒฐ์ ๊ฐ ๋ ๋ฒ ๋์ด์. ํ ๊ฑด์ ์ทจ์ํด ์ฃผ์ธ์.", | |
| questions={ | |
| "refund": {"type": "noul", "instructions": "ํ๋ถยท์ทจ์ ์์ฒญ์ธ๊ฐ์?"}, | |
| "team": {"type": "choice", "instructions": "๋ด๋น ํ์ ๊ณ ๋ฅด์ธ์.", | |
| "options": ["billing", "shipping", "account"]}, | |
| }, | |
| ) | |
| GPU์์ ํ ๊ฑด์ฉ ํ๋จํ ๋๋ `model.to("cuda").enable_cuda_graphs()`๋ฅผ ํ ๋ฒ ๋ถ๋ฅด๋ฉด decide()๊ฐ ๊ธธ์ด ๊ตฌ๊ฐ๋ณ | |
| CUDA ๊ทธ๋ํ๋ก ๋๋ค (A100์์ ์ฝ 3๋ฐฐ ๋น ๋ฅด๋ค. ์๋ `_GraphRunner`). | |
| ์ ๋ ฅ ํ์ v2(config.input_format)๋ถํฐ๋ ๊ฐ์ forward์์ ์ธ์ด ๋ชจ๋ธ ๊ธ์ ๊ฒฝ๋ก๋ ์ฝ๋๋ค: ์ ํ์ง์ ๊ธ์(A., B., โฆ)๋ฅผ | |
| ๋ถ์ด๊ณ ์ง๋ฌธ๋ง๋ค ๋ต ์๋ฆฌ๋ฅผ ๋์ด, ๊ทธ ์๋ฆฌ์ ๋ค์ ํ ํฐ ํ๋ฅ (์ถ๋ ฅ์ธต = ์ ๋ ฅ ์๋ฒ ๋ฉ)์ ๊ฒฐ์ ํค๋์ ํ๋ฅ ๊ณผ ์๋๋ค. | |
| ์ด ํ์ผ์ ์ ์ฅ์์ ๊ฐ์ด ์ฌ๋ผ๊ฐ ๋ฐ๋ ์ชฝ์์ ์คํ๋๋ค. ํ์ต ์ฝ๋(sysone)์ ๊ธฐ๋์ง ์๊ณ , ์ ๋ ฅ ํ์์ ํ์ต ๋์ | |
| ๊ฐ์ ๊ท์น์ ๋ฐ๋ฅธ๋ค (์ํ โ ์ง๋ฌธ๋ง๋ค ๋จธ๋ฆฌ๋งยท์ง์๋ฌธยท์ ํ์ง ์ค, ์ ํ์ง ์ค์ ๋ง์ง๋ง ํ ํฐ ์์น๋ก ์ ์, ์ํ๊ฐ ๊ธธ๋ฉด ๋ค๋ฅผ ์๋ฅธ๋ค). | |
| """ | |
| from __future__ import annotations | |
| import contextlib | |
| import json | |
| import threading | |
| from typing import Any, ClassVar | |
| import torch | |
| from torch import nn | |
| from transformers import AutoConfig, AutoModel, PreTrainedModel | |
| from .configuration_sysone import SysOneConfig | |
| QTYPES = ("noul", "choice", "score") | |
| NOUL_OPTIONS = ("true", "false") | |
| STATE_HEADER = "### ์ํ (state)\n" | |
| MIN_STATE_TOKENS = 32 | |
| TYPE_HINT = { | |
| "noul": "์/์๋์ค (true/false)", | |
| "choice": "ํ๋ ๊ณ ๋ฅด๊ธฐ", | |
| "score": "์์ ์ฒ๋ (๋ฎ์โ๋์)", | |
| } | |
| LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | |
| NOUL_SHOWN = {"true": "true (์ / yes)", "false": "false (์๋์ค / no)"} | |
| LETTER_HINT = ( | |
| "Answer with one letter." # ์ง๋ฌธ๋ง๋ค ๋ฐ๋ก ๋ฌป๋ ํ๋กฌํํธ(sysone.baseline)์ ๋ง์ง๋ง ์ค๊ณผ ๊ฐ๋ค | |
| ) | |
| ANSWER_CUE = LETTER_HINT + "\n๋ต:" | |
| ALL_HINT = "Answer each question with one letter." | |
| CHAT_SYSTEM = ( | |
| "You are a careful decision model. Read the state and answer each multiple-choice question " | |
| "with the letter of exactly one option." | |
| ) | |
| CHAT_OPEN = f"<|im_start|>system\n{CHAT_SYSTEM}<|im_end|>\n<|im_start|>user\n" | |
| CHAT_CLOSE = "<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n" | |
| CHAT_CLOSE_NOTHINK = "<|im_end|>\n<|im_start|>assistant\n" | |
| TRUNC_MARK = "\n(โฆ์ค๋ตโฆ)\n" | |
| REOPEN = "<|im_end|>\n<|im_start|>user\n" # v4chat: ๋น ์ด์์คํดํธ ์ฐจ๋ก๋ฅผ ๋ซ๊ณ ๋ค์ ์ง๋ฌธ์ ์ฌ์ฉ์ ์ฐจ๋ก๋ฅผ ์ฐ๋ค | |
| TAIL = -2 | |
| AFTER = -3 | |
| class DecisionHead(nn.Module): | |
| """์ ํ์ง ์์น hidden์ ์ฟผ๋ฆฌ๋ก: ์ ํ์ง๋ผ๋ฆฌ self-attention + ์ ์ฒด ์ํ์ค cross-attention + ์ ํ prior.""" | |
| def __init__( | |
| self, | |
| d_model: int, | |
| d_head: int = 512, | |
| n_layers: int = 2, | |
| n_heads: int = 8, | |
| dropout: float = 0.0, | |
| ): | |
| super().__init__() | |
| self.norm_in = nn.LayerNorm(d_model) | |
| self.mem_proj = nn.Linear(d_model, d_head) | |
| self.q_proj = nn.Linear(d_model, d_head) | |
| self.type_emb = nn.Embedding(len(QTYPES), d_head) | |
| layer = nn.TransformerDecoderLayer( | |
| d_head, n_heads, 4 * d_head, dropout, batch_first=True, norm_first=True | |
| ) | |
| self.layers = nn.TransformerDecoder(layer, n_layers) | |
| self.norm_out = nn.LayerNorm(d_head) | |
| self.out = nn.Linear(d_head, 1) | |
| self.prior = nn.Linear(d_model, 1) | |
| def forward(self, hidden, attention_mask, opt_pos, opt_mask, opt_type): | |
| h = self.norm_in(hidden.float()) | |
| opt_h = h.gather(1, opt_pos[..., None].expand(-1, -1, h.size(-1))) | |
| q = self.q_proj(opt_h) + self.type_emb(opt_type) | |
| x = self.layers( | |
| q, | |
| self.mem_proj(h), | |
| tgt_key_padding_mask=~opt_mask, | |
| memory_key_padding_mask=~attention_mask, | |
| ) | |
| return (self.out(self.norm_out(x)) + self.prior(opt_h)).squeeze(-1) | |
| def parse_questions(questions: dict[str, Any]) -> list[dict[str, Any]]: | |
| out = [] | |
| for name, q in questions.items(): | |
| qtype = q.get("type") | |
| if qtype not in QTYPES: | |
| raise ValueError(f"์ง๋ฌธ {name}: type์ {QTYPES} ์ค ํ๋ (๋ฐ์ ๊ฐ: {qtype})") | |
| if qtype == "noul": | |
| options = list(NOUL_OPTIONS) | |
| else: | |
| options = [str(o) for o in q.get("options") or []] | |
| if len(options) < 2 or len(set(options)) != len(options): | |
| raise ValueError(f"์ง๋ฌธ {name}: ์๋ก ๋ค๋ฅธ ์ ํ์ง๊ฐ 2๊ฐ ์ด์ ํ์ํ๋ค") | |
| out.append({"name": name, "type": qtype, "instructions": q.get("instructions", ""), | |
| "criteria": q.get("criteria", ""), "options": options}) # fmt: skip | |
| if not out: | |
| raise ValueError("์ง๋ฌธ์ด ์๋ค") | |
| return out | |
| def _ids(tok, text: str) -> list[int]: | |
| return tok(text, add_special_tokens=False)["input_ids"] | |
| def _cut_state(tok, s_all: list[int], budget: int, fmt: str) -> list[int]: | |
| """v1์ ์ํ ๋ค๋ฅผ ์๋ฅด๊ณ , v2๋ ์ 1/4๊ณผ ๋ค 3/4๋ฅผ ๋จ๊ฒจ ๊ฐ์ด๋ฐ์ ์ค์ ํ์๋ฅผ ๋ฃ๋๋ค (์ต์ ๋ฐํยท๊ธ์ ๋์ ์งํจ๋ค).""" | |
| if len(s_all) <= budget: | |
| return s_all | |
| if fmt == "v1": | |
| return s_all[:budget] | |
| mark = _ids(tok, TRUNC_MARK) | |
| keep = max(0, budget - len(mark)) | |
| head = keep // 4 | |
| return s_all[:head] + mark + s_all[len(s_all) - (keep - head) :] | |
| def encode( | |
| tok, state: Any, questions: dict[str, Any], max_len: int, fmt: str = "v1" | |
| ) -> dict[str, Any]: | |
| """[์ํ ๋จธ๋ฆฌ๋ง + ์ํ] [์ง๋ฌธ 1 ๋จธ๋ฆฌ๋งยท์ง์๋ฌธยท๊ธฐ์คยท์ ํ์ง ์ค (+ v2 ๋ต ์๋ฆฌ)] [์ง๋ฌธ 2 ...] โ ํ ํฐ id์ ์์น. | |
| v2: ์ ํ์ง ์ค `A. ์ ํ์ง`, ์ ํ์ง ๋ค `๋ต:` (์ ํ์ง 26๊ฐ ์ดํ์ผ ๋). v2chat: ์ฑํ ํ์์ผ๋ก ๊ฐ์ธ๊ณ ๋ง์ง๋ง ์ง๋ฌธ์ | |
| ์ด์์คํดํธ ์ฐจ๋ก ์์์์ ๋ตํ๋ค. ํ์ต ์ฝ๋(sysone.encode)์ ๊ฐ์ ๊ท์น์ด๋ค. | |
| """ | |
| qs = parse_questions(questions) | |
| v2, after = fmt != "v1", fmt == "v3chat" | |
| chat = fmt in ("v2chat", "v3chat", "v4chat", "v4chat-nothink") | |
| turns, in_answer = fmt in ("v4chat", "v4chat-nothink"), False | |
| close = CHAT_CLOSE_NOTHINK if fmt == "v4chat-nothink" else CHAT_CLOSE | |
| prefix = [t for t in (tok.cls_token_id, tok.bos_token_id) if t is not None][:1] | |
| q_ids: list[int] = [] | |
| opt_rel, opt_q, opt_type, slot_rel, n_letters = [], [], [], [], [] | |
| for qi, q in enumerate(qs): | |
| head = f"\n### ์ง๋ฌธ {qi + 1}: {q['name']} [{TYPE_HINT[q['type']]}]\n{q['instructions']}\n" | |
| if q["criteria"]: | |
| head += f"๊ธฐ์ค: {q['criteria']}\n" | |
| if in_answer: | |
| q_ids += _ids(tok, REOPEN) | |
| in_answer = False | |
| q_ids += _ids(tok, head) | |
| lm = v2 and len(q["options"]) <= len(LETTERS) | |
| for j, opt in enumerate(q["options"]): | |
| shown = NOUL_SHOWN[opt] if v2 and q["type"] == "noul" else opt | |
| q_ids += _ids(tok, f"{LETTERS[j]}. {shown}\n" if lm else f"- {shown}\n") | |
| opt_rel.append(len(q_ids) - 1) | |
| opt_q.append(qi) | |
| opt_type.append(QTYPES.index(q["type"])) | |
| if lm and turns: | |
| q_ids += _ids(tok, LETTER_HINT + close) | |
| slot_rel.append(len(q_ids) - 1) | |
| in_answer = True | |
| elif lm and after: | |
| slot_rel.append(AFTER) | |
| elif lm and chat and qi == len(qs) - 1: | |
| q_ids += _ids(tok, LETTER_HINT) | |
| slot_rel.append(TAIL) | |
| elif lm: | |
| q_ids += _ids(tok, ANSWER_CUE) | |
| slot_rel.append(len(q_ids) - 1) | |
| else: | |
| slot_rel.append(-1) | |
| n_letters.append(len(q["options"]) if lm else 0) | |
| a_ids: list[int] = [] | |
| lm_qs = [qi for qi, p in enumerate(slot_rel) if p == AFTER] | |
| if lm_qs: | |
| q_ids += _ids(tok, LETTER_HINT if len(lm_qs) == 1 else ALL_HINT) | |
| if len(lm_qs) == 1: | |
| slot_rel[lm_qs[0]] = TAIL | |
| else: | |
| for i, qi in enumerate(lm_qs): | |
| a_ids += _ids(tok, ("\n" if i else "") + f"์ง๋ฌธ {qi + 1}:") | |
| slot_rel[qi] = AFTER - len(a_ids) | |
| open_ids = _ids(tok, CHAT_OPEN) if chat else [] | |
| close_ids = _ids(tok, CHAT_CLOSE) if chat and not turns else [] | |
| header = _ids(tok, STATE_HEADER) | |
| budget = (max_len - len(prefix) - len(open_ids) - len(header) - len(q_ids) - len(close_ids) | |
| - len(a_ids)) # fmt: skip | |
| if budget < MIN_STATE_TOKENS: | |
| raise ValueError( | |
| f"์ง๋ฌธยท์ ํ์ง๋ง {len(q_ids)} ํ ํฐ์ด๋ผ max_len={max_len}์ ์ํ๊ฐ ๋ค์ด๊ฐ ์๋ฆฌ๊ฐ ์๋ค" | |
| ) | |
| text = state if isinstance(state, str) else json.dumps(state, ensure_ascii=False, indent=1) | |
| s_all = _ids(tok, text) | |
| s_ids = _cut_state(tok, s_all, budget, fmt) | |
| base = len(prefix) + len(open_ids) + len(header) + len(s_ids) | |
| input_ids = prefix + open_ids + header + s_ids + q_ids + close_ids + a_ids | |
| turn_end = base + len(q_ids) + len(close_ids) | |
| def slot(p: int) -> int: | |
| if p >= 0: | |
| return base + p | |
| if p == TAIL: | |
| return turn_end - 1 | |
| return turn_end + (AFTER - p) - 1 if p < AFTER else -1 | |
| slot_pos = [slot(p) for p in slot_rel] | |
| return {"input_ids": input_ids, "opt_pos": [base + p for p in opt_rel], "opt_q": opt_q, | |
| "opt_type": opt_type, "slot_pos": slot_pos, "n_letters": n_letters, "questions": qs, | |
| "truncated": len(s_all) > budget} # fmt: skip | |
| def letter_logits(hidden, slot_pos, emb, letter_ids): | |
| """๋ต ์๋ฆฌ hidden (B, Q, d) ร ์ถ๋ ฅ์ธต(= ์ ๋ ฅ ์๋ฒ ๋ฉ)์ ๊ธ์ ํ โ ๊ธ์ logit (B, Q, 26). ๋ณํ๋ผ๋ฆฌ logsumexp.""" | |
| d = hidden.size(-1) | |
| h = hidden.gather(1, slot_pos.clamp(min=0)[..., None].expand(-1, -1, d)).float() | |
| w = emb[letter_ids.reshape(-1)].float().view(*letter_ids.shape, d) | |
| return torch.einsum("bqd,kld->bqkl", h, w).logsumexp(2) | |
| def temperature_bucket(qtype: str, n_opts: int, head_only: bool = False) -> str: | |
| """์จ๋ ๊ตฌ๊ฐ. ํ์ v2์์ ๊ธ์ ๊ฒฝ๋ก๊ฐ ์๋ ์ง๋ฌธ(์ ํ์ง 27๊ฐ ์ด์)์ ๋ฐ๋ก ์ด๋ค.""" | |
| if head_only: | |
| return f"{qtype}:27+" | |
| return f"{qtype}:{n_opts if n_opts <= 2 else '3-5' if n_opts <= 5 else '6+'}" | |
| def graph_buckets(max_len: int) -> list[int]: | |
| """CUDA ๊ทธ๋ํ ๊ธธ์ด ๊ตฌ๊ฐ: 512๊น์ง 32์ฉ, 1024๊น์ง 64์ฉ, ๊ทธ ๋ค๋ 128์ฉ max_len์ ๋ฎ์ ๋๊น์ง. | |
| ์ ๋ ฅ์ ๊ตฌ๊ฐ ๊ธธ์ด๊น์ง ์ฑ์ ๊ณ์ฐํ๋ฏ๋ก ๊ตฌ๊ฐ์ด ์ด์ดํ ์๋ก ๋ ๋ญ๋นํ๋ค (ํ์ v4chat์์ 221ํ ํฐ์ด 256์ด ์๋๋ผ 224๋ก). | |
| """ | |
| out, n = [], 0 | |
| while n < max_len: | |
| n += 32 if n < 512 else 64 if n < 1024 else 128 | |
| out.append(n) | |
| return out | |
| class _GraphRunner: | |
| """๋ฐฐ์น 1 ๋ฐฑ๋ณธ forward๋ฅผ ๊ธธ์ด ๊ตฌ๊ฐ๋ง๋ค CUDA ๊ทธ๋ํ๋ก ํ ๋ฒ ๋ นํํด ๋๊ณ ๋ค์ ์ฌ์ํ๋ค (`enable_cuda_graphs`). | |
| - eager ์คํ์ ์ธต๋ง๋ค ์์ ์ปค๋์ ํ๋์ฉ ๋ถ๋ฅธ๋ค (4B๋ forward ํ ๋ฒ์ 2์ฒ์ฌ ๊ฐ). ์งง์ ์ ๋ ฅ์์๋ GPU ๊ณ์ฐ๋ณด๋ค | |
| ์ด ํธ์ถ ๋น์ฉ์ด ์ปค์ ๊ธธ์ด์ ์๊ด์์ด ์ง์ฐ์ด ๊ฑฐ์ ๊ฐ์๋ค. ์ฌ์์ ํธ์ถ ๋น์ฉ์ ์์ค๋ค | |
| - ์ ๋ ฅ์ ๊ตฌ๊ฐ ๊ธธ์ด๊น์ง ์ค๋ฅธ์ชฝ์ ์ฑ์ด๋ค. ์ธ๊ณผ ๋ชจ๋ธ์ด๋ผ ์ฑ์ด ํ ํฐ์ ์์ชฝ hidden์ ๋ฐ๊พธ์ง ์๋๋ค (์์น ์ค์ฐจ๋ง ๋ค๋ฅด๋ค) | |
| - ๊ทธ๋ํ๋ค์ ๋ฉ๋ชจ๋ฆฌ ํ ํ๋๋ฅผ ๊ฐ์ด ์ด๋ค. ์ถ๋ ฅ ๋ฒํผ๋ ๊ณต์ ๋ผ ์ฌ์๋ถํฐ ํค๋ ๊ณ์ฐ๊น์ง ์ ๊ธ ์์์ ํ๋ค | |
| """ | |
| def __init__(self, backbone: nn.Module, max_len: int): | |
| self.backbone = backbone | |
| self.device = next(backbone.parameters()).device | |
| self.buckets = graph_buckets(max_len) | |
| self.pool = torch.cuda.graph_pool_handle() | |
| self.graphs: dict[int, tuple[Any, torch.Tensor, torch.Tensor]] = {} | |
| self.lock = threading.Lock() | |
| def capture(self, length: int) -> None: | |
| static = torch.zeros(1, length, dtype=torch.long, device=self.device) | |
| stream = torch.cuda.Stream(self.device) | |
| stream.wait_stream(torch.cuda.current_stream(self.device)) | |
| with torch.no_grad(), torch.cuda.stream(stream): | |
| for _ in range(2): # Triton ์ปค๋ ์๋ ์กฐ์ ๊ณผ cuBLAS ์ค๋น๋ฅผ ๋ นํ ์ ์ ๋๋ธ๋ค | |
| self.backbone(input_ids=static, use_cache=False) | |
| torch.cuda.current_stream(self.device).wait_stream(stream) | |
| graph = torch.cuda.CUDAGraph() | |
| with torch.no_grad(), torch.cuda.graph(graph, pool=self.pool): | |
| out = self.backbone(input_ids=static, use_cache=False).last_hidden_state | |
| self.graphs[length] = (graph, static, out) | |
| def __call__(self, input_ids: torch.Tensor) -> torch.Tensor | None: | |
| """(1, n) โ (1, n, d) hidden. ๊ฐ์ฅ ๊ธด ๊ตฌ๊ฐ๋ณด๋ค ๊ธธ๋ฉด None์ด๋ค (ํธ์ถํ ์ชฝ์ด eager๋ก ๊ณ์ฐํ๋ค).""" | |
| n = input_ids.size(1) | |
| length = next((b for b in self.buckets if b >= n), None) | |
| if input_ids.size(0) != 1 or length is None: | |
| return None | |
| if length not in self.graphs: | |
| self.capture(length) | |
| graph, static, out = self.graphs[length] | |
| static[:, :n].copy_(input_ids) | |
| static[:, n:].zero_() | |
| graph.replay() | |
| return out[:, :n] | |
| class SysOneForDecision(PreTrainedModel): | |
| config_class = SysOneConfig | |
| base_model_prefix = "sysone" | |
| # ํค๋๋ float32๋ก ํ์ตํ๋ค. _keep_in_fp32_modules๋ fp16์๋ง ์ ์ฉ๋์ด bf16๊น์ง ๋ง์ผ๋ ค๋ฉด _strict๊ฐ ํ์ํ๋ค | |
| _keep_in_fp32_modules: ClassVar[list[str]] = ["head"] | |
| _keep_in_fp32_modules_strict: ClassVar[list[str]] = ["head"] | |
| _supports_sdpa = True | |
| def __init__(self, config: SysOneConfig): | |
| super().__init__(config) | |
| text = dict(config.text_config) | |
| self.backbone = AutoModel.from_config(AutoConfig.for_model(text.pop("model_type"), **text)) | |
| self.head = DecisionHead(self.backbone.config.hidden_size, **config.head) | |
| self._graphs: _GraphRunner | None = None | |
| self._letter_ids: torch.Tensor | None = None | |
| self.post_init() | |
| def forward(self, input_ids, attention_mask, opt_pos, opt_mask, opt_type): | |
| hidden = self.backbone( | |
| input_ids=input_ids, attention_mask=attention_mask.long(), use_cache=False | |
| ).last_hidden_state | |
| return self.head(hidden, attention_mask, opt_pos, opt_mask, opt_type) | |
| def enable_cuda_graphs(self, capture_all: bool = True) -> SysOneForDecision: | |
| """decide()์ ๋ฐฑ๋ณธ์ ๊ธธ์ด ๊ตฌ๊ฐ๋ณ CUDA ๊ทธ๋ํ๋ก ๋๋ฆฐ๋ค (๋ฐฐ์น 1). | |
| GPU๋ก ์ฎ๊ธด ๋ค ๋ถ๋ฅธ๋ค. capture_all์ด๋ฉด ๋ชจ๋ ๊ตฌ๊ฐ์ ์ง๊ธ ๋ นํํ๊ณ (A100์์ ์ฝ 5์ด, ์ถ๊ฐ ๋ฉ๋ชจ๋ฆฌ 1GiB ์์ชฝ), | |
| ์๋๋ฉด ์ฒ์ ์ฐ๋ ๊ตฌ๊ฐ์ ๊ทธ๋ ๋ นํํ๋ค. ์ฅ์นยทdtypeยท๊ฐ์ค์น๋ฅผ ๋ฐ๊พธ๋ฉด ๋ค์ ๋ถ๋ฌ์ผ ํ๋ค. | |
| """ | |
| device = next(self.backbone.parameters()).device | |
| if device.type != "cuda": | |
| raise RuntimeError( | |
| "CUDA ๊ทธ๋ํ๋ CUDA ์ฅ์น์์๋ง ์ธ ์ ์๋ค. model.to('cuda') ๋ค์ ๋ถ๋ฅผ ๊ฒ" | |
| ) | |
| runner = _GraphRunner(self.backbone, self.config.max_len) | |
| if capture_all: | |
| for length in runner.buckets: | |
| runner.capture(length) | |
| self._graphs = runner | |
| return self | |
| def disable_cuda_graphs(self) -> None: | |
| self._graphs = None | |
| def decide( | |
| self, tokenizer, state: Any, questions: dict[str, Any], max_len: int | None = None | |
| ) -> dict[str, Any]: | |
| """์ง๋ฌธ๋ง๋ค ์ ํ์ง๋ณ ํ๋ฅ (๋ณด์ ์จ๋ ์ ์ฉ). | |
| choiceยทscore: answer(๊ฐ์ฅ ๋์ ์ ํ์ง)ยทconfidence, score๋ expected(์ ํ์ง ์๋ฒ 1๋ถํฐ์ ๊ธฐ๋๊ฐ)๋ ์ค๋ค. | |
| noul: answer(bool)ยทp_true. truncated๋ ์ํ๊ฐ max_len์ ๋์ด ์๋ ธ๋์ง๋ค. | |
| ์ ๋ ฅ ํ์ v2๋ถํฐ๋ ๊ธ์ ๊ฒฝ๋ก๊ฐ ์๋ ์ง๋ฌธ์ paths(๊ฒฐ์ ํค๋ยท์ธ์ด ๋ชจ๋ธ ๊ธ์์ ๊ฒฝ๋ก๋ณ ํ๋ฅ , ๋ณด์ ์ )๋ ์ค๋ค. | |
| """ | |
| if ( | |
| self.head.out.weight.dtype != torch.float32 | |
| ): # ๋ค๋ฅธ dtype์ผ๋ก ์ฎ๊ฒจ์ก์ด๋ ํค๋๋ float32๋ก ๊ณ์ฐํ๋ค | |
| self.head.float() | |
| fmt = getattr(self.config, "input_format", "v1") | |
| enc = encode(tokenizer, state, questions, max_len or self.config.max_len, fmt) | |
| dev = self.head.out.weight.device | |
| ids = torch.tensor([enc["input_ids"]], device=dev) | |
| n = len(enc["opt_pos"]) | |
| mask = torch.ones_like(ids, dtype=torch.bool) | |
| pos = torch.tensor([enc["opt_pos"]], device=dev) | |
| opt_mask = torch.ones(1, n, dtype=torch.bool, device=dev) | |
| opt_type = torch.tensor([enc["opt_type"]], device=dev) | |
| use_lm = fmt != "v1" and any(enc["n_letters"]) | |
| runner = self._graphs if self._graphs is not None and dev == self._graphs.device else None | |
| letters = None | |
| with runner.lock if runner is not None else contextlib.nullcontext(): | |
| hidden = runner(ids) if runner is not None else None | |
| if hidden is None: | |
| hidden = self.backbone( | |
| input_ids=ids, attention_mask=mask.long(), use_cache=False | |
| ).last_hidden_state | |
| logits = self.head(hidden, mask, pos, opt_mask, opt_type)[0].float().cpu() | |
| if use_lm: | |
| if self._letter_ids is None or self._letter_ids.device != dev: | |
| self._letter_ids = torch.tensor(self.config.letter_ids, device=dev) | |
| slots = torch.tensor([enc["slot_pos"]], device=dev) | |
| emb = self.backbone.get_input_embeddings().weight | |
| letters = letter_logits(hidden, slots, emb, self._letter_ids)[0].float().cpu() | |
| temps = self.config.temperatures or {} | |
| w = float(getattr(self.config, "lm_weight", 0.5)) | |
| answers = {} | |
| for qi, q in enumerate(enc["questions"]): | |
| cols = [j for j, x in enumerate(enc["opt_q"]) if x == qi] | |
| n_let = enc["n_letters"][qi] if fmt != "v1" else None | |
| final, paths = logits[cols], None | |
| if n_let and letters is not None: | |
| lm = letters[qi, :n_let] | |
| paths = {"head": final.softmax(-1).tolist(), "lm": lm.softmax(-1).tolist()} | |
| final = (1 - w) * final.log_softmax(-1) + w * lm.log_softmax(-1) | |
| t = temps.get(temperature_bucket(q["type"], len(cols), n_let == 0), temps.get("*", 1.0)) | |
| probs = (final / t).softmax(-1).tolist() | |
| out: dict[str, Any] = {"probs": dict(zip(q["options"], (round(p, 4) for p in probs)))} | |
| if paths: | |
| out["paths"] = {k: dict(zip(q["options"], (round(p, 4) for p in v))) | |
| for k, v in paths.items()} # fmt: skip | |
| if q["type"] == "noul": | |
| out |= {"answer": probs[0] >= 0.5, "p_true": round(probs[0], 4)} | |
| else: | |
| best = max(range(len(probs)), key=probs.__getitem__) | |
| out |= {"answer": q["options"][best], "confidence": round(probs[best], 4)} | |
| if q["type"] == "score": | |
| out["expected"] = round(sum((k + 1) * p for k, p in enumerate(probs)), 3) | |
| answers[q["name"]] = out | |
| return {"answers": answers, "truncated": enc["truncated"]} | |
| def decide_record( | |
| self, tokenizer, record: dict[str, Any], max_len: int | None = None | |
| ) -> dict[str, Any]: | |
| """{"id", "state", "questions"} ๋ ์ฝ๋ ํ๋ (ํ์ต ๋ฐ์ดํฐ์ ๊ฐ์ ํ์).""" | |
| return { | |
| "id": record.get("id"), | |
| **self.decide(tokenizer, record["state"], record["questions"], max_len), | |
| } | |