e93507a09c
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
128 lines
4.3 KiB
Python
128 lines
4.3 KiB
Python
import ast
|
|
import json
|
|
import os
|
|
|
|
os.environ.setdefault("PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION", "python")
|
|
|
|
from transformers.utils import sentencepiece_model_pb2
|
|
|
|
from unsloth.tokenizer_utils import fix_sentencepiece_gguf
|
|
|
|
|
|
NORMAL, CONTROL, USER_DEFINED = 1, 3, 4
|
|
|
|
_SAVE_PY = os.path.abspath(
|
|
os.path.join(os.path.dirname(__file__), "..", "..", "unsloth", "save.py")
|
|
)
|
|
_TOK_PY = os.path.abspath(
|
|
os.path.join(os.path.dirname(__file__), "..", "..", "unsloth", "tokenizer_utils.py")
|
|
)
|
|
|
|
|
|
def _build(pieces):
|
|
m = sentencepiece_model_pb2.ModelProto()
|
|
for piece, score, typ in pieces:
|
|
p = m.pieces.add()
|
|
p.piece = piece
|
|
p.score = score
|
|
p.type = typ
|
|
return m.SerializeToString()
|
|
|
|
|
|
def _read(path):
|
|
m = sentencepiece_model_pb2.ModelProto()
|
|
with open(path, "rb") as f:
|
|
m.ParseFromString(f.read())
|
|
return [(p.piece, p.type) for p in m.pieces]
|
|
|
|
|
|
def test_user_defined_special_piece_is_not_retyped(tmp_path):
|
|
pieces = [
|
|
("<s>", 0.0, CONTROL),
|
|
("a", -1.0, NORMAL),
|
|
("<ud_special>", -1.0, USER_DEFINED),
|
|
]
|
|
(tmp_path / "tokenizer.model").write_bytes(_build(pieces))
|
|
(tmp_path / "tokenizer.json").write_text(
|
|
json.dumps({"added_tokens": [{"id": 2, "content": "<ud_special>", "special": True}]})
|
|
)
|
|
fix_sentencepiece_gguf(str(tmp_path))
|
|
got = dict(_read(str(tmp_path / "tokenizer.model")))
|
|
assert got["<ud_special>"] == USER_DEFINED
|
|
|
|
|
|
def test_malformed_entry_missing_id_does_not_raise(tmp_path):
|
|
pieces = [("<s>", 0.0, CONTROL), ("a", -1.0, NORMAL), ("<sot>", -1.0, NORMAL)]
|
|
(tmp_path / "tokenizer.model").write_bytes(_build(pieces))
|
|
(tmp_path / "tokenizer.json").write_text(
|
|
json.dumps(
|
|
{
|
|
"added_tokens": [
|
|
{"content": "no_id_entry", "special": True},
|
|
{"id": 2, "content": "<sot>", "special": True},
|
|
]
|
|
}
|
|
)
|
|
)
|
|
fix_sentencepiece_gguf(str(tmp_path))
|
|
got = dict(_read(str(tmp_path / "tokenizer.model")))
|
|
assert got["<sot>"] == CONTROL
|
|
|
|
|
|
def test_entry_with_non_int_id_is_skipped(tmp_path):
|
|
pieces = [("<s>", 0.0, CONTROL), ("a", -1.0, NORMAL)]
|
|
(tmp_path / "tokenizer.model").write_bytes(_build(pieces))
|
|
(tmp_path / "tokenizer.json").write_text(
|
|
json.dumps({"added_tokens": [{"id": "oops", "content": "x", "special": True}]})
|
|
)
|
|
before = (tmp_path / "tokenizer.model").read_bytes()
|
|
fix_sentencepiece_gguf(str(tmp_path))
|
|
after = (tmp_path / "tokenizer.model").read_bytes()
|
|
assert before == after
|
|
|
|
|
|
def test_save_py_except_clause_is_broad_exception():
|
|
with open(_SAVE_PY) as f:
|
|
tree = ast.parse(f.read())
|
|
for node in ast.walk(tree):
|
|
if isinstance(node, ast.FunctionDef) and node.name == "unsloth_save_pretrained_gguf":
|
|
for subnode in ast.walk(node):
|
|
if isinstance(subnode, ast.Try):
|
|
body_src = "\n".join(ast.unparse(s) for s in subnode.body)
|
|
if "fix_sentencepiece_gguf(" not in body_src:
|
|
continue
|
|
handler = subnode.handlers[0]
|
|
assert handler.type is not None
|
|
assert isinstance(handler.type, ast.Name)
|
|
assert handler.type.id == "Exception"
|
|
return
|
|
raise AssertionError(
|
|
"fix_sentencepiece_gguf try block not found in unsloth_save_pretrained_gguf"
|
|
)
|
|
|
|
|
|
def test_tokenizer_utils_uses_import_protobuf_fallback_pattern():
|
|
with open(_TOK_PY) as f:
|
|
src = f.read()
|
|
tree = ast.parse(src)
|
|
for node in ast.walk(tree):
|
|
if isinstance(node, ast.FunctionDef) and node.name == "fix_sentencepiece_gguf":
|
|
fn_src = ast.unparse(node)
|
|
assert "import_protobuf" in fn_src
|
|
return
|
|
raise AssertionError("fix_sentencepiece_gguf not found in tokenizer_utils.py")
|
|
|
|
|
|
def test_all_special_tokens_are_gated_by_tokenizer_json_not_by_type(tmp_path):
|
|
pieces = [
|
|
("<s>", 0.0, CONTROL),
|
|
("a", -1.0, NORMAL),
|
|
("<n1>", -1.0, NORMAL),
|
|
("<u1>", -1.0, USER_DEFINED),
|
|
]
|
|
(tmp_path / "tokenizer.model").write_bytes(_build(pieces))
|
|
fix_sentencepiece_gguf(str(tmp_path))
|
|
got = dict(_read(str(tmp_path / "tokenizer.model")))
|
|
assert got["<n1>"] == NORMAL
|
|
assert got["<u1>"] == USER_DEFINED
|