Download conversion/packing.py from FluidInference/decision-1.0-kai-coreml: direct link, hf CLI and curl.
- Browser
- Download file 6.75 kB
-
https://huggingface.co/FluidInference/decision-1.0-kai-coreml/resolve/main/conversion/packing.py
- Command line
-
hf download hf://FluidInference/decision-1.0-kai-coreml/conversion/packing.py
-
curl -L -o packing.py https://huggingface.co/FluidInference/decision-1.0-kai-coreml/resolve/main/conversion/packing.py
6.75 kB
| """One joint row per question. Metadata IDs and gold labels never enter tokens.""" | |
| import math | |
| import torch | |
| KINDS = ("choice", "noul", "score") | |
| DEFAULT_NO = "No. The statement or question is not satisfied." | |
| DEFAULT_YES = "Yes. The statement or question is satisfied." | |
| def nonempty(value, name): | |
| if not isinstance(value, str) or not value.strip(): | |
| raise ValueError(f"{name} must be a nonempty string") | |
| return value | |
| def view(record, labeled=False): | |
| q = record["question"] | |
| kind = str(q["type"]).lower() | |
| if kind not in KINDS: | |
| raise ValueError("Unsupported question type") | |
| nonempty(record["state_text"], "state_text") | |
| nonempty(q["text"], "question.text") | |
| if kind == "noul": | |
| options = [{"id": "no", "text": q.get("false_criterion", DEFAULT_NO)}, | |
| {"id": "yes", "text": q.get("true_criterion", DEFAULT_YES)}] | |
| else: | |
| options = q["options" if kind == "choice" else "levels"] | |
| if not isinstance(options, list) or not 2 <= len(options) <= 255: | |
| raise ValueError("Require 2..255 complete candidate descriptions") | |
| for o in options: | |
| nonempty(o["id"], "candidate.id") | |
| nonempty(o["text"], "candidate.text") | |
| if len({o["id"] for o in options}) != len(options): | |
| raise ValueError("Duplicate candidate IDs") | |
| values = [float(o["value"]) for o in options] if kind == "score" else list(range(len(options))) | |
| if kind == "score" and (not all(math.isfinite(v) for v in values) | |
| or any(a >= b for a, b in zip(values, values[1:]))): | |
| raise ValueError("Score level values must be finite and strictly increasing") | |
| target = None | |
| if labeled: | |
| gold = record["target"] | |
| if kind == "noul": | |
| yes = float(gold["probability"]) | |
| target = [1 - yes, yes] | |
| elif "probabilities" in gold: | |
| target = [float(x) for x in gold["probabilities"]] | |
| elif kind == "choice" and "choice_id" in gold: | |
| if gold["choice_id"] not in {o["id"] for o in options}: | |
| raise ValueError("Unknown gold choice_id") | |
| target = [float(o["id"] == gold["choice_id"]) for o in options] | |
| else: | |
| raise ValueError("Score requires an explicit target probability distribution") | |
| if (len(target) != len(options) or not all(math.isfinite(x) and 0 <= x <= 1 for x in target) | |
| or abs(sum(target) - 1) > 1e-6): | |
| raise ValueError("Invalid target probability distribution") | |
| return kind, options, values, target | |
| class MarkerCollator: | |
| def __init__(self, tokenizer, max_length=8192, state_truncation="error"): | |
| if isinstance(max_length, bool) or not isinstance(max_length, int) or max_length < 8: | |
| raise ValueError("Invalid max_length") | |
| if state_truncation not in ("error", "right"): | |
| raise ValueError("state_truncation must be error or right") | |
| self.tokenizer, self.max_length = tokenizer, max_length | |
| self.state_truncation = state_truncation | |
| self.bos = tokenizer.cls_token_id if tokenizer.cls_token_id is not None else tokenizer.bos_token_id | |
| self.sep = tokenizer.sep_token_id if tokenizer.sep_token_id is not None else tokenizer.eos_token_id | |
| self.pad, self.marker = tokenizer.pad_token_id, tokenizer.mask_token_id | |
| if any(v is None for v in (self.bos, self.sep, self.pad, self.marker)): | |
| raise ValueError("Tokenizer must provide existing BOS/CLS, SEP/EOS, PAD and MASK IDs") | |
| def tokens(self, text): | |
| ids = self.tokenizer(text, add_special_tokens=False, truncation=False)["input_ids"] | |
| if not ids: | |
| raise ValueError("Empty token span") | |
| return ids | |
| def encode(self, record, labeled=False): | |
| kind, options, values, target = view(record, labeled) | |
| question = self.tokens(f"{kind} question: {record['question']['text']}") | |
| ids = [self.bos] + question + [self.sep] | |
| positions, spans = [], [] | |
| for j, option in enumerate(options): | |
| # Ordinal rank is meaningful only for Score; opaque external IDs never enter text. | |
| text = f"level {j}: {option['text']}" if kind == "score" else option["text"] | |
| desc = self.tokens(text) | |
| positions.append(len(ids)) | |
| ids.append(self.marker) | |
| spans.append([len(ids), len(ids) + len(desc)]) | |
| ids.extend(desc + [self.sep]) | |
| state = self.tokens(record["state_text"]) | |
| room = self.max_length - len(ids) - 1 | |
| if room < 1: | |
| raise ValueError("Complete question and candidates leave no room for state") | |
| if len(state) > room and self.state_truncation == "error": | |
| raise ValueError(f"Record {record.get('id')} exceeds {self.max_length} tokens; no implicit truncation") | |
| kept = state[:room] | |
| state_span = [len(ids), len(ids) + len(kept)] | |
| ids.extend(kept + [self.sep]) | |
| return {"ids": ids, "positions": positions, "candidate_spans": spans, | |
| "state_span": state_span, "kind": kind, "target": target, "values": values, | |
| "candidate_ids": [o["id"] for o in options], "id": record.get("id"), | |
| "question_id": record["question"].get("id"), "state_tokens_original": len(state), | |
| "state_tokens_kept": len(kept), "input_tokens": len(ids)} | |
| def __call__(self, records, labeled=False, device="cpu"): | |
| encoded = [self.encode(r, labeled) for r in records] | |
| if not encoded: | |
| raise ValueError("Empty batch") | |
| b, length, k = len(encoded), max(len(e["ids"]) for e in encoded), max(len(e["positions"]) for e in encoded) | |
| ids = torch.full((b, length), self.pad, dtype=torch.long) | |
| mask = torch.zeros((b, length), dtype=torch.bool) | |
| positions = torch.zeros((b, k), dtype=torch.long) | |
| valid = torch.zeros((b, k), dtype=torch.bool) | |
| target, values = torch.zeros((b, k)), torch.zeros((b, k)) | |
| for i, e in enumerate(encoded): | |
| n, count = len(e["ids"]), len(e["positions"]) | |
| ids[i, :n] = torch.tensor(e["ids"]) | |
| mask[i, :n] = True | |
| positions[i, :count] = torch.tensor(e["positions"]) | |
| valid[i, :count] = True | |
| values[i, :count] = torch.tensor(e["values"]) | |
| if labeled: | |
| target[i, :count] = torch.tensor(e["target"]) | |
| batch = {"input_ids": ids, "attention_mask": mask, "marker_positions": positions, | |
| "valid_candidates": valid, "targets": target, "values": values, | |
| "kind_ids": torch.tensor([KINDS.index(e["kind"]) for e in encoded])} | |
| return {name: value.to(device) for name, value in batch.items()}, encoded | |