chore: import upstream snapshot with attribution
Lockfile supply-chain audit / lockfile supply-chain audit (push) Has been cancelled
Windows Studio GGUF CI / GPU prebuilt resolves without Visual Studio (push) Has been cancelled
Windows Studio GGUF CI / setup.ps1 unit tests (VS 2026 / CMake guard) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2022) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-2025-vs2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-latest) (push) Has been cancelled
Windows Studio Update CI / Studio Updating Tests (push) Has been cancelled
Wheel CI / Wheel build + content sanity + import smoke (push) Has been cancelled
Lint CI / Source lint (Python + shell + YAML + JSON + safety nets) (push) Has been cancelled
MLX CI on Mac M1 / dispatch (push) Has been cancelled
Security audit / advisory audit (pip + npm + cargo) (push) Has been cancelled
Security audit / pip scan-packages :: extras (push) Has been cancelled
Security audit / pip scan-packages :: studio (push) Has been cancelled
Security audit / pip scan-packages :: hf-stack (push) Has been cancelled
Security audit / npm scan-packages (Studio frontend tarballs) (push) Has been cancelled
Security audit / workflow-trigger lint (pull_request_target / cache-poisoning) (push) Has been cancelled
Security audit / pytest tests/security (push) Has been cancelled
Security audit / npm provenance + new install-script diff (push) Has been cancelled
Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Backend CI / (Python 3.10) (push) Has been cancelled
Backend CI / (Python 3.11) (push) Has been cancelled
Backend CI / (Python 3.12) (push) Has been cancelled
Backend CI / (Python 3.13) (push) Has been cancelled
Backend CI / Repo tests (CPU) (push) Has been cancelled
Frontend CI / Frontend build + bundle sanity (push) Has been cancelled
Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Mac Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Mac Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-14) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15-intel) (push) Has been cancelled
Mac Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26-intel) (push) Has been cancelled
Mac Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Tauri CI / Tauri Linux debug build (no codesign) (push) Has been cancelled
Mac Studio Update CI / Studio Updating Tests (push) Has been cancelled
Studio UI CI / Chat UI Tests (push) Has been cancelled
Windows Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Windows Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Update CI / Studio Updating Tests (push) Has been cancelled
Core / Core (HF=default + TRL=default) (push) Has been cancelled
Core / Core (HF=4.57.6 + TRL<1) (push) Has been cancelled
Core / Core (HF=latest + TRL=latest) (push) Has been cancelled
Core / llama.cpp build + smoke (push) Has been cancelled
Windows Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Windows Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Windows Studio GGUF CI / JSON, images (push) Has been cancelled
Windows Studio GGUF CI / Studio install + inference without Visual Studio (push) Has been cancelled
Studio export capability / capability (macos-latest) (push) Has been cancelled
Studio export capability / capability (ubuntu-latest) (push) Has been cancelled
Studio export capability / capability (windows-latest) (push) Has been cancelled
Cross-platform parity / parity (macos-latest) (push) Has been cancelled
Cross-platform parity / parity (windows-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Studio load-orchestrator CI / test (push) Has been cancelled
Lockfile supply-chain audit / lockfile supply-chain audit (push) Has been cancelled
Windows Studio GGUF CI / GPU prebuilt resolves without Visual Studio (push) Has been cancelled
Windows Studio GGUF CI / setup.ps1 unit tests (VS 2026 / CMake guard) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2022) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-2025-vs2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-latest) (push) Has been cancelled
Windows Studio Update CI / Studio Updating Tests (push) Has been cancelled
Wheel CI / Wheel build + content sanity + import smoke (push) Has been cancelled
Lint CI / Source lint (Python + shell + YAML + JSON + safety nets) (push) Has been cancelled
MLX CI on Mac M1 / dispatch (push) Has been cancelled
Security audit / advisory audit (pip + npm + cargo) (push) Has been cancelled
Security audit / pip scan-packages :: extras (push) Has been cancelled
Security audit / pip scan-packages :: studio (push) Has been cancelled
Security audit / pip scan-packages :: hf-stack (push) Has been cancelled
Security audit / npm scan-packages (Studio frontend tarballs) (push) Has been cancelled
Security audit / workflow-trigger lint (pull_request_target / cache-poisoning) (push) Has been cancelled
Security audit / pytest tests/security (push) Has been cancelled
Security audit / npm provenance + new install-script diff (push) Has been cancelled
Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Backend CI / (Python 3.10) (push) Has been cancelled
Backend CI / (Python 3.11) (push) Has been cancelled
Backend CI / (Python 3.12) (push) Has been cancelled
Backend CI / (Python 3.13) (push) Has been cancelled
Backend CI / Repo tests (CPU) (push) Has been cancelled
Frontend CI / Frontend build + bundle sanity (push) Has been cancelled
Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Mac Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Mac Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-14) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15-intel) (push) Has been cancelled
Mac Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26-intel) (push) Has been cancelled
Mac Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Tauri CI / Tauri Linux debug build (no codesign) (push) Has been cancelled
Mac Studio Update CI / Studio Updating Tests (push) Has been cancelled
Studio UI CI / Chat UI Tests (push) Has been cancelled
Windows Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Windows Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Update CI / Studio Updating Tests (push) Has been cancelled
Core / Core (HF=default + TRL=default) (push) Has been cancelled
Core / Core (HF=4.57.6 + TRL<1) (push) Has been cancelled
Core / Core (HF=latest + TRL=latest) (push) Has been cancelled
Core / llama.cpp build + smoke (push) Has been cancelled
Windows Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Windows Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Windows Studio GGUF CI / JSON, images (push) Has been cancelled
Windows Studio GGUF CI / Studio install + inference without Visual Studio (push) Has been cancelled
Studio export capability / capability (macos-latest) (push) Has been cancelled
Studio export capability / capability (ubuntu-latest) (push) Has been cancelled
Studio export capability / capability (windows-latest) (push) Has been cancelled
Cross-platform parity / parity (macos-latest) (push) Has been cancelled
Cross-platform parity / parity (windows-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Studio load-orchestrator CI / test (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
"""ORPO should use a processor's tokenizer for text-only row tokenization."""
|
||||
|
||||
import ast
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
RL_PATH = os.path.join(REPO_ROOT, "unsloth", "models", "rl_replacements.py")
|
||||
|
||||
|
||||
def _load_orpo_rewriter(name = "orpo_trainer_text_tokenizer"):
|
||||
src = open(RL_PATH).read()
|
||||
tree = ast.parse(src)
|
||||
ns = {"re": re}
|
||||
# Materialise sibling module-level _-prefixed assignments the rewriter may reference.
|
||||
for node in tree.body:
|
||||
if isinstance(node, ast.Assign):
|
||||
for target in node.targets:
|
||||
if isinstance(target, ast.Name) and target.id.startswith("_"):
|
||||
exec(ast.get_source_segment(src, node), ns)
|
||||
for node in tree.body:
|
||||
if isinstance(node, ast.FunctionDef) and node.name == name:
|
||||
exec(ast.get_source_segment(src, node), ns)
|
||||
return ns[name]
|
||||
raise AssertionError(f"{name} not found")
|
||||
|
||||
|
||||
class _Tokenizer:
|
||||
bos_token_id = 1
|
||||
eos_token_id = 2
|
||||
|
||||
def __init__(self):
|
||||
self.calls = []
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
text,
|
||||
add_special_tokens = False,
|
||||
**kwargs,
|
||||
):
|
||||
self.calls.append((text, add_special_tokens, kwargs))
|
||||
ids = [ord(c) % 31 + 3 for c in text]
|
||||
return {"input_ids": ids, "attention_mask": [1] * len(ids)}
|
||||
|
||||
|
||||
class _Processor:
|
||||
def __init__(self):
|
||||
self.tokenizer = _Tokenizer()
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
raise AssertionError("text-only ORPO tokenization should not call processor")
|
||||
|
||||
|
||||
class _Trainer:
|
||||
def __init__(self):
|
||||
self.processing_class = _Processor()
|
||||
self.is_encoder_decoder = False
|
||||
self.max_length = 2048
|
||||
self.max_prompt_length = 1024
|
||||
self.max_completion_length = 1024
|
||||
self.truncation_mode = "keep_end"
|
||||
self.label_pad_token_id = -100
|
||||
self.padding_value = 0
|
||||
|
||||
|
||||
def _exec_rewritten(
|
||||
function_name,
|
||||
source,
|
||||
extra_ns = None,
|
||||
):
|
||||
rewriter = _load_orpo_rewriter()
|
||||
rewritten = rewriter(function_name, source)
|
||||
ns = {} if extra_ns is None else dict(extra_ns)
|
||||
exec(rewritten, ns)
|
||||
return ns[function_name]
|
||||
|
||||
|
||||
def test_orpo_tokenize_row_returns_original_when_tokenizer_anchor_missing():
|
||||
rewriter = _load_orpo_rewriter()
|
||||
source = """
|
||||
def tokenize_row(self, feature, model=None):
|
||||
output = {}
|
||||
output["prompt_input_ids"] = self.processing_class(feature["prompt"], add_special_tokens=False)["input_ids"]
|
||||
return output
|
||||
"""
|
||||
|
||||
rewritten = rewriter("tokenize_row", source)
|
||||
|
||||
assert rewritten == source
|
||||
assert "tokenizer(" not in rewritten
|
||||
|
||||
|
||||
def test_orpo_build_tokenized_answer_uses_processor_tokenizer():
|
||||
source = """
|
||||
def build_tokenized_answer(self, prompt, answer):
|
||||
full_tokenized = self.processing_class(prompt + answer, add_special_tokens=False)
|
||||
prompt_input_ids = self.processing_class(prompt, add_special_tokens=False)["input_ids"]
|
||||
return full_tokenized["input_ids"][len(prompt_input_ids):]
|
||||
"""
|
||||
fn = _exec_rewritten("build_tokenized_answer", source)
|
||||
trainer = _Trainer()
|
||||
|
||||
assert fn(trainer, "a", "b")
|
||||
assert [call[0] for call in trainer.processing_class.tokenizer.calls] == ["ab", "a"]
|
||||
|
||||
|
||||
def test_orpo_tokenize_row_uses_processor_tokenizer():
|
||||
source = """
|
||||
def tokenize_row(self, feature, model=None):
|
||||
batch = {}
|
||||
prompt = feature["prompt"]
|
||||
chosen = feature["chosen"]
|
||||
rejected = feature["rejected"]
|
||||
if not self.is_encoder_decoder:
|
||||
prompt_tokens = self.processing_class(prompt, add_special_tokens=False)
|
||||
prompt_tokens = {f"prompt_{k}": v for k, v in prompt_tokens.items()}
|
||||
chosen_tokens = self.build_tokenized_answer(prompt, chosen)
|
||||
rejected_tokens = self.build_tokenized_answer(prompt, rejected)
|
||||
prompt_len_input_ids = len(prompt_tokens["prompt_input_ids"])
|
||||
chosen_prompt_len_input_ids = len(chosen_tokens["prompt_input_ids"])
|
||||
rejected_prompt_len_input_ids = len(rejected_tokens["prompt_input_ids"])
|
||||
prompt_tokens, chosen_tokens, rejected_tokens = add_bos_token_if_needed(
|
||||
self.processing_class.bos_token_id,
|
||||
prompt_len_input_ids,
|
||||
prompt_tokens,
|
||||
chosen_prompt_len_input_ids,
|
||||
chosen_tokens,
|
||||
rejected_prompt_len_input_ids,
|
||||
rejected_tokens,
|
||||
)
|
||||
chosen_tokens, rejected_tokens = add_eos_token_if_needed(
|
||||
self.processing_class.eos_token_id, chosen_tokens, rejected_tokens
|
||||
)
|
||||
batch["prompt_input_ids"] = prompt_tokens["prompt_input_ids"]
|
||||
batch["chosen_input_ids"] = chosen_tokens["input_ids"]
|
||||
batch["rejected_input_ids"] = rejected_tokens["input_ids"]
|
||||
return batch
|
||||
"""
|
||||
|
||||
def add_bos_token_if_needed(*args):
|
||||
return args[2], args[4], args[6]
|
||||
|
||||
def add_eos_token_if_needed(eos_token_id, chosen_tokens, rejected_tokens):
|
||||
chosen_tokens["input_ids"] = chosen_tokens["input_ids"] + [eos_token_id]
|
||||
rejected_tokens["input_ids"] = rejected_tokens["input_ids"] + [eos_token_id]
|
||||
return chosen_tokens, rejected_tokens
|
||||
|
||||
trainer = _Trainer()
|
||||
trainer.build_tokenized_answer = lambda prompt, answer: {
|
||||
"prompt_input_ids": trainer.processing_class.tokenizer(prompt)["input_ids"],
|
||||
"input_ids": trainer.processing_class.tokenizer(answer)["input_ids"],
|
||||
}
|
||||
fn = _exec_rewritten(
|
||||
"tokenize_row",
|
||||
source,
|
||||
{
|
||||
"add_bos_token_if_needed": add_bos_token_if_needed,
|
||||
"add_eos_token_if_needed": add_eos_token_if_needed,
|
||||
},
|
||||
)
|
||||
|
||||
output = fn(trainer, {"prompt": "p", "chosen": "c", "rejected": "r"})
|
||||
|
||||
assert output["chosen_input_ids"][-1] == _Tokenizer.eos_token_id
|
||||
assert [call[0] for call in trainer.processing_class.tokenizer.calls] == [
|
||||
"p",
|
||||
"p",
|
||||
"c",
|
||||
"p",
|
||||
"r",
|
||||
]
|
||||
|
||||
|
||||
def test_orpo_init_pad_token_id_falls_back_to_tokenizer():
|
||||
rewriter = _load_orpo_rewriter("orpo_trainer_processor_pad_token")
|
||||
source = """
|
||||
def __init__(self, processing_class):
|
||||
data_collator = DPODataCollatorWithPadding(
|
||||
pad_token_id=processing_class.pad_token_id,
|
||||
)
|
||||
self.padding_value = processing_class.pad_token_id
|
||||
"""
|
||||
|
||||
rewritten = rewriter("__init__", source)
|
||||
|
||||
assert "processing_class.pad_token_id" not in rewritten
|
||||
assert "getattr(processing_class, 'pad_token_id'" in rewritten
|
||||
|
||||
class _Processor:
|
||||
# No pad_token_id at the processor level; only on the inner tokenizer.
|
||||
class tokenizer:
|
||||
pad_token_id = 17
|
||||
|
||||
captured = {}
|
||||
|
||||
def DPODataCollatorWithPadding(**kwargs):
|
||||
captured["pad_token_id"] = kwargs["pad_token_id"]
|
||||
return object()
|
||||
|
||||
ns = {"DPODataCollatorWithPadding": DPODataCollatorWithPadding}
|
||||
exec(rewritten, ns)
|
||||
|
||||
class _Trainer:
|
||||
pass
|
||||
|
||||
trainer = _Trainer()
|
||||
ns["__init__"](trainer, _Processor())
|
||||
|
||||
assert captured["pad_token_id"] == 17
|
||||
assert trainer.padding_value == 17
|
||||
|
||||
|
||||
def test_orpo_init_pad_token_id_uses_processor_when_present():
|
||||
rewriter = _load_orpo_rewriter("orpo_trainer_processor_pad_token")
|
||||
source = """
|
||||
def __init__(self, processing_class):
|
||||
self.padding_value = processing_class.pad_token_id
|
||||
"""
|
||||
|
||||
rewritten = rewriter("__init__", source)
|
||||
|
||||
class _Tokenizer:
|
||||
# Inner tokenizer must NOT be consulted when the processor exposes
|
||||
# pad_token_id itself.
|
||||
pad_token_id = 999
|
||||
|
||||
class _Processor:
|
||||
pad_token_id = 42
|
||||
tokenizer = _Tokenizer()
|
||||
|
||||
ns = {}
|
||||
exec(rewritten, ns)
|
||||
|
||||
class _Trainer:
|
||||
pass
|
||||
|
||||
trainer = _Trainer()
|
||||
ns["__init__"](trainer, _Processor())
|
||||
assert trainer.padding_value == 42
|
||||
|
||||
|
||||
def test_orpo_init_pad_token_id_noop_on_non_init():
|
||||
rewriter = _load_orpo_rewriter("orpo_trainer_processor_pad_token")
|
||||
source = "def tokenize_row(self):\n return processing_class.pad_token_id\n"
|
||||
assert rewriter("tokenize_row", source) == source
|
||||
Reference in New Issue
Block a user