Merge remote-tracking branch 'origin/main' into daniel/studio-sliding-window-compaction
This commit is contained in:
commit
44de2ee2ec
6 changed files with 128 additions and 14 deletions
10
install.ps1
10
install.ps1
|
|
@ -1300,7 +1300,7 @@ shell.Run cmd, 0, False
|
|||
if ($SkipTorch) {
|
||||
# No-torch: install unsloth + unsloth-zoo with --no-deps, then
|
||||
# runtime deps (typer, safetensors, transformers, etc.) with --no-deps.
|
||||
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --no-deps --reinstall-package unsloth --reinstall-package unsloth-zoo "unsloth>=2026.5.6" unsloth-zoo }
|
||||
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --no-deps --reinstall-package unsloth --reinstall-package unsloth-zoo "unsloth>=2026.5.7" unsloth-zoo }
|
||||
if ($baseInstallExit -eq 0) {
|
||||
# Resolve pydantic WITH deps so pip pins pydantic-core
|
||||
# to the matching version (no-torch-runtime.txt below
|
||||
|
|
@ -1314,7 +1314,7 @@ shell.Run cmd, 0, False
|
|||
}
|
||||
}
|
||||
} else {
|
||||
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --reinstall-package unsloth --reinstall-package unsloth-zoo "unsloth>=2026.5.6" unsloth-zoo }
|
||||
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --reinstall-package unsloth --reinstall-package unsloth-zoo "unsloth>=2026.5.7" unsloth-zoo }
|
||||
}
|
||||
if ($baseInstallExit -ne 0) {
|
||||
Write-Host "[ERROR] Failed to install unsloth (exit code $baseInstallExit)" -ForegroundColor Red
|
||||
|
|
@ -1352,7 +1352,7 @@ shell.Run cmd, 0, False
|
|||
if ($SkipTorch) {
|
||||
# No-torch: install unsloth + unsloth-zoo with --no-deps, then
|
||||
# runtime deps (typer, safetensors, transformers, etc.) with --no-deps.
|
||||
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --no-deps --upgrade-package unsloth --upgrade-package unsloth-zoo "unsloth>=2026.5.6" unsloth-zoo }
|
||||
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --no-deps --upgrade-package unsloth --upgrade-package unsloth-zoo "unsloth>=2026.5.7" unsloth-zoo }
|
||||
if ($baseInstallExit -eq 0) {
|
||||
# Same pydantic-with-deps trick as the migrated branch.
|
||||
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython pydantic }
|
||||
|
|
@ -1364,7 +1364,7 @@ shell.Run cmd, 0, False
|
|||
}
|
||||
}
|
||||
} elseif ($StudioLocalInstall) {
|
||||
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --upgrade-package unsloth "unsloth>=2026.5.6" unsloth-zoo }
|
||||
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --upgrade-package unsloth "unsloth>=2026.5.7" unsloth-zoo }
|
||||
} else {
|
||||
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython --upgrade-package unsloth -- "$PackageName" }
|
||||
}
|
||||
|
|
@ -1392,7 +1392,7 @@ shell.Run cmd, 0, False
|
|||
Write-TauriLog "STEP" "Installing unsloth"
|
||||
substep "installing unsloth (this may take a few minutes)..."
|
||||
if ($StudioLocalInstall) {
|
||||
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython unsloth-zoo "unsloth>=2026.5.6" --torch-backend=auto }
|
||||
$baseInstallExit = Invoke-InstallCommand { uv pip install --python $VenvPython unsloth-zoo "unsloth>=2026.5.7" --torch-backend=auto }
|
||||
if ($baseInstallExit -ne 0) {
|
||||
Write-Host "[ERROR] Failed to install unsloth (exit code $baseInstallExit)" -ForegroundColor Red
|
||||
return (Exit-InstallFailure "Failed to install unsloth (exit code $baseInstallExit)" $baseInstallExit)
|
||||
|
|
|
|||
10
install.sh
10
install.sh
|
|
@ -1865,7 +1865,7 @@ if [ "$_MIGRATED" = true ]; then
|
|||
# to prevent transitive torch resolution.
|
||||
run_install_cmd "install unsloth (migrated no-torch)" uv pip install --python "$_VENV_PY" --no-deps \
|
||||
--reinstall-package unsloth --reinstall-package unsloth-zoo \
|
||||
"unsloth>=2026.5.6" unsloth-zoo
|
||||
"unsloth>=2026.5.7" unsloth-zoo
|
||||
# Resolve pydantic WITH deps so pip pins pydantic-core to the
|
||||
# matching version (no-torch-runtime.txt below is --no-deps).
|
||||
# All transitive deps are torch-free.
|
||||
|
|
@ -1878,7 +1878,7 @@ if [ "$_MIGRATED" = true ]; then
|
|||
else
|
||||
run_install_cmd "install unsloth (migrated)" uv pip install --python "$_VENV_PY" \
|
||||
--reinstall-package unsloth --reinstall-package unsloth-zoo \
|
||||
"unsloth>=2026.5.6" unsloth-zoo
|
||||
"unsloth>=2026.5.7" unsloth-zoo
|
||||
fi
|
||||
if [ "$STUDIO_LOCAL_INSTALL" = true ]; then
|
||||
substep "overlaying local repo (editable)..."
|
||||
|
|
@ -2046,7 +2046,7 @@ elif [ -n "$TORCH_INDEX_URL" ]; then
|
|||
# runtime deps (typer, safetensors, transformers, etc.) with --no-deps.
|
||||
run_install_cmd "install unsloth (no-torch)" uv pip install --python "$_VENV_PY" --no-deps \
|
||||
--upgrade-package unsloth --upgrade-package unsloth-zoo \
|
||||
"unsloth>=2026.5.6" unsloth-zoo
|
||||
"unsloth>=2026.5.7" unsloth-zoo
|
||||
# Same pydantic-with-deps trick as the migrated branch.
|
||||
run_install_cmd "install pydantic (with deps for compatible core)" \
|
||||
uv pip install --python "$_VENV_PY" pydantic
|
||||
|
|
@ -2064,7 +2064,7 @@ elif [ -n "$TORCH_INDEX_URL" ]; then
|
|||
fi
|
||||
elif [ "$STUDIO_LOCAL_INSTALL" = true ]; then
|
||||
run_install_cmd "install unsloth (local)" uv pip install --python "$_VENV_PY" \
|
||||
--upgrade-package unsloth "unsloth>=2026.5.6" unsloth-zoo
|
||||
--upgrade-package unsloth "unsloth>=2026.5.7" unsloth-zoo
|
||||
substep "overlaying local repo (editable)..."
|
||||
run_install_cmd "overlay local repo" uv pip install --python "$_VENV_PY" -e "$_REPO_ROOT" --no-deps
|
||||
substep "overlaying unsloth-zoo from git main..."
|
||||
|
|
@ -2096,7 +2096,7 @@ else
|
|||
tauri_log "STEP" "Installing Unsloth"
|
||||
substep "installing unsloth (this may take a few minutes)..."
|
||||
if [ "$STUDIO_LOCAL_INSTALL" = true ]; then
|
||||
run_install_cmd "install unsloth (auto torch backend)" uv pip install --python "$_VENV_PY" unsloth-zoo "unsloth>=2026.5.6" --torch-backend=auto
|
||||
run_install_cmd "install unsloth (auto torch backend)" uv pip install --python "$_VENV_PY" unsloth-zoo "unsloth>=2026.5.7" --torch-backend=auto
|
||||
substep "overlaying local repo (editable)..."
|
||||
run_install_cmd "overlay local repo" uv pip install --python "$_VENV_PY" -e "$_REPO_ROOT" --no-deps
|
||||
substep "overlaying unsloth-zoo from git main..."
|
||||
|
|
|
|||
|
|
@ -475,6 +475,7 @@ class ExportBackend:
|
|||
self.current_model.save_pretrained_merged(
|
||||
save_directory,
|
||||
self.current_tokenizer,
|
||||
save_method = "merged_16bit",
|
||||
)
|
||||
else:
|
||||
self.current_model.save_pretrained(save_directory)
|
||||
|
|
@ -510,6 +511,7 @@ class ExportBackend:
|
|||
self.current_model.save_pretrained_merged(
|
||||
tmp_dir,
|
||||
self.current_tokenizer,
|
||||
save_method = "merged_16bit",
|
||||
)
|
||||
self.current_model.push_to_hub_merged(
|
||||
repo_id,
|
||||
|
|
|
|||
77
tests/python/test_construct_chat_template_validation.py
Normal file
77
tests/python/test_construct_chat_template_validation.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
"""Negative-path validation tests for unsloth.chat_templates.construct_chat_template.
|
||||
|
||||
Regression coverage for the str.find() / regex no-match guards added in
|
||||
PR #5763 follow-up: missing placeholders or unrecoverable two-example
|
||||
structures must raise RuntimeError with a clear message, not IndexError
|
||||
or AttributeError, and must never silently drop the last character via
|
||||
s[:-1].
|
||||
|
||||
Uses a minimal fake tokenizer so the cases run on CPU-only CI without
|
||||
HF_TOKEN and without downloading a gated model. The validation paths
|
||||
exercised here fail before construct_chat_template reaches any heavy
|
||||
tokenizer interaction, so the stub stays small.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from unsloth.chat_templates import construct_chat_template
|
||||
|
||||
|
||||
class _FakeTokenizer:
|
||||
"""Minimum surface construct_chat_template touches before the
|
||||
validation guards fire."""
|
||||
|
||||
name_or_path = "fake/tokenizer"
|
||||
eos_token = "</s>"
|
||||
|
||||
def get_vocab(self):
|
||||
return {"</s>": 0}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"template, expected_in_message",
|
||||
[
|
||||
("only {INPUT} here, no output marker", "{OUTPUT}"),
|
||||
("only {OUTPUT} here, no input marker", "{INPUT}"),
|
||||
("neither sentinel here, just literal text", "{INPUT}"),
|
||||
("neither sentinel here, just literal text", "{OUTPUT}"),
|
||||
],
|
||||
)
|
||||
def test_missing_placeholder_in_chat_template_raises(template, expected_in_message):
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
construct_chat_template(
|
||||
tokenizer = _FakeTokenizer(),
|
||||
chat_template = template,
|
||||
extra_eos_tokens = ["</s>"],
|
||||
)
|
||||
assert expected_in_message in str(exc_info.value)
|
||||
|
||||
|
||||
def test_single_pair_template_raises_clear_error_not_attribute_error():
|
||||
"""One {INPUT}/{OUTPUT} pair (rather than the required two) used to
|
||||
crash with AttributeError on `found.group(1)` after the for-loop
|
||||
broke without setting `found`. Must raise RuntimeError now."""
|
||||
template = "user: {INPUT}\nassistant: {OUTPUT}\n"
|
||||
with pytest.raises(RuntimeError):
|
||||
construct_chat_template(
|
||||
tokenizer = _FakeTokenizer(),
|
||||
chat_template = template,
|
||||
extra_eos_tokens = ["</s>"],
|
||||
)
|
||||
|
||||
|
||||
def test_error_message_excerpt_is_bounded():
|
||||
"""Error messages must include a bounded excerpt of the offending
|
||||
template, not dump arbitrarily large content into the traceback."""
|
||||
huge = ("garbage " * 5000) + "{INPUT}" # ~40 KB, missing {OUTPUT}
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
construct_chat_template(
|
||||
tokenizer = _FakeTokenizer(),
|
||||
chat_template = huge,
|
||||
extra_eos_tokens = ["</s>"],
|
||||
)
|
||||
msg = str(exc_info.value)
|
||||
# Excerpt is repr-quoted and capped; total message should stay well
|
||||
# under the template length.
|
||||
assert len(msg) < 1000
|
||||
assert "{OUTPUT}" in msg
|
||||
|
|
@ -2461,17 +2461,40 @@ extra_eos_tokens = None,
|
|||
f"{left_changed}"
|
||||
)
|
||||
except:
|
||||
ending = chat_template[chat_template.find("{OUTPUT}") + len("{OUTPUT}"):]
|
||||
output_pos = chat_template.find("{OUTPUT}")
|
||||
input_pos = chat_template.find("{INPUT}")
|
||||
if output_pos == -1 or input_pos == -1:
|
||||
missing = []
|
||||
if input_pos == -1: missing.append("{INPUT}")
|
||||
if output_pos == -1: missing.append("{OUTPUT}")
|
||||
raise RuntimeError(
|
||||
f"Unsloth: chat_template must contain {' and '.join(missing)} "
|
||||
f"placeholder(s). Got: {chat_template[:200]!r}"
|
||||
)
|
||||
ending = chat_template[output_pos + len("{OUTPUT}"):]
|
||||
|
||||
ending = re.escape(ending)
|
||||
find_text = "{INPUT}" + ending + "(.+?{OUTPUT}" + ending + ")"
|
||||
response_part = re.findall(find_text, chat_template, flags = re.DOTALL | re.MULTILINE)
|
||||
if len(response_part) == 0:
|
||||
raise RuntimeError(
|
||||
"Unsloth: Could not recover a two-example structure from chat_template. "
|
||||
"Provide exactly two {INPUT}/{OUTPUT} pairs (and optionally {SYSTEM}). "
|
||||
f"Got: {chat_template[:200]!r}"
|
||||
)
|
||||
response_part = response_part[0]
|
||||
|
||||
found = None
|
||||
for j in range(1, len(response_part)):
|
||||
try_find = re.escape(response_part[:j])
|
||||
try: found = next(re.finditer("(" + try_find + ").+?\\{INPUT\\}", chat_template, flags = re.DOTALL | re.MULTILINE))
|
||||
except: break
|
||||
if found is None:
|
||||
raise RuntimeError(
|
||||
"Unsloth: Could not locate a separator between examples in chat_template. "
|
||||
"Provide exactly two {INPUT}/{OUTPUT} pairs (and optionally {SYSTEM}). "
|
||||
f"Got: {chat_template[:200]!r}"
|
||||
)
|
||||
separator = found.group(1)
|
||||
|
||||
response_start = chat_template.find(response_part)
|
||||
|
|
@ -2607,8 +2630,20 @@ extra_eos_tokens = None,
|
|||
jinja_template = "{{ bos_token }}" + jinja_template
|
||||
|
||||
# Get instruction and output parts for train_on_inputs = False
|
||||
input_part = input_part [:input_part .find("{INPUT}")]
|
||||
output_part = output_part[:output_part.find("{OUTPUT}")]
|
||||
input_idx = input_part .find("{INPUT}")
|
||||
output_idx = output_part.find("{OUTPUT}")
|
||||
if input_idx == -1:
|
||||
raise RuntimeError(
|
||||
f"Unsloth: The instruction section of the template must contain the "
|
||||
f"'{{INPUT}}' placeholder. Section: {input_part[:200]!r}"
|
||||
)
|
||||
if output_idx == -1:
|
||||
raise RuntimeError(
|
||||
f"Unsloth: The response section of the template must contain the "
|
||||
f"'{{OUTPUT}}' placeholder. Section: {output_part[:200]!r}"
|
||||
)
|
||||
input_part = input_part [:input_idx ]
|
||||
output_part = output_part[:output_idx]
|
||||
return modelfile, jinja_template, input_part, output_part
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
__version__ = "2026.5.6"
|
||||
__version__ = "2026.5.7"
|
||||
|
||||
__all__ = [
|
||||
"SUPPORTS_BFLOAT16",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue