diff --git a/pyproject.toml b/pyproject.toml
index 16f662d6ba..648aad3ae1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -25,8 +25,30 @@ classifiers = [
"Topic :: Scientific/Engineering :: Artificial Intelligence",
]
dependencies = [
+ "unsloth_zoo>=2026.6.4",
+ "wheel>=0.42.0",
+ "packaging",
+ "torch>=2.4.0,<2.11.0",
+ "torchvision",
+ "numpy",
+ "tqdm",
+ "psutil",
+ "tyro",
+ "protobuf",
+ "xformers>=0.0.27.post2 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
+ "bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
+ "triton>=3.0.0 ; ('linux' in sys_platform)",
+ "triton-windows ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
+ "sentencepiece>=0.2.0",
+ "datasets>=3.4.1,!=4.0.*,!=4.1.0,<4.4.0",
+ "accelerate>=0.34.1",
+ "peft>=0.18.0,!=0.11.0",
+ "huggingface_hub>=0.34.0",
+ "hf_transfer",
+ "diffusers",
+ "transformers>=4.51.3,!=4.52.0,!=4.52.1,!=4.52.2,!=4.52.3,!=4.53.0,!=4.54.0,!=4.55.0,!=4.55.1,!=4.57.0,!=4.57.4,!=4.57.5,!=5.0.0,!=5.1.0,<=5.5.0",
+ "trl>=0.18.2,!=0.19.0,<=0.24.0",
"typer",
- "rich",
"pydantic",
"pyyaml",
"nest-asyncio",
@@ -55,7 +77,6 @@ studio = [
"frontend/.git*",
"backend/requirements/**/*",
"backend/plugins/**/*",
- "backend/assets/**/*.jinja",
"backend/core/data_recipe/oxc-validator/*.json",
"backend/core/data_recipe/oxc-validator/*.mjs",
]
@@ -69,11 +90,11 @@ triton = [
"triton>=3.0.0 ; ('linux' in sys_platform)",
"triton-windows ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
-
-huggingfacenotorch = [
+huggingface = [
"unsloth_zoo>=2026.6.4",
"wheel>=0.42.0",
"packaging",
+ "torchvision",
"numpy",
"tqdm",
"psutil",
@@ -89,12 +110,10 @@ huggingfacenotorch = [
"transformers>=4.51.3,!=4.52.0,!=4.52.1,!=4.52.2,!=4.52.3,!=4.53.0,!=4.54.0,!=4.55.0,!=4.55.1,!=4.57.0,!=4.57.4,!=4.57.5,!=5.0.0,!=5.1.0,<=5.5.0",
"trl>=0.18.2,!=0.19.0,<=0.24.0",
"sentence-transformers",
-]
-huggingface = [
- "unsloth[huggingfacenotorch]",
- "unsloth_zoo>=2026.6.4",
- "torchvision",
- "unsloth[triton]",
+ "typer",
+ "pydantic",
+ "pyyaml",
+ "nest-asyncio",
]
windows = [
"unsloth[huggingface]",
@@ -105,240 +124,126 @@ base = [
"unsloth[huggingface]",
]
cu118only = [
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.22.post7%2Bcu118-cp39-cp39-manylinux2014_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.22.post7%2Bcu118-cp310-cp310-manylinux2014_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.22.post7%2Bcu118-cp311-cp311-manylinux2014_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
+ "xformers==0.0.22.post7 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu121only = [
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.22.post7-cp39-cp39-manylinux2014_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.22.post7-cp310-cp310-manylinux2014_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.22.post7-cp311-cp311-manylinux2014_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
+ "xformers==0.0.22.post7 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu118onlytorch211 = [
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.23%2Bcu118-cp39-cp39-manylinux2014_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.23%2Bcu118-cp310-cp310-manylinux2014_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.23%2Bcu118-cp311-cp311-manylinux2014_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
+ "xformers==0.0.23 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu121onlytorch211 = [
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.23-cp39-cp39-manylinux2014_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.23-cp310-cp310-manylinux2014_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.23-cp311-cp311-manylinux2014_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
+ "xformers==0.0.23 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu118onlytorch212 = [
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.23.post1%2Bcu118-cp39-cp39-manylinux2014_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.23.post1%2Bcu118-cp310-cp310-manylinux2014_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.23.post1%2Bcu118-cp311-cp311-manylinux2014_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
+ "xformers==0.0.23.post1 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu121onlytorch212 = [
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.23.post1-cp39-cp39-manylinux2014_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.23.post1-cp310-cp310-manylinux2014_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.23.post1-cp311-cp311-manylinux2014_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
+ "xformers==0.0.23.post1 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu118onlytorch220 = [
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.24%2Bcu118-cp39-cp39-manylinux2014_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.24%2Bcu118-cp310-cp310-manylinux2014_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.24%2Bcu118-cp311-cp311-manylinux2014_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
+ "xformers==0.0.24 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu121onlytorch220 = [
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.24-cp39-cp39-manylinux2014_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.24-cp310-cp310-manylinux2014_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.24-cp311-cp311-manylinux2014_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
+ "xformers==0.0.24 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu118onlytorch230 = [
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.27%2Bcu118-cp39-cp39-manylinux2014_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.27%2Bcu118-cp310-cp310-manylinux2014_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.27%2Bcu118-cp311-cp311-manylinux2014_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.27%2Bcu118-cp312-cp312-manylinux2014_x86_64.whl ; python_version=='3.12' and ('linux' in sys_platform)",
+ "xformers==0.0.27 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu121onlytorch230 = [
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.27-cp39-cp39-manylinux2014_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.27-cp310-cp310-manylinux2014_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.27-cp311-cp311-manylinux2014_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.27-cp312-cp312-manylinux2014_x86_64.whl ; python_version=='3.12' and ('linux' in sys_platform)",
+ "xformers==0.0.27 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu118onlytorch240 = [
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.27.post2%2Bcu118-cp39-cp39-manylinux2014_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.27.post2%2Bcu118-cp310-cp310-manylinux2014_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.27.post2%2Bcu118-cp311-cp311-manylinux2014_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.27.post2%2Bcu118-cp312-cp312-manylinux2014_x86_64.whl ; python_version=='3.12' and ('linux' in sys_platform)",
+ "xformers==0.0.27.post2 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu121onlytorch240 = [
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.28.post1-cp39-cp39-manylinux_2_28_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.28.post1-cp310-cp310-manylinux_2_28_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.28.post1-cp311-cp311-manylinux_2_28_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.28.post1-cp312-cp312-manylinux_2_28_x86_64.whl ; python_version=='3.12' and ('linux' in sys_platform)",
+ "xformers==0.0.27.post2 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu124onlytorch240 = [
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.28.post1-cp39-cp39-manylinux_2_28_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.28.post1-cp310-cp310-manylinux_2_28_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.28.post1-cp311-cp311-manylinux_2_28_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.28.post1-cp312-cp312-manylinux_2_28_x86_64.whl ; python_version=='3.12' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.28.post1-cp39-cp39-win_amd64.whl ; python_version=='3.9' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.28.post1-cp310-cp310-win_amd64.whl ; python_version=='3.10' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.28.post1-cp311-cp311-win_amd64.whl ; python_version=='3.11' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.28.post1-cp312-cp312-win_amd64.whl ; python_version=='3.12' and (sys_platform == 'win32')",
+ "xformers==0.0.28.post1 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu118onlytorch250 = [
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.28.post2-cp39-cp39-manylinux_2_28_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.28.post2-cp310-cp310-manylinux_2_28_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.28.post2-cp311-cp311-manylinux_2_28_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.28.post2-cp312-cp312-manylinux_2_28_x86_64.whl ; python_version=='3.12' and ('linux' in sys_platform)",
+ "xformers==0.0.28.post2 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu121onlytorch250 = [
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.28.post2-cp39-cp39-manylinux_2_28_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.28.post2-cp310-cp310-manylinux_2_28_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.28.post2-cp311-cp311-manylinux_2_28_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.28.post2-cp312-cp312-manylinux_2_28_x86_64.whl ; python_version=='3.12' and ('linux' in sys_platform)",
+ "xformers==0.0.28.post2 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu124onlytorch250 = [
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.28.post2-cp39-cp39-manylinux_2_28_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.28.post2-cp310-cp310-manylinux_2_28_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.28.post2-cp311-cp311-manylinux_2_28_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.28.post2-cp312-cp312-manylinux_2_28_x86_64.whl ; python_version=='3.12' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.28.post2-cp39-cp39-win_amd64.whl ; python_version=='3.9' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.28.post2-cp310-cp310-win_amd64.whl ; python_version=='3.10' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.28.post2-cp311-cp311-win_amd64.whl ; python_version=='3.11' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.28.post2-cp312-cp312-win_amd64.whl ; python_version=='3.12' and (sys_platform == 'win32')",
+ "xformers==0.0.28.post2 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu118onlytorch251 = [
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.29.post1-cp39-cp39-manylinux_2_28_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.29.post1-cp310-cp310-manylinux_2_28_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.29.post1-cp311-cp311-manylinux_2_28_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.29.post1-cp312-cp312-manylinux_2_28_x86_64.whl ; python_version=='3.12' and ('linux' in sys_platform)",
+ "xformers==0.0.29.post1 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu121onlytorch251 = [
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.29.post1-cp39-cp39-manylinux_2_28_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.29.post1-cp310-cp310-manylinux_2_28_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.29.post1-cp311-cp311-manylinux_2_28_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu121/xformers-0.0.29.post1-cp312-cp312-manylinux_2_28_x86_64.whl ; python_version=='3.12' and ('linux' in sys_platform)",
+ "xformers==0.0.29.post1 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu124onlytorch251 = [
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.29.post1-cp39-cp39-manylinux_2_28_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.29.post1-cp310-cp310-manylinux_2_28_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.29.post1-cp311-cp311-manylinux_2_28_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.29.post1-cp312-cp312-manylinux_2_28_x86_64.whl ; python_version=='3.12' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.29.post1-cp39-cp39-win_amd64.whl ; python_version=='3.9' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.29.post1-cp310-cp310-win_amd64.whl ; python_version=='3.10' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.29.post1-cp311-cp311-win_amd64.whl ; python_version=='3.11' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.29.post1-cp312-cp312-win_amd64.whl ; python_version=='3.12' and (sys_platform == 'win32')",
+ "xformers==0.0.29.post1 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu118onlytorch260 = [
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.29.post3-cp39-cp39-manylinux_2_28_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.29.post3-cp310-cp310-manylinux_2_28_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.29.post3-cp311-cp311-manylinux_2_28_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.29.post3-cp312-cp312-manylinux_2_28_x86_64.whl ; python_version=='3.12' and ('linux' in sys_platform)",
+ "xformers==0.0.29.post3 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu124onlytorch260 = [
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.29.post3-cp39-cp39-manylinux_2_28_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.29.post3-cp310-cp310-manylinux_2_28_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.29.post3-cp311-cp311-manylinux_2_28_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.29.post3-cp312-cp312-manylinux_2_28_x86_64.whl ; python_version=='3.12' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.29.post3-cp39-cp39-win_amd64.whl ; python_version=='3.9' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.29.post3-cp310-cp310-win_amd64.whl ; python_version=='3.10' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.29.post3-cp311-cp311-win_amd64.whl ; python_version=='3.11' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu124/xformers-0.0.29.post3-cp312-cp312-win_amd64.whl ; python_version=='3.12' and (sys_platform == 'win32')",
+ "xformers==0.0.29.post3 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu126onlytorch260 = [
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.29.post3-cp39-cp39-manylinux_2_28_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.29.post3-cp310-cp310-manylinux_2_28_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.29.post3-cp311-cp311-manylinux_2_28_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.29.post3-cp312-cp312-manylinux_2_28_x86_64.whl ; python_version=='3.12' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.29.post3-cp39-cp39-win_amd64.whl ; python_version=='3.9' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.29.post3-cp310-cp310-win_amd64.whl ; python_version=='3.10' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.29.post3-cp311-cp311-win_amd64.whl ; python_version=='3.11' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.29.post3-cp312-cp312-win_amd64.whl ; python_version=='3.12' and (sys_platform == 'win32')",
+ "xformers==0.0.29.post3 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu118onlytorch270 = [
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.30-cp39-cp39-manylinux_2_28_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.30-cp310-cp310-manylinux_2_28_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.30-cp311-cp311-manylinux_2_28_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.30-cp312-cp312-manylinux_2_28_x86_64.whl ; python_version=='3.12' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.30-cp39-cp39-win_amd64.whl ; python_version=='3.9' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.30-cp310-cp310-win_amd64.whl ; python_version=='3.10' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.30-cp311-cp311-win_amd64.whl ; python_version=='3.11' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.30-cp312-cp312-win_amd64.whl ; python_version=='3.12' and (sys_platform == 'win32')",
+ "xformers==0.0.30 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu126onlytorch270 = [
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.30-cp39-cp39-manylinux_2_28_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.30-cp310-cp310-manylinux_2_28_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.30-cp311-cp311-manylinux_2_28_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.30-cp312-cp312-manylinux_2_28_x86_64.whl ; python_version=='3.12' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.30-cp39-cp39-win_amd64.whl ; python_version=='3.9' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.30-cp310-cp310-win_amd64.whl ; python_version=='3.10' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.30-cp311-cp311-win_amd64.whl ; python_version=='3.11' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.30-cp312-cp312-win_amd64.whl ; python_version=='3.12' and (sys_platform == 'win32')",
+ "xformers==0.0.30 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu128onlytorch270 = [
- "xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.30-cp39-cp39-manylinux_2_28_x86_64.whl ; python_version=='3.9' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.30-cp310-cp310-manylinux_2_28_x86_64.whl ; python_version=='3.10' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.30-cp311-cp311-manylinux_2_28_x86_64.whl ; python_version=='3.11' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.30-cp312-cp312-manylinux_2_28_x86_64.whl ; python_version=='3.12' and ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.30-cp39-cp39-win_amd64.whl ; python_version=='3.9' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.30-cp310-cp310-win_amd64.whl ; python_version=='3.10' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.30-cp311-cp311-win_amd64.whl ; python_version=='3.11' and (sys_platform == 'win32')",
- "xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.30-cp312-cp312-win_amd64.whl ; python_version=='3.12' and (sys_platform == 'win32')",
+ "xformers==0.0.30 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu118onlytorch271 = [
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.31.post1-cp39-abi3-manylinux_2_28_x86_64.whl ; ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu118/xformers-0.0.31.post1-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32')",
+ "xformers==0.0.31.post1 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu126onlytorch271 = [
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.31.post1-cp39-abi3-manylinux_2_28_x86_64.whl ; ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.31.post1-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32')",
+ "xformers==0.0.31.post1 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu128onlytorch271 = [
- "xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.31.post1-cp39-abi3-manylinux_2_28_x86_64.whl ; ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.31.post1-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32')",
+ "xformers==0.0.31.post1 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu118onlytorch280 = [
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.32.post2-cp39-abi3-manylinux_2_28_x86_64.whl ; ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.32.post2-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32')",
+ "xformers==0.0.32.post2 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu126onlytorch280 = [
- "xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.32.post2-cp39-abi3-manylinux_2_28_x86_64.whl ; ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.32.post2-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32')",
+ "xformers==0.0.32.post2 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu128onlytorch280 = [
- "xformers @ https://download.pytorch.org/whl/cu129/xformers-0.0.32.post2-cp39-abi3-manylinux_2_28_x86_64.whl ; ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu129/xformers-0.0.32.post2-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32')",
+ "xformers==0.0.32.post2 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu130onlytorch280 = [
]
cu126onlytorch290 = [
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.33.post1-cp39-abi3-manylinux_2_28_x86_64.whl ; ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.33.post1-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32')",
+ "xformers==0.0.33.post1 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu128onlytorch290 = [
- "xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.33.post1-cp39-abi3-manylinux_2_28_x86_64.whl ; ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.33.post1-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32')",
+ "xformers==0.0.33.post1 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu130onlytorch290 = [
- "xformers @ https://download.pytorch.org/whl/cu130/xformers-0.0.33.post1-cp39-abi3-manylinux_2_28_x86_64.whl ; ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu130/xformers-0.0.33.post1-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32')",
+ "xformers==0.0.33.post1 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu126onlytorch291 = [
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.33.post2-cp39-abi3-manylinux_2_28_x86_64.whl ; ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.33.post2-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32')",
+ "xformers==0.0.33.post2 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu128onlytorch291 = [
- "xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.33.post2-cp39-abi3-manylinux_2_28_x86_64.whl ; ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.33.post2-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32')",
+ "xformers==0.0.33.post2 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu130onlytorch291 = [
- "xformers @ https://download.pytorch.org/whl/cu130/xformers-0.0.33.post2-cp39-abi3-manylinux_2_28_x86_64.whl ; ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu130/xformers-0.0.33.post2-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32')",
+ "xformers==0.0.33.post2 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu126onlytorch2100 = [
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.34-cp39-abi3-manylinux_2_28_x86_64.whl ; ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu126/xformers-0.0.34-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32')",
+ "xformers==0.0.34 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu128onlytorch2100 = [
- "xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.34-cp39-abi3-manylinux_2_28_x86_64.whl ; ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu128/xformers-0.0.34-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32')",
+ "xformers==0.0.34 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu130onlytorch2100 = [
- "xformers @ https://download.pytorch.org/whl/cu130/xformers-0.0.34-cp39-abi3-manylinux_2_28_x86_64.whl ; ('linux' in sys_platform)",
- "xformers @ https://download.pytorch.org/whl/cu130/xformers-0.0.34-cp39-abi3-win_amd64.whl ; (sys_platform == 'win32')",
+ "xformers==0.0.34 ; ('linux' in sys_platform or sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
]
cu118 = [
"unsloth[huggingface]",
@@ -352,22 +257,22 @@ cu121 = [
]
cu118-torch211 = [
"unsloth[huggingface]",
- "bitsandbytes==0.45.5",
+ "bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu118onlytorch211]",
]
cu121-torch211 = [
"unsloth[huggingface]",
- "bitsandbytes==0.45.5",
+ "bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu121onlytorch211]",
]
cu118-torch212 = [
"unsloth[huggingface]",
- "bitsandbytes==0.45.5",
+ "bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu118onlytorch212]",
]
cu121-torch212 = [
"unsloth[huggingface]",
- "bitsandbytes==0.45.5",
+ "bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu121onlytorch212]",
]
cu118-torch220 = [
@@ -437,17 +342,17 @@ cu124-torch251 = [
]
cu118-torch260 = [
"unsloth[huggingface]",
- "bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
+ "bitsandbytes>=0.45.1",
"unsloth[cu118onlytorch260]",
]
cu124-torch260 = [
"unsloth[huggingface]",
- "bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
+ "bitsandbytes>=0.45.1",
"unsloth[cu124onlytorch260]",
]
cu126-torch260 = [
"unsloth[huggingface]",
- "bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
+ "bitsandbytes>=0.45.1",
"unsloth[cu126onlytorch260]",
]
cu118-torch270 = [
@@ -557,16 +462,19 @@ conda = [
]
colab-torch211 = [
"unsloth[huggingface]",
- "bitsandbytes==0.45.5",
+ "bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu121onlytorch211]",
]
+flashattention = [
+ "packaging ; ('linux' in sys_platform)",
+ "ninja ; ('linux' in sys_platform)",
+ "flash-attn>=2.6.3 ; ('linux' in sys_platform)",
+]
colab-ampere-torch211 = [
"unsloth[huggingface]",
- "bitsandbytes==0.45.5",
+ "bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu121onlytorch211]",
- "packaging",
- "ninja",
- "flash-attn>=2.6.3 ; ('linux' in sys_platform)",
+ "unsloth[flashattention]",
]
colab-torch220 = [
"unsloth[huggingface]",
@@ -577,9 +485,7 @@ colab-ampere-torch220 = [
"unsloth[huggingface]",
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu121onlytorch220]",
- "packaging",
- "ninja",
- "flash-attn>=2.6.3 ; ('linux' in sys_platform)",
+ "unsloth[flashattention]",
]
colab-new = [
"unsloth_zoo>=2026.6.4",
@@ -598,6 +504,10 @@ colab-new = [
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[triton]",
"sentence-transformers",
+ "typer",
+ "pydantic",
+ "pyyaml",
+ "nest-asyncio",
]
colab-no-deps = [
"accelerate>=0.34.1",
@@ -610,11 +520,6 @@ colab-no-deps = [
colab = [
"unsloth[cu121]",
]
-flashattention = [
- "packaging ; ('linux' in sys_platform)",
- "ninja ; ('linux' in sys_platform)",
- "flash-attn>=2.6.3 ; ('linux' in sys_platform)",
-]
colab-ampere = [
"unsloth[colab-ampere-torch220]",
"unsloth[flashattention]",
@@ -633,13 +538,13 @@ cu121-ampere = [
]
cu118-ampere-torch211 = [
"unsloth[huggingface]",
- "bitsandbytes==0.45.5",
+ "bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu118onlytorch211]",
"unsloth[flashattention]",
]
cu121-ampere-torch211 = [
"unsloth[huggingface]",
- "bitsandbytes==0.45.5",
+ "bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu121onlytorch211]",
"unsloth[flashattention]",
]
@@ -723,19 +628,19 @@ cu124-ampere-torch251 = [
]
cu118-ampere-torch260 = [
"unsloth[huggingface]",
- "bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
+ "bitsandbytes>=0.45.1",
"unsloth[cu118onlytorch260]",
"unsloth[flashattention]",
]
cu124-ampere-torch260 = [
"unsloth[huggingface]",
- "bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
+ "bitsandbytes>=0.45.1",
"unsloth[cu124onlytorch260]",
"unsloth[flashattention]",
]
cu126-ampere-torch260 = [
"unsloth[huggingface]",
- "bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
+ "bitsandbytes>=0.45.1",
"unsloth[cu126onlytorch260]",
"unsloth[flashattention]",
]
@@ -797,7 +702,6 @@ cu130-ampere-torch280 = [
"unsloth[huggingface]",
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu130onlytorch280]",
- "unsloth[flashattention]",
]
cu126-ampere-torch290 = [
"unsloth[huggingface]",
@@ -844,464 +748,6 @@ cu130-ampere-torch2100 = [
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
"unsloth[cu130onlytorch2100]",
]
-flashattentiontorch260abiFALSEcu12x = [
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.6cxx11abiFALSE-cp39-cp39-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.9'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.6cxx11abiFALSE-cp310-cp310-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.10'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.6cxx11abiFALSE-cp311-cp311-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.11'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.6cxx11abiFALSE-cp312-cp312-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.12'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.6cxx11abiFALSE-cp313-cp313-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.13'",
-]
-flashattentiontorch260abiTRUEcu12x = [
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.6cxx11abiTRUE-cp39-cp39-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.9'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.6cxx11abiTRUE-cp310-cp310-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.10'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.6cxx11abiTRUE-cp311-cp311-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.11'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.6cxx11abiTRUE-cp312-cp312-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.12'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.6cxx11abiTRUE-cp313-cp313-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.13'",
-]
-flashattentiontorch250abiFALSEcu12x = [
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.5cxx11abiFALSE-cp39-cp39-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.9'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.5cxx11abiFALSE-cp310-cp310-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.10'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.5cxx11abiFALSE-cp311-cp311-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.11'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.5cxx11abiFALSE-cp312-cp312-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.12'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.5cxx11abiFALSE-cp313-cp313-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.13'",
-]
-flashattentiontorch250abiTRUEcu12x = [
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.5cxx11abiTRUE-cp39-cp39-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.9'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.5cxx11abiTRUE-cp310-cp310-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.10'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.5cxx11abiTRUE-cp311-cp311-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.11'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.5cxx11abiTRUE-cp312-cp312-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.12'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.5cxx11abiTRUE-cp313-cp313-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.13'",
-]
-flashattentiontorch240abiFALSEcu12x = [
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.4cxx11abiFALSE-cp39-cp39-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.9'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.4cxx11abiFALSE-cp310-cp310-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.10'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.4cxx11abiFALSE-cp311-cp311-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.11'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.4cxx11abiFALSE-cp312-cp312-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.12'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.4cxx11abiFALSE-cp313-cp313-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.13'",
-]
-flashattentiontorch240abiTRUEcu12x = [
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.4cxx11abiTRUE-cp39-cp39-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.9'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.4cxx11abiTRUE-cp310-cp310-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.10'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.4cxx11abiTRUE-cp311-cp311-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.11'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.4cxx11abiTRUE-cp312-cp312-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.12'",
- "flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.4cxx11abiTRUE-cp313-cp313-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.13'",
-]
-intelgputorch260 = [
- "unsloth_zoo[intelgpu]",
- "unsloth[huggingfacenotorch]",
-
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.2.0-cp39-cp39-linux_x86_64.whl#sha256=147607f190a7d7aa24ba454def5977fbbfec792fdae18e4ed278cfec29b69271 ; ('linux' in sys_platform) and python_version == '3.9' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.2.0-cp310-cp310-linux_x86_64.whl#sha256=23aa423fa1542afc34f67eb3ba8ef20060f6d1b3a4697eaeab22b11c92b30f2b ; ('linux' in sys_platform) and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.2.0-cp311-cp311-linux_x86_64.whl#sha256=bcfa995229bbfd9ffd8d6c8d9f6428d393e876fa6e23ee3c20e3c0d73ca75ca5 ; ('linux' in sys_platform) and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.2.0-cp312-cp312-linux_x86_64.whl#sha256=bd340903d03470708df3442438acb8b7e08087ab9e61fbe349b2872bf9257ab0 ; ('linux' in sys_platform) and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.2.0-cp313-cp313-linux_x86_64.whl#sha256=814dccc8a07159e6eca74bed70091bc8fea2d9dd87b0d91845f9f38cde62f01c ; ('linux' in sys_platform) and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "bitsandbytes @ https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-manylinux_2_24_x86_64.whl ; ('linux' in sys_platform) and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "bitsandbytes @ https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.6.0%2Bxpu-cp39-cp39-linux_x86_64.whl#sha256=6a8adf6dc4c089406e8b3a7e58ab57a463bddf9b07130d2576e76eced43e92af ; ('linux' in sys_platform) and python_version == '3.9' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.6.0%2Bxpu-cp310-cp310-linux_x86_64.whl#sha256=ff4561cbf07c83bbccaa0f6e9bb0e6dcf721bacd53c9c43c4eb0e7331b4792f9 ; ('linux' in sys_platform) and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.6.0%2Bxpu-cp311-cp311-linux_x86_64.whl#sha256=12005f66b810ddd3ab93f86c4522bcfdd412cbd27fc9d189b661ff7509bc5e8a ; ('linux' in sys_platform) and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.6.0%2Bxpu-cp312-cp312-linux_x86_64.whl#sha256=c4c5c67625cdacf35765c2b94e61fe166e3c3f4a14521b1212a59ad1b3eb0f2e ; ('linux' in sys_platform) and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.6.0%2Bxpu-cp313-cp313-linux_x86_64.whl#sha256=e6864f7a60a5ecc43d5d38f59a16e5dd132384f73dfd3a697f74944026038f7b ; ('linux' in sys_platform) and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-]
-intel-gpu-torch260 = [
- "unsloth[intelgputorch260]"
-]
-intelgputorch270 = [
- "unsloth_zoo[intelgpu]",
- "unsloth[huggingfacenotorch]",
-
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=749a7098492c6a27b356c97149a4a62973b953eae60bc1b6259260974f344913 ; ('linux' in sys_platform) and python_version == '3.9' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=44362e80abd752471a08341093321955b066daa2cfb4810e73b8e3b240850f93 ; ('linux' in sys_platform) and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=faa6b8c945a837a080f641bc8ccc77a98fa66980dcd7e62e715fd853737343fd ; ('linux' in sys_platform) and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=40f6fb65b345dc9a61813abe7ac9a585f2c9808f414d140cc2a5f11f53ee063c ; ('linux' in sys_platform) and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=b22b4c02ec71b4bfc862ae3cdfd2871dc0b05d2b1802f5db2196e0f897d581e9 ; ('linux' in sys_platform) and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.3.0-cp39-cp39-win_amd64.whl#sha256=d4b738d7fa5100c1bd766f91614962828a4810eb57b4df92cd5214a83505a752 ; sys_platform == 'win32' and python_version == '3.9' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.3.0-cp310-cp310-win_amd64.whl#sha256=143fe8a64d807bcdb7d81bbc062816add325570aa160448454ab6ded4a0a17a1 ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.3.0-cp311-cp311-win_amd64.whl#sha256=a8025459ff325d6e3532eb5cf72519db1b178155e7d60aff6c56beb5968fc758 ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.3.0-cp312-cp312-win_amd64.whl#sha256=0dd07e6d5b872e42e48f5ee140e609d4554ca3cc509d5bf509ac232267cf358e ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.3.0-cp313-cp313-win_amd64.whl#sha256=a936a18182d8e065a9933afc9a3ebbffadd38604969f87c493831214539fc027 ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "bitsandbytes @ https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-manylinux_2_24_x86_64.whl ; ('linux' in sys_platform) and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "bitsandbytes @ https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.7.0%2Bxpu-cp39-cp39-linux_x86_64.whl#sha256=f8ee75e50fcbb37ed5b498299ca2264da99ab278a93fae2358e921e4a6e28273 ; ('linux' in sys_platform) and python_version == '3.9' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.7.0%2Bxpu-cp310-cp310-linux_x86_64.whl#sha256=d6fdc342961d98fdcd9d03dfd491a3208bb5f7fbb435841f8f72ce9fdcd2d026 ; ('linux' in sys_platform) and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.7.0%2Bxpu-cp311-cp311-linux_x86_64.whl#sha256=74d07f9357df5cf2bf223ad3c84de16346bfaa0504f988fdd5590d3e177e5e86 ; ('linux' in sys_platform) and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.7.0%2Bxpu-cp312-cp312-linux_x86_64.whl#sha256=c806d44aa2ca5d225629f6fbc6c994d5deaac2d2cde449195bc8e3522ddd219a ; ('linux' in sys_platform) and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.7.0%2Bxpu-cp313-cp313-linux_x86_64.whl#sha256=25d8277b7f01d42e2e014ccbab57a2692b6ec4eff8dcf894eda1b297407cf97a ; ('linux' in sys_platform) and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.7.0%2Bxpu-cp39-cp39-win_amd64.whl#sha256=046e85125266ae69c1a0d083e6c092f947ab4b6b41532c16bafe40dbced845df ; sys_platform == 'win32' and python_version == '3.9' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.7.0%2Bxpu-cp310-cp310-win_amd64.whl#sha256=9ebaeffb82b0b3e39b6030927d3ebe0eb62a0e9045a3b2d7b0a9e7b15222c0db ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.7.0%2Bxpu-cp311-cp311-win_amd64.whl#sha256=356ba66cee127e7e2c942880bd50e03768306a4ea08d358a0f29c6eebfc4bc81 ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.7.0%2Bxpu-cp312-cp312-win_amd64.whl#sha256=94739e665d9b4d5cd7af5f517cb6103f6f9fb421c095184609653a24524040f5 ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.7.0%2Bxpu-cp313-cp313-win_amd64.whl#sha256=31df3cb674918e89bc8c532baa331dc84f4430e1f9c0ec379232db44cba78355 ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-]
-intel-gpu-torch270 = [
- "unsloth[intelgputorch270]"
-]
-intelgputorch280 = [
- "unsloth_zoo[intelgpu]",
- "unsloth[huggingfacenotorch]",
-
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.4.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=ac4d8e33986b1c3c5e48151640539272b2187e83016985853111b46fb82c3c94 ; 'linux' in sys_platform and python_version == '3.9' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.4.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=999fef4c1f711092b9d3086525920545df490de476ecebe899ffc777019ae17f ; 'linux' in sys_platform and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.4.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=57b09c8c492985ff6a27cd3a22b08e8f7b96b407bd8030967b6efbb9f63b80cf ; 'linux' in sys_platform and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=df4bb3282bac9a3b90231700077110d8680b338416de03c2b7c6133c9b602649 ; 'linux' in sys_platform and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=60da63c99ca827bdcb0df28e0298bf7d066dc607454c6d6176783cb4e79d838b ; 'linux' in sys_platform and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.4.0-cp39-cp39-win_amd64.whl#sha256=64aea8de349f3e2e0ebf4c24b011a8122531fdffda5776edaef45829cc241cf8 ; sys_platform == 'win32' and python_version == '3.9' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.4.0-cp310-cp310-win_amd64.whl#sha256=ae573d255b257fdbed319a3440dc9d0a721e31160ab7f6eba1b2226e6a409a1d ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.4.0-cp311-cp311-win_amd64.whl#sha256=8e0ea4558e5776d8ddab0264310be9b26aee5641bcac0da023537556d4317b86 ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.4.0-cp312-cp312-win_amd64.whl#sha256=4090dde07a4fffc34aaf855701a9db28e9fccb57b368ade520f1a0f8e811c878 ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.4.0-cp313-cp313-win_amd64.whl#sha256=a33d0888f3c8df028a2d028842715837d0049524d6c06b9bb11869890a13601a ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.8.0%2Bxpu-cp39-cp39-linux_x86_64.whl ; 'linux' in sys_platform and python_version == '3.9' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.8.0%2Bxpu-cp310-cp310-linux_x86_64.whl ; 'linux' in sys_platform and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.8.0%2Bxpu-cp311-cp311-linux_x86_64.whl ; 'linux' in sys_platform and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.8.0%2Bxpu-cp312-cp312-linux_x86_64.whl ; 'linux' in sys_platform and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.8.0%2Bxpu-cp313-cp313-linux_x86_64.whl ; 'linux' in sys_platform and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.8.0%2Bxpu-cp39-cp39-win_amd64.whl#sha256=f2f401276892428e4875cf1d8717c5cbab704b16fc594ccf23795e7b16549a99 ; sys_platform == 'win32' and python_version == '3.9' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.8.0%2Bxpu-cp310-cp310-win_amd64.whl#sha256=125c60cd59d51b39581a7e9afcd4679bc3a6b8c1f9440b1bb502a23fdd60571e ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.8.0%2Bxpu-cp311-cp311-win_amd64.whl#sha256=47f1a57258cd460e80b38b2ed6744e31587ab77a96b4215bf59546cb4bab5cc0 ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.8.0%2Bxpu-cp312-cp312-win_amd64.whl#sha256=0937d8943c145a83d9bafc6f80ef28971167817f9eda26066d33f72caf8a6646 ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.8.0%2Bxpu-cp313-cp313-win_amd64.whl#sha256=e034aab1d71760dc80a731531be43673ffe15e99033b82d24e40d2e6d41bd8bf ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "bitsandbytes @ https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-manylinux_2_24_x86_64.whl ; ('linux' in sys_platform) and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "bitsandbytes @ https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.23.0%2Bxpu-cp39-cp39-manylinux_2_28_x86_64.whl#sha256=6e981c192045fc249c008441179ff237bb00174d818b875b0475730b63f0eaca ; 'linux' in sys_platform and python_version == '3.9' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.23.0%2Bxpu-cp310-cp310-manylinux_2_28_x86_64.whl#sha256=e5ba4805969277175ebfd59cc717093528cc6e3ada89ac2725fc7a3c1fee6169 ; 'linux' in sys_platform and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.23.0%2Bxpu-cp311-cp311-manylinux_2_28_x86_64.whl#sha256=74c39c144104416bc4c5ad8c26ab0c169dc5cc6be58059e01bc3665dd0ef676f ; 'linux' in sys_platform and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.23.0%2Bxpu-cp312-cp312-manylinux_2_28_x86_64.whl#sha256=0acec355b80c3899841184084f365df336c508602812e34a44007b8b60d53af4 ; 'linux' in sys_platform and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.23.0%2Bxpu-cp313-cp313-manylinux_2_28_x86_64.whl#sha256=e2109ae773dad27b98ca17681044b4f876563c37f2382b75de3a371399edcff8 ; 'linux' in sys_platform and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.23.0%2Bxpu-cp39-cp39-win_amd64.whl#sha256=5f7904e7048d414379bc8c1167260f1e84204f105db2d0a2f9c89e87ce1cf205 ; sys_platform == 'win32' and python_version == '3.9' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.23.0%2Bxpu-cp310-cp310-win_amd64.whl#sha256=005fca5e658ca8e37adb63c1a021c84f5e56dfa6cf0d601d89cfe40b9473f79f ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.23.0%2Bxpu-cp311-cp311-win_amd64.whl#sha256=c6d030f5361461550c0ff1339b5bca8585fc1e84fda2e64b6184e65a581e4f98 ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.23.0%2Bxpu-cp312-cp312-win_amd64.whl#sha256=91aafd61864cdce27461cbec13ddbf28c1bc6494265a1e4b80131c64a3b7d18f ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.23.0%2Bxpu-cp313-cp313-win_amd64.whl#sha256=71dc4a6421742ed1e7f585b04a100ad53615c341fbccfbc255aefb38ea9091da ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-]
-intel-gpu-torch280 = [
- "unsloth[intelgputorch280]"
-]
-intelgputorch290 = [
- "unsloth_zoo[intelgpu]",
- "unsloth[huggingfacenotorch]",
-
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.5.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=c169a1de14c19673b17c751290d467fa282fc90fa5da4314b2e5cdab1f553146 ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.5.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=013d9dd5d6479bd22983161f462e61c8dbe1d82e6730624a7a8d5945507eaa61 ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=afc8cabfbf7ed51fd278d1e0f88d6afc157b0201bad4b99d681e4d542f9e66d4 ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=0d24c1716088f2764d0d24c64227732195b6a42706c3c5fc89eeb4904bfa0818 ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.5.0-cp310-cp310-win_amd64.whl#sha256=c83ab007311d9cfb6e809ee5a4587d99a9eef4be720b90da4f1aaa68b45139a0 ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.5.0-cp311-cp311-win_amd64.whl#sha256=debf75348da8e8c7166b4d4a9b91d1508bb8d6581e339f79f7604b2e6746bacd ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.5.0-cp312-cp312-win_amd64.whl#sha256=97337a47425f1963a723475bd61037460e84ba01db4f87a1d662c3718ff6c47e ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.5.0-cp313-cp313-win_amd64.whl#sha256=2caf8138695f6abb023ecd02031a2611ba1bf8fff2f19802567cb2fadefe9e87 ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.9.0%2Bxpu-cp310-cp310-linux_x86_64.whl#sha256=5afbe860ce991825a36b75706a523601087e414b77598ef0d9d3d565741c277d ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.9.0%2Bxpu-cp311-cp311-linux_x86_64.whl#sha256=607fe419c32d6e8e0556f745742e7cff1d0babce51f54be890e0c1422359c442 ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.9.0%2Bxpu-cp312-cp312-linux_x86_64.whl#sha256=376bae584d89980b8e59934d248c38d5fa3b7d4687a4df1a19f4bc1d23dcc8c1 ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.9.0%2Bxpu-cp313-cp313-linux_x86_64.whl#sha256=98d6a06dd7fb185874367b18bd609f05f16fdce4142a5980ca94461949965cd2 ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.9.0%2Bxpu-cp310-cp310-win_amd64.whl#sha256=47cc68f631f65bd9c84924d052cd04dec7531023caa85e80345e9c94611c887d ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.9.0%2Bxpu-cp311-cp311-win_amd64.whl#sha256=d56c44ab4818aba57e5c7b628f422d014e0d507427170a771c5be85e308b0bc6 ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.9.0%2Bxpu-cp312-cp312-win_amd64.whl#sha256=18cad93aaff76a01ce73aef6935ece7cfc03344b905592ec731446c44d44592b ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.9.0%2Bxpu-cp313-cp313-win_amd64.whl#sha256=579929cdc10a76800ead41289cac191ea36d1b16f5f501d3fc25607d4375cd83 ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "bitsandbytes @ https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-manylinux_2_24_x86_64.whl ; ('linux' in sys_platform) and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "bitsandbytes @ https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.24.0%2Bxpu-cp310-cp310-manylinux_2_28_x86_64.whl#sha256=cbfae2b79b7549fd368c2462fc8e94f8f26cc450782ee72138e908077c09a519 ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.24.0%2Bxpu-cp311-cp311-manylinux_2_28_x86_64.whl#sha256=044fa36ef4b6b43edcd490b75c853fa4b3eb033c2bded29f8fbcf27734713c67 ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.24.0%2Bxpu-cp312-cp312-manylinux_2_28_x86_64.whl#sha256=4b91e4bec1d740a6211f02578a79888550b73f3a4e1383035f8f6d72f587212c ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.24.0%2Bxpu-cp313-cp313-manylinux_2_28_x86_64.whl#sha256=88239e73ca37254bec84f29cd5887e10ff712de7edbbda3fbb3609cd6190d99e ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.24.0%2Bxpu-cp310-cp310-win_amd64.whl#sha256=19c7da8ca767d593e13a88a12bb08d06e34a673f6f26c2f9c191d60e81c02953 ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.24.0%2Bxpu-cp311-cp311-win_amd64.whl#sha256=9bb0d1421c544ac8e2eca5b47daacaf54706dc9139c003aa5e77ee5f355c5931 ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.24.0%2Bxpu-cp312-cp312-win_amd64.whl#sha256=6a5194bc736089606342d48a3f6822829b167617e9495d91d753dd1bd46fda18 ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.24.0%2Bxpu-cp313-cp313-win_amd64.whl#sha256=da47a3ce2bb7f0301a31124668b5908f9b9e92d6241443de15a310ef9632fd83 ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-]
-intel-gpu-torch290 = [
- "unsloth[intelgputorch290]"
-]
-intelgputorch271 = [
- "unsloth_zoo[intelgpu]",
- "unsloth[huggingfacenotorch]",
-
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.3.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=663ce21364096b268c6687f26f22862cb1001cae0c4ec9f98a0998415f99e2b0 ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.3.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=dd92cc17000bad19f213b6a877d7f10cd71341b703cd188513ce9fff8d42e3dd ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.3.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=aa5c3ec21a89e967d1dfe61e3d5b1c1ae9620c871ed804771d3378d6a44066f2 ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=d1c6f522e11112a311b1a61ba7b40b43ad8305675fa29153017ccb1ad0b6816d ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.3.1-cp310-cp310-win_amd64.whl#sha256=a5c16dcf449a9cb62bc3788f7ec45782bb3ead6edc2637a12b60ef0f8f45dc55 ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.3.1-cp311-cp311-win_amd64.whl#sha256=bc2d76ffa4ceed5b38ae34b52dbff643442e1a44d52ca72d7cb520ca1950e9ae ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.3.1-cp312-cp312-win_amd64.whl#sha256=b09ca59ce52d6d27b1510df783cde222b703a71857a6fa953f1f155f9f50811a ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.3.1-cp313-cp313-win_amd64.whl#sha256=1260c4a4bad426b6cd3c8f3e1a21835381c6f217bf434bcb55fedec08a206dea ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.7.1%2Bxpu-cp310-cp310-linux_x86_64.whl#sha256=231c3fbd88a75d94de5ccbbb7f4f9a96cb3c58b3d891c2a1b469d38df95f9be6 ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.7.1%2Bxpu-cp311-cp311-linux_x86_64.whl#sha256=78edcc27709dd819fc820f5eb9421bd10d3f3dcb14adb25ee60766c76f0e67f3 ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.7.1%2Bxpu-cp312-cp312-linux_x86_64.whl#sha256=b443df40bc9cb7d648a9f8f9ed1d5c3a1203e561ebd0a61dd55fb8a58833d5ec ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.7.1%2Bxpu-cp313-cp313-linux_x86_64.whl#sha256=412b58ffcceebea399c9a1bcdb22896aa10385c2650a8c4f8a677fb11c49b448 ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.7.1%2Bxpu-cp310-cp310-win_amd64.whl#sha256=2591228dc2cb73c78daf24277c4449ba9474f94cd31938147249269fe89d05d6 ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.7.1%2Bxpu-cp311-cp311-win_amd64.whl#sha256=1aacb86e9a9684ffc8bde3db14b251d00df7019a9a434ec99a59076a2696325d ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.7.1%2Bxpu-cp312-cp312-win_amd64.whl#sha256=9b65dc8562521b60d77aa653132bc03a19da0291318fcf919faa3f03080d8f7e ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.7.1%2Bxpu-cp313-cp313-win_amd64.whl#sha256=cd3669fee311bc3ee5501d696bf989226a6f2bf957d120a04881a07af05526d6 ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "bitsandbytes @ https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-manylinux_2_24_x86_64.whl ; ('linux' in sys_platform) and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "bitsandbytes @ https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.22.1%2Bxpu-cp310-cp310-manylinux_2_28_x86_64.whl#sha256=f8cdf6889c02b3166679eef661b68757ea7e99c314432c3d41dac3d2ed4a59d4 ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.22.1%2Bxpu-cp311-cp311-manylinux_2_28_x86_64.whl#sha256=f7d15b65d52809745992e0001c25034f33ac01f2dff5248614e07b5d009a59b7 ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.22.1%2Bxpu-cp312-cp312-manylinux_2_28_x86_64.whl#sha256=1ff1f98d70846352c7f56833bedab1a055ead27b11c120b8c719063ee0383554 ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.22.1%2Bxpu-cp313-cp313-manylinux_2_28_x86_64.whl#sha256=f46945344ea911a70309231eaaf3b80c96f6646ce5515dc89aa94f94144e310e ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.22.1%2Bxpu-cp310-cp310-win_amd64.whl#sha256=ecae9a02de769e2070d37388116beb407c3f0d60b8e65c1da1423f4eafee361a ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.22.1%2Bxpu-cp311-cp311-win_amd64.whl#sha256=2914e62782431bebd6ad9a3b98a2b7311e448e84a7534bb7f35874b9279a17de ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.22.1%2Bxpu-cp312-cp312-win_amd64.whl#sha256=5b462c156f4e2097e1e53649d3f298ce352fa4c5d1e6addd360375b10ebd6c67 ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.22.1%2Bxpu-cp313-cp313-win_amd64.whl#sha256=fa87b3677cd1af67ce423004283c1bde80e3571f391182a3e89b485e18e3c70f ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-]
-intel-gpu-torch271 = [
- "unsloth[intelgputorch271]"
-]
-intelgputorch291 = [
- "unsloth_zoo[intelgpu]",
- "unsloth[huggingfacenotorch]",
-
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.5.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=c169a1de14c19673b17c751290d467fa282fc90fa5da4314b2e5cdab1f553146 ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.5.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=013d9dd5d6479bd22983161f462e61c8dbe1d82e6730624a7a8d5945507eaa61 ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=afc8cabfbf7ed51fd278d1e0f88d6afc157b0201bad4b99d681e4d542f9e66d4 ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=0d24c1716088f2764d0d24c64227732195b6a42706c3c5fc89eeb4904bfa0818 ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.5.0-cp310-cp310-win_amd64.whl#sha256=c83ab007311d9cfb6e809ee5a4587d99a9eef4be720b90da4f1aaa68b45139a0 ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.5.0-cp311-cp311-win_amd64.whl#sha256=debf75348da8e8c7166b4d4a9b91d1508bb8d6581e339f79f7604b2e6746bacd ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.5.0-cp312-cp312-win_amd64.whl#sha256=97337a47425f1963a723475bd61037460e84ba01db4f87a1d662c3718ff6c47e ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "pytorch_triton_xpu @ https://download.pytorch.org/whl/pytorch_triton_xpu-3.5.0-cp313-cp313-win_amd64.whl#sha256=2caf8138695f6abb023ecd02031a2611ba1bf8fff2f19802567cb2fadefe9e87 ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.9.1%2Bxpu-cp310-cp310-linux_x86_64.whl#sha256=fb7895c744132d6a8e56ce8434ae1d8355c9bda4e9f58832744ff742d6268eaf ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.9.1%2Bxpu-cp311-cp311-linux_x86_64.whl#sha256=da2604a9114a28de71ce654819424d20a246adf644d191ae160837df9731b79e ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.9.1%2Bxpu-cp312-cp312-linux_x86_64.whl#sha256=d5968d78d81c1d01efc1b3bf83d7da3d83161dcc3a9fcf91f500591db1c6c75d ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.9.1%2Bxpu-cp313-cp313-linux_x86_64.whl#sha256=b56d6b0d65863f370527e971dbfa046a5dd2a1f61cc95071db26c764f36e4dce ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.9.1%2Bxpu-cp310-cp310-win_amd64.whl#sha256=2f318fb6a4bf1101cc17f35a5371f7c1768b41fceed03628397834e85b3edfdd ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.9.1%2Bxpu-cp311-cp311-win_amd64.whl#sha256=c9cedc3fb099366b2e6c563df6578e323564b1b5d40ac27be73c674755343a1d ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.9.1%2Bxpu-cp312-cp312-win_amd64.whl#sha256=bee9623254d0f95a1ca115dbd17e9a9d966fdb8ae123e2ada4a9eb2fb8d38db8 ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.9.1%2Bxpu-cp313-cp313-win_amd64.whl#sha256=cd5c857da52a63c121561b30b0979e69ade70b575fd74e389787bc7c1ee2ac11 ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "bitsandbytes @ https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-manylinux_2_24_x86_64.whl ; ('linux' in sys_platform) and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "bitsandbytes @ https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.24.1%2Bxpu-cp310-cp310-manylinux_2_28_x86_64.whl#sha256=cc5272da2cb4554edf059eedd6d1f5ef2859033b0fb79d5dcb8e99a0697f3325 ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.24.1%2Bxpu-cp311-cp311-manylinux_2_28_x86_64.whl#sha256=3c80d6a068c32fc4ebddb27953e03a0141bd0f10ca8730417cbc0e0748158285 ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.24.1%2Bxpu-cp312-cp312-manylinux_2_28_x86_64.whl#sha256=8cf640a867cf270b3fda7a10002c29d3fc2ad6dfbd76404a8cdd820489adb04c ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.24.1%2Bxpu-cp313-cp313-manylinux_2_28_x86_64.whl#sha256=d9c59ee5ae3d0560f02401c8dfd8054d50813a8dbb5d33a8777de7d02f6fcb7b ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.24.1%2Bxpu-cp310-cp310-win_amd64.whl#sha256=843ea7fcd8f5a22ebbc20d2d61d9eec7593821a0372eb8cabb73953d12ef6acf ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.24.1%2Bxpu-cp311-cp311-win_amd64.whl#sha256=e5ff8a31d3c700f8dbac59697c8e32298a43ec059609ebc6ea7bab3eff6384e1 ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.24.1%2Bxpu-cp312-cp312-win_amd64.whl#sha256=8bae6d4c042f8d20818da4a5aa9109c6fbd6ec11bc422be152ce8adf9a7095bf ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.24.1%2Bxpu-cp313-cp313-win_amd64.whl#sha256=47059e290fc2a41ba78666ffcde102c436abf7ff8a34d200268b48c4fa0f9c45 ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-]
-intel-gpu-torch291 = [
- "unsloth[intelgputorch291]"
-]
-intelgputorch210 = [
- "unsloth_zoo[intelgpu]",
- "unsloth[huggingfacenotorch]",
-
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.6.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.6.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.6.0-cp310-cp310-win_amd64.whl ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.6.0-cp311-cp311-win_amd64.whl ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.6.0-cp312-cp312-win_amd64.whl ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.6.0-cp313-cp313-win_amd64.whl ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.10.0%2Bxpu-cp310-cp310-linux_x86_64.whl#sha256=abb1d1ec1ac672bac0ff35420c965f2df0c636ef9d94e2a830e34578489d0a57 ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.10.0%2Bxpu-cp311-cp311-linux_x86_64.whl#sha256=71ad2f82da0f41eaec159f39fc85854e27c2391efa91b373e550648a6f4aaad3 ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.10.0%2Bxpu-cp312-cp312-linux_x86_64.whl#sha256=b473571d478912f92881cc13f15fa18f8463fb0fb8a068c96ed47a7d45a4da0a ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.10.0%2Bxpu-cp313-cp313-linux_x86_64.whl#sha256=3bc64a746ff25a93de140902c60c9e819d7413f5cea1e88d80999c27a5901e9c ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.10.0%2Bxpu-cp310-cp310-win_amd64.whl#sha256=ce50691ab3fb6301d9b7bb8b3834cf5fa7152a2b5f91fd24c5efdc601a25b780 ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.10.0%2Bxpu-cp311-cp311-win_amd64.whl#sha256=cb9d37f21cb9fb7df67d62863f021c3144e8d8832b9ea8e8523ac308bc620ea1 ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.10.0%2Bxpu-cp312-cp312-win_amd64.whl#sha256=3ad605be4728b6d3a28a44d07dd794b1a9e45551b0057815bf25eb2a6d6a56a7 ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.10.0%2Bxpu-cp313-cp313-win_amd64.whl#sha256=2b4b56dd6c792aef82006904fa888692e3782e4ae5da27526801bad4898f05a5 ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "bitsandbytes @ https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-manylinux_2_24_x86_64.whl ; ('linux' in sys_platform) and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "bitsandbytes @ https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.25.0%2Bxpu-cp310-cp310-manylinux_2_28_x86_64.whl#sha256=7e1e7b170fcf7161c8499b67156c5a05462243626dc0974010791a0bab4378d3 ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.25.0%2Bxpu-cp311-cp311-manylinux_2_28_x86_64.whl#sha256=bd6add201bd7628af70437292e1447abb368e0b5f4ff9abd334ae435efd44792 ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.25.0%2Bxpu-cp312-cp312-manylinux_2_28_x86_64.whl#sha256=6ad2543496bc29e59d3dd614a94d09aa9870318aedb66045344fffddfedd2cf8 ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.25.0%2Bxpu-cp313-cp313-manylinux_2_28_x86_64.whl#sha256=80269f37865fcd8b57f20e4786efae2200bfa2b2727926c3c7acc82f0e7d3548 ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.25.0%2Bxpu-cp310-cp310-win_amd64.whl#sha256=6b9485ba85dcba4d196d6134d9c3332fb228fb2556416bf0450a64e8a472fcba ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.25.0%2Bxpu-cp311-cp311-win_amd64.whl#sha256=36cbaedf10f6412af5c89afd9aeea474e6a56a0050348ada8fabe1ecaf6b879e ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.25.0%2Bxpu-cp312-cp312-win_amd64.whl#sha256=738357d97468d75fe3d510ac37e65130f2787f81d9bbc1518898f7396dc3403f ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.25.0%2Bxpu-cp313-cp313-win_amd64.whl#sha256=1c4b44b36a557f7381e3076fb8843366742238648441d607c8d049c6da0f8886 ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-]
-intel-gpu-torch210 = [
- "unsloth[intelgputorch210]"
-]
-intelgputorch2110 = [
- "unsloth_zoo[intelgpu]",
- "unsloth[huggingfacenotorch]",
-
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.7.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=2a1841138750f708ec017becbf8d357526f3fa350deee6553be5735ad66160a3 ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.7.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=e85378f1fc1ea002271de2a35475b75008fa554b86ef9d3bc55be9c513a63b51 ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.7.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=a6663ebe43e3c0d560ff774708632d7a75208ee64a291c1724ed5c16a92d1c72 ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.7.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=08c8d43b2831faf9d6799480df2b45dde58102257aebd810d07a2ce18cd4e5df ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.7.0-cp310-cp310-win_amd64.whl#sha256=90fb8f767950a4ffca627faa7f86d9c697237ea4352d7e23505c5c9ed8e72216 ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.7.0-cp311-cp311-win_amd64.whl#sha256=aa7de82f4265089e74f25a2701b7532e5c47d74224d877b61da1d66156e3f0c1 ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.7.0-cp312-cp312-win_amd64.whl#sha256=5ba3a31c6e1b259ad2d924e1b50f72a78c6ebd7eb4f364473bbf93e144734e80 ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.7.0-cp313-cp313-win_amd64.whl#sha256=e8b4caba9b2399ea4c7f9a2777042564dea5d6f9e586a2dcb015a4ce20f000f7 ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.11.0%2Bxpu-cp310-cp310-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.11.0%2Bxpu-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.11.0%2Bxpu-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.11.0%2Bxpu-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.11.0%2Bxpu-cp310-cp310-win_amd64.whl ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.11.0%2Bxpu-cp311-cp311-win_amd64.whl ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.11.0%2Bxpu-cp312-cp312-win_amd64.whl ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.11.0%2Bxpu-cp313-cp313-win_amd64.whl ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "bitsandbytes @ https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-manylinux_2_24_x86_64.whl ; ('linux' in sys_platform) and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "bitsandbytes @ https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.26.0%2Bxpu-cp310-cp310-manylinux_2_28_x86_64.whl#sha256=6e634354b752b7366e8ad16b84f3e7e5863776a7ab448bbabae4fd36668dee7a ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.26.0%2Bxpu-cp311-cp311-manylinux_2_28_x86_64.whl#sha256=293169899f562ce473a58836dd024f0b1e72a347400278287ab393d1b04991e4 ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.26.0%2Bxpu-cp312-cp312-manylinux_2_28_x86_64.whl#sha256=e204d14be6f0f84d5f0e6e9213556e80326c3ab682cac108bcbef340bf45297b ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.26.0%2Bxpu-cp313-cp313-manylinux_2_28_x86_64.whl#sha256=f134344006f0989a2d771554b7905fb05bd93d63b195e64626fde3495ec6f287 ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.26.0%2Bxpu-cp310-cp310-win_amd64.whl#sha256=7e52729cb9736c66dc79a7f42de6b31db93b9161d3357fd34cfa33f5fe32b8ea ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.26.0%2Bxpu-cp311-cp311-win_amd64.whl#sha256=83a6130100c6b6750d8aa9fd29e5d0c53b1c85b1153b8ed4139aea54fc1892cc ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.26.0%2Bxpu-cp312-cp312-win_amd64.whl#sha256=03788e0e5a5b85a2f09d11f0263d579fcb0cf5623d8810149be0e37836c2738c ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.26.0%2Bxpu-cp313-cp313-win_amd64.whl#sha256=cb1da1d378ce440f7d1e0ed8cf21bd280d904ab25a55c9453f8377825818df74 ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-]
-intel-gpu-torch2110 = [
- "unsloth[intelgputorch2110]"
-]
-intelgputorch2120 = [
- "unsloth_zoo[intelgpu]",
- "unsloth[huggingfacenotorch]",
-
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.7.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=844d981cb1b3948085e8cfa62c74de9f100259f6131959aa70be49123b88ae81 ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.7.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=a16b1d00e94ad87d62af3512e390348b8656419598004100c56028bf494f086b ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.7.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=4e46e71e077cf483404a4c17ce40d71c5f0e13a81459139d4346ca427b1dd455 ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.7.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl#sha256=4fdaed1bafc51d3a2834656a3420a6686a74ea226508765a49bf15d58ff3a930 ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.7.1-cp310-cp310-win_amd64.whl#sha256=2778b46b22e9fa0916398db299a125027a1b2331c1173b3dd2b9e2cab6263a31 ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.7.1-cp311-cp311-win_amd64.whl#sha256=ad5b147d04ee0d40f3d4d32f85f5aa3a3beb6cd5799ca026d3d7f4afa3d9e24f ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.7.1-cp312-cp312-win_amd64.whl#sha256=d9482063af2a308543f23333e32edd738ea87cbb33ade68afda9ae0fd704ccd9 ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "triton-xpu @ https://download.pytorch.org/whl/triton_xpu-3.7.1-cp313-cp313-win_amd64.whl#sha256=5d4d67f0deb1e851c01b293e602b8dcddad26ca2be61221cee3dc0e1aa0cdefd ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.12.0%2Bxpu-cp310-cp310-linux_x86_64.whl#sha256=e8923cd1fe560472904b1461b745d2f1826bb9c1bc0808225d5f28a450e4d553 ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.12.0%2Bxpu-cp311-cp311-linux_x86_64.whl#sha256=f7c082b2fc9b61def594d30ea57762dc4a8bc7111a9a9593953ed948de242e28 ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.12.0%2Bxpu-cp312-cp312-linux_x86_64.whl#sha256=f59decc04bec27862ed0197554a52370dbcba3e6892616d1fbce450e402bf2d5 ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.12.0%2Bxpu-cp313-cp313-linux_x86_64.whl#sha256=56f74e7c6c096e1a7ac215eb79ee590b764be3fbba8f4febc145bca47194a083 ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.12.0%2Bxpu-cp310-cp310-win_amd64.whl#sha256=b9779b71457b5a916ae052ed2467c10273cae4862d469b191359173b2038c53e ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.12.0%2Bxpu-cp311-cp311-win_amd64.whl#sha256=7ef8e776c992e4e3ae007ebc108eb4f36b1d1dd9da97ecb308ab7fded89a2659 ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.12.0%2Bxpu-cp312-cp312-win_amd64.whl#sha256=7f1d40febf2b8724adf4ff23866897d87478cc43de2a20f7776dc00be334c464 ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torch @ https://download.pytorch.org/whl/xpu/torch-2.12.0%2Bxpu-cp313-cp313-win_amd64.whl#sha256=32770e2613df26e2c81ae64ea001b2ca12b8d152231285caff9b5f963a21ad75 ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "bitsandbytes @ https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-manylinux_2_24_x86_64.whl ; ('linux' in sys_platform) and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "bitsandbytes @ https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-win_amd64.whl ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.27.0%2Bxpu-cp310-cp310-manylinux_2_28_x86_64.whl#sha256=0d517462caf6f5201c0d7c880f4ac431783c88fcc59b4587836da6c72a89509c ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.27.0%2Bxpu-cp311-cp311-manylinux_2_28_x86_64.whl#sha256=4b6feada86aa0bd606904b05898b33538106120d8ed706ba11d0011046534cb8 ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.27.0%2Bxpu-cp312-cp312-manylinux_2_28_x86_64.whl#sha256=e231819be0f87829c2344c909c1f0db9d6ae7d6faefe644a526a1a01d0c18d98 ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.27.0%2Bxpu-cp313-cp313-manylinux_2_28_x86_64.whl#sha256=8bc7d37515cea18af4c389d5fde58b1a9d76b015f2d87e4a7dc62ad50b1cc200 ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.27.0%2Bxpu-cp310-cp310-win_amd64.whl#sha256=65dbb041057dddfe369f29cfaab63f75563621779a23a7b1e2c0ff8a84d4376a ; sys_platform == 'win32' and python_version == '3.10' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.27.0%2Bxpu-cp311-cp311-win_amd64.whl#sha256=df647445365924d69fe3bb2a15a7edfe5b63ef91e4ae69af11d93582985237a4 ; sys_platform == 'win32' and python_version == '3.11' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.27.0%2Bxpu-cp312-cp312-win_amd64.whl#sha256=b0db3df0d0d154d18ba988ab420f1da2549f9372113ff54ff66e4ae3c7fe3bd0 ; sys_platform == 'win32' and python_version == '3.12' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
- "torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.27.0%2Bxpu-cp313-cp313-win_amd64.whl#sha256=c70850842068c43a0d50eaf139c25b6f6cc9b17a0dae70218c7e69edbee0bc80 ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-]
-intel-gpu-torch2120 = [
- "unsloth[intelgputorch2120]"
-]
-intel = [
- "unsloth[intelgputorch280]",
-]
-amd = [
- "unsloth[huggingfacenotorch]",
- "bitsandbytes>=0.49.1 ; ('linux' in sys_platform) and (platform_machine == 'AMD64' or platform_machine == 'x86_64' or platform_machine == 'aarch64')",
- "bitsandbytes>=0.49.1 ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
-]
-rocm702-torch280 = [
- "unsloth[amd]",
-
- "triton @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.0.2/triton-3.4.0%2Brocm7.0.2.gitf9e5bf54-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "triton @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.0.2/triton-3.4.0%2Brocm7.0.2.gitf9e5bf54-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "triton @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.0.2/triton-3.4.0%2Brocm7.0.2.gitf9e5bf54-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
-
- "torch @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.0.2/torch-2.8.0%2Brocm7.0.2.lw.git245bf6ed-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torch @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.0.2/torch-2.8.0%2Brocm7.0.2.lw.git245bf6ed-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torch @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.0.2/torch-2.8.0%2Brocm7.0.2.lw.git245bf6ed-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
-
- "torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.0.2/torchvision-0.23.0%2Brocm7.0.2.git824e8c87-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.0.2/torchvision-0.23.0%2Brocm7.0.2.git824e8c87-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.0.2/torchvision-0.23.0%2Brocm7.0.2.git824e8c87-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
-]
-rocm72-torch291 = [
- "unsloth[amd]",
-
- "triton @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/triton-3.5.1%2Brocm7.2.0.gita272dfa8-cp310-cp310-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "triton @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/triton-3.5.1%2Brocm7.2.0.gita272dfa8-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "triton @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/triton-3.5.1%2Brocm7.2.0.gita272dfa8-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "triton @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/triton-3.5.1%2Brocm7.2.0.gita272dfa8-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
-
- "torch @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torch-2.9.1%2Brocm7.2.0.lw.git7e1940d4-cp310-cp310-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "torch @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torch-2.9.1%2Brocm7.2.0.lw.git7e1940d4-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torch @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torch-2.9.1%2Brocm7.2.0.lw.git7e1940d4-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torch @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torch-2.9.1%2Brocm7.2.0.lw.git7e1940d4-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
- "torch @ https://repo.radeon.com/rocm/windows/rocm-rel-7.2/torch-2.9.1%2Brocmsdk20260116-cp312-cp312-win_amd64.whl ; sys_platform == 'win32' and python_version == '3.12'",
-
- "torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchvision-0.24.0%2Brocm7.2.0.gitb919bd0c-cp310-cp310-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchvision-0.24.0%2Brocm7.2.0.gitb919bd0c-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchvision-0.24.0%2Brocm7.2.0.gitb919bd0c-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchvision-0.24.0%2Brocm7.2.0.gitb919bd0c-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
- "torchvision @ https://repo.radeon.com/rocm/windows/rocm-rel-7.2/torchvision-0.24.1%2Brocmsdk20260116-cp312-cp312-win_amd64.whl ; sys_platform == 'win32' and python_version == '3.12'",
-]
-rocm711-torch291 = [
- "unsloth[amd]",
-
- "triton @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/triton-3.5.1%2Brocm7.1.1.gita272dfa8-cp310-cp310-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "triton @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/triton-3.5.1%2Brocm7.1.1.gita272dfa8-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "triton @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/triton-3.5.1%2Brocm7.1.1.gita272dfa8-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "triton @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/triton-3.5.1%2Brocm7.1.1.gita272dfa8-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
-
- "torch @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torch-2.9.1%2Brocm7.1.1.lw.git351ff442-cp310-cp310-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "torch @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torch-2.9.1%2Brocm7.1.1.lw.git351ff442-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torch @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torch-2.9.1%2Brocm7.1.1.lw.git351ff442-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torch @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torch-2.9.1%2Brocm7.1.1.lw.git351ff442-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
-
- "torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torchvision-0.24.0%2Brocm7.1.1.gitb919bd0c-cp310-cp310-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torchvision-0.24.0%2Brocm7.1.1.gitb919bd0c-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torchvision-0.24.0%2Brocm7.1.1.gitb919bd0c-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torchvision-0.24.0%2Brocm7.1.1.gitb919bd0c-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
-]
-rocm72-torch2100 = [
- "unsloth[amd]",
-
- "triton @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/triton-3.6.0%2Brocm7.2.0.gitba5c1517-cp310-cp310-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "triton @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/triton-3.6.0%2Brocm7.2.0.gitba5c1517-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "triton @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/triton-3.6.0%2Brocm7.2.0.gitba5c1517-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "triton @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/triton-3.6.0%2Brocm7.2.0.gitba5c1517-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
-
- "torch @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torch-2.10.0%2Brocm7.2.0.lw.gitb6ee5fde-cp310-cp310-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "torch @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torch-2.10.0%2Brocm7.2.0.lw.gitb6ee5fde-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torch @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torch-2.10.0%2Brocm7.2.0.lw.gitb6ee5fde-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torch @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torch-2.10.0%2Brocm7.2.0.lw.gitb6ee5fde-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
-
- "torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchvision-0.25.0%2Brocm7.2.0.git82df5f59-cp310-cp310-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchvision-0.25.0%2Brocm7.2.0.git82df5f59-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchvision-0.25.0%2Brocm7.2.0.git82df5f59-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchvision-0.25.0%2Brocm7.2.0.git82df5f59-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
-]
-rocm711-torch2100 = [
- "unsloth[amd]",
-
- "triton @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/triton-3.6.0%2Brocm7.1.1.gitba5c1517-cp310-cp310-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "triton @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/triton-3.6.0%2Brocm7.1.1.gitba5c1517-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "triton @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/triton-3.6.0%2Brocm7.1.1.gitba5c1517-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "triton @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/triton-3.6.0%2Brocm7.1.1.gitba5c1517-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
-
- "torch @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torch-2.10.0%2Brocm7.1.1.lw.gitd9556b05-cp310-cp310-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "torch @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torch-2.10.0%2Brocm7.1.1.lw.gitd9556b05-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torch @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torch-2.10.0%2Brocm7.1.1.lw.gitd9556b05-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torch @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torch-2.10.0%2Brocm7.1.1.lw.gitd9556b05-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
-
- "torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torchvision-0.25.0%2Brocm7.1.1.git82df5f59-cp310-cp310-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.10' and platform_machine == 'x86_64'",
- "torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torchvision-0.25.0%2Brocm7.1.1.git82df5f59-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
- "torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torchvision-0.25.0%2Brocm7.1.1.git82df5f59-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
- "torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torchvision-0.25.0%2Brocm7.1.1.git82df5f59-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
-]
[project.urls]
homepage = "https://unsloth.ai"
@@ -1310,7 +756,6 @@ repository = "https://github.com/unslothai/unsloth"
[tool.ruff]
target-version = "py311"
-line-length = 100
force-exclude = true
extend-exclude = [
"*chat_templates.py",
@@ -1343,4 +788,3 @@ ignore = [
# does NOT pick up the GPU-heavy tests under tests/python, tests/qlora,
# etc. The CI security job runs `pytest tests/security` explicitly.
testpaths = ["tests/security"]
-pythonpath = ["."]
diff --git a/scripts/check_frontend_dep_removal.py b/scripts/check_frontend_dep_removal.py
index b95c4ca7f6..74d089220b 100644
--- a/scripts/check_frontend_dep_removal.py
+++ b/scripts/check_frontend_dep_removal.py
@@ -52,7 +52,9 @@ EXPECTED_NOISE_FILES = {
}
# File types where a quoted string can be a module specifier.
-JS_LIKE_EXT = re.compile(r"\.(ts|tsx|js|jsx|mjs|cjs|html|htm|css|scss|sass|json|jsonc)$")
+JS_LIKE_EXT = re.compile(
+ r"\.(ts|tsx|js|jsx|mjs|cjs|html|htm|css|scss|sass|json|jsonc)$"
+)
# Files where JS import patterns could be a real module reference (.mdx is
# real ESM; .md code fences are not).
SCRIPT_LIKE_EXT = re.compile(r"\.(ts|tsx|js|jsx|mjs|cjs|mdx)$")
@@ -249,7 +251,9 @@ def classify(pkg: str, file: str, content: str) -> str | None:
if is_script and re.search(rf"\bimport\(\s*['\"]{esc}{sub}['\"]\s*\)", content):
return "dynamic_import"
# require / require.resolve
- if is_script and re.search(rf"\brequire(?:\.resolve)?\(\s*['\"]{esc}{sub}['\"]\s*\)", content):
+ if is_script and re.search(
+ rf"\brequire(?:\.resolve)?\(\s*['\"]{esc}{sub}['\"]\s*\)", content
+ ):
return "require"
# Re-exports: `export * from`, `export { x } from`, `export type { Foo } from`.
if is_script and re.search(
@@ -261,12 +265,16 @@ def classify(pkg: str, file: str, content: str) -> str | None:
# HTML script / link. Match pkg as a complete path segment so
# `/node_modules/foo-extra/...` is not treated as usage of `foo`.
html_pkg = rf"{esc}(?:/[^'\"#?]*)?(?=['\"#?])"
- if is_html and re.search(rf"")
+ req = _build_request(
+ "127.0.0.1:8902", origin = "data:text/html,"
+ )
assert _is_same_origin_request(req) is False
diff --git a/studio/backend/tests/test_inference_model_validation.py b/studio/backend/tests/test_inference_model_validation.py
index 2427ad35fa..56cc6d215e 100644
--- a/studio/backend/tests/test_inference_model_validation.py
+++ b/studio/backend/tests/test_inference_model_validation.py
@@ -201,7 +201,10 @@ def test_walkback_skips_explicitly_consumed_tool_call_id():
{"role": "tool", "content": "second result"},
]
)
- assert [m.tool_call_id for m in req.messages if m.role == "tool"] == ["call_a", "call_b"]
+ assert [m.tool_call_id for m in req.messages if m.role == "tool"] == [
+ "call_a",
+ "call_b",
+ ]
def test_walkback_handles_malformed_function_string():
diff --git a/studio/backend/tests/test_inference_orchestrator_crash_message.py b/studio/backend/tests/test_inference_orchestrator_crash_message.py
index be1a673e62..b75e049280 100644
--- a/studio/backend/tests/test_inference_orchestrator_crash_message.py
+++ b/studio/backend/tests/test_inference_orchestrator_crash_message.py
@@ -25,7 +25,9 @@ def test_subprocess_crash_message_includes_signal_and_oom_hint():
msg = orchestrator._subprocess_crash_message("wait")
- assert msg.startswith("The inference worker stopped unexpectedly while loading the model.")
+ assert msg.startswith(
+ "The inference worker stopped unexpectedly while loading the model."
+ )
assert "memory pressure" in msg
assert "smaller model" in msg
assert "Details:" in msg
diff --git a/studio/backend/tests/test_install_resolve_prebuilt.py b/studio/backend/tests/test_install_resolve_prebuilt.py
index ede5629664..ca15b07391 100644
--- a/studio/backend/tests/test_install_resolve_prebuilt.py
+++ b/studio/backend/tests/test_install_resolve_prebuilt.py
@@ -60,24 +60,38 @@ def test_published_repo_for_host():
# CPU-only Linux (x64 and arm64) -> ggml-org upstream.
assert ilp.published_repo_for_host(_host(is_linux = True, is_x86_64 = True)) == UPSTREAM
assert (
- ilp.published_repo_for_host(_host(is_linux = True, is_arm64 = True, machine = "aarch64"))
+ ilp.published_repo_for_host(
+ _host(is_linux = True, is_arm64 = True, machine = "aarch64")
+ )
== UPSTREAM
)
# GPU Linux -> fork.
assert (
- ilp.published_repo_for_host(_host(is_linux = True, is_x86_64 = True, has_usable_nvidia = True))
+ ilp.published_repo_for_host(
+ _host(is_linux = True, is_x86_64 = True, has_usable_nvidia = True)
+ )
+ == FORK
+ )
+ assert (
+ ilp.published_repo_for_host(_host(is_linux = True, is_x86_64 = True, has_rocm = True))
== FORK
)
- assert ilp.published_repo_for_host(_host(is_linux = True, is_x86_64 = True, has_rocm = True)) == FORK
# CPU-only Windows -> ggml-org (setup.ps1: the fork ships no win-cpu bundle).
assert (
- ilp.published_repo_for_host(_host(system = "Windows", is_windows = True, is_x86_64 = True))
+ ilp.published_repo_for_host(
+ _host(system = "Windows", is_windows = True, is_x86_64 = True)
+ )
== UPSTREAM
)
# GPU Windows -> fork.
assert (
ilp.published_repo_for_host(
- _host(system = "Windows", is_windows = True, is_x86_64 = True, has_usable_nvidia = True)
+ _host(
+ system = "Windows",
+ is_windows = True,
+ is_x86_64 = True,
+ has_usable_nvidia = True,
+ )
)
== FORK
)
@@ -121,7 +135,13 @@ def _run_resolve(monkeypatch, capsys, plans_or_exc):
monkeypatch.setattr(
sys,
"argv",
- ["install_llama_prebuilt.py", "--resolve-prebuilt", "latest", "--output-format", "json"],
+ [
+ "install_llama_prebuilt.py",
+ "--resolve-prebuilt",
+ "latest",
+ "--output-format",
+ "json",
+ ],
)
rc = ilp.main()
assert rc == ilp.EXIT_SUCCESS
@@ -133,7 +153,9 @@ def test_resolve_prebuilt_available(monkeypatch, capsys):
release_tag = "b9585",
llama_tag = "b9585",
attempts = [
- SimpleNamespace(name = "llama-b9585-bin-macos-arm64.tar.gz", install_kind = "macos-arm64")
+ SimpleNamespace(
+ name = "llama-b9585-bin-macos-arm64.tar.gz", install_kind = "macos-arm64"
+ )
],
)
out = _run_resolve(monkeypatch, capsys, [plan])
@@ -153,7 +175,9 @@ def test_resolve_prebuilt_unavailable(monkeypatch, capsys):
def test_resolve_prebuilt_linux_amd_tooling_routes_to_fork(monkeypatch, capsys):
# CPU-probed Linux host but rocminfo on PATH: the dispatch must route to the
# fork so a HIP source build is not offered an upstream CPU prebuilt.
- monkeypatch.setattr(ilp, "detect_host", lambda: _host(is_linux = True, is_x86_64 = True))
+ monkeypatch.setattr(
+ ilp, "detect_host", lambda: _host(is_linux = True, is_x86_64 = True)
+ )
monkeypatch.setattr(ilp.shutil, "which", lambda tool: tool == "rocminfo")
seen = {}
@@ -165,7 +189,13 @@ def test_resolve_prebuilt_linux_amd_tooling_routes_to_fork(monkeypatch, capsys):
monkeypatch.setattr(
sys,
"argv",
- ["install_llama_prebuilt.py", "--resolve-prebuilt", "latest", "--output-format", "json"],
+ [
+ "install_llama_prebuilt.py",
+ "--resolve-prebuilt",
+ "latest",
+ "--output-format",
+ "json",
+ ],
)
assert ilp.main() == ilp.EXIT_SUCCESS
out = json.loads(capsys.readouterr().out.strip().splitlines()[-1])
diff --git a/studio/backend/tests/test_kv_cache_estimation.py b/studio/backend/tests/test_kv_cache_estimation.py
index cd834b345b..8b79495554 100644
--- a/studio/backend/tests/test_kv_cache_estimation.py
+++ b/studio/backend/tests/test_kv_cache_estimation.py
@@ -338,7 +338,8 @@ class TestArchSwaPatternDefaults:
assert kv_default > 0
assert kv_legacy > 0
assert kv_default < kv_legacy, (
- f"arch fallback should under-shoot legacy estimate: " f"{kv_default} >= {kv_legacy}"
+ f"arch fallback should under-shoot legacy estimate: "
+ f"{kv_default} >= {kv_legacy}"
)
def test_scalar_sliding_window_pattern_expanded(self):
@@ -421,9 +422,21 @@ class TestDynamicSwaResolver:
from core.inference.llama_cpp import _period_from_layer_types
# gemma3 (1 global/6), gpt-oss (alternating), gemma3n (1/5).
- assert _period_from_layer_types((["sliding_attention"] * 5 + ["full_attention"]) * 4) == 6
- assert _period_from_layer_types(["sliding_attention", "full_attention"] * 12) == 2
- assert _period_from_layer_types((["sliding_attention"] * 4 + ["full_attention"]) * 7) == 5
+ assert (
+ _period_from_layer_types(
+ (["sliding_attention"] * 5 + ["full_attention"]) * 4
+ )
+ == 6
+ )
+ assert (
+ _period_from_layer_types(["sliding_attention", "full_attention"] * 12) == 2
+ )
+ assert (
+ _period_from_layer_types(
+ (["sliding_attention"] * 4 + ["full_attention"]) * 7
+ )
+ == 5
+ )
def test_period_from_layer_types_returns_none_for_aperiodic(self):
from core.inference.llama_cpp import _period_from_layer_types
@@ -447,7 +460,9 @@ class TestDynamicSwaResolver:
== "google/gemma-3-1b-it"
)
assert (
- _hf_repo_from_url("https://huggingface.co/google/gemma-3-1b-it/blob/main/config.json")
+ _hf_repo_from_url(
+ "https://huggingface.co/google/gemma-3-1b-it/blob/main/config.json"
+ )
== "google/gemma-3-1b-it"
)
for bad in [
@@ -500,7 +515,9 @@ class TestDynamicSwaResolver:
b = _backend_from_gguf(
"newmodel",
_SWA_FIELDS,
- general = {"general.source.huggingface.repository": "vendor/newmodel-1b-instruct"},
+ general = {
+ "general.source.huggingface.repository": "vendor/newmodel-1b-instruct"
+ },
)
assert b._sliding_window_pattern == [(i + 1) % 4 != 0 for i in range(12)]
assert calls == ["vendor/newmodel-1b-instruct"]
@@ -547,7 +564,9 @@ class TestDynamicSwaResolver:
monkeypatch.setattr(lc, "_fetch_swa_entry_from_hf", lambda repo_id: None)
# Force failure into Tier 3; bypass Tier 2.5.
- monkeypatch.setattr(lc, "_resolve_swa_entry_from_transformers", lambda arch: None)
+ monkeypatch.setattr(
+ lc, "_resolve_swa_entry_from_transformers", lambda arch: None
+ )
b = _backend_from_gguf(
"newmodel",
_SWA_FIELDS,
@@ -593,14 +612,18 @@ class TestTransformersIntrospection:
class _FakeLazyMapping(dict):
def __getitem__(self, k):
- return _FakeBrokenConfig if k == "brokenarch" else super().__getitem__(k)
+ return (
+ _FakeBrokenConfig if k == "brokenarch" else super().__getitem__(k)
+ )
import sys, types as _types
fake_auto = _types.ModuleType("transformers.models.auto.configuration_auto")
fake_auto.CONFIG_MAPPING_NAMES = {"brokenarch": "FakeBroken"}
fake_auto.CONFIG_MAPPING = _FakeLazyMapping({"brokenarch": "FakeBroken"})
- monkeypatch.setitem(sys.modules, "transformers.models.auto.configuration_auto", fake_auto)
+ monkeypatch.setitem(
+ sys.modules, "transformers.models.auto.configuration_auto", fake_auto
+ )
assert lc._resolve_swa_entry_from_transformers("brokenarch") == 7
def test_returns_none_when_transformers_unavailable(self, monkeypatch):
@@ -628,7 +651,9 @@ class TestTransformersIntrospection:
from core.inference.llama_cpp import _resolve_swa_entry_from_transformers
assert _resolve_swa_entry_from_transformers("totally-fake-arch-xyz") is None
- def test_full_resolver_uses_transformers_before_hf_fetch(self, monkeypatch, tmp_path):
+ def test_full_resolver_uses_transformers_before_hf_fetch(
+ self, monkeypatch, tmp_path
+ ):
# Bootstrap empty: Tier 2.5 must answer before Tier 3 fires.
self._isolate_cache(monkeypatch, tmp_path)
from core.inference import llama_cpp as lc
@@ -1273,7 +1298,8 @@ class TestServerFlags:
"_kv_key_length": 256,
"_kv_value_length": 256,
"_sliding_window": 512,
- "_sliding_window_pattern": [True, True, True, True, True, False] * 4 + [True, True],
+ "_sliding_window_pattern": [True, True, True, True, True, False] * 4
+ + [True, True],
}
defaults.update(overrides)
b = LlamaCppBackend()
@@ -1329,7 +1355,9 @@ class TestServerFlags:
def test_swa_full_suppresses_checkpoint_term(self):
b = self._swa_backend()
with_cp = b._estimate_kv_cache_bytes(8192, "f16", ctx_checkpoints = 8)
- with_cp_full = b._estimate_kv_cache_bytes(8192, "f16", ctx_checkpoints = 8, swa_full = True)
+ with_cp_full = b._estimate_kv_cache_bytes(
+ 8192, "f16", ctx_checkpoints = 8, swa_full = True
+ )
no_cp_full = b._estimate_kv_cache_bytes(8192, "f16", swa_full = True)
# Checkpoints only matter when SWA layers don't already keep n_ctx.
assert with_cp_full == no_cp_full
@@ -1346,7 +1374,9 @@ class TestServerFlags:
for slots in (1, 2, 4, 8):
for unified in (True, False):
assert (
- b._estimate_kv_cache_bytes(4096, "f16", n_parallel = slots, kv_unified = unified)
+ b._estimate_kv_cache_bytes(
+ 4096, "f16", n_parallel = slots, kv_unified = unified
+ )
== baseline
)
@@ -1355,7 +1385,9 @@ class TestServerFlags:
baseline = b._estimate_kv_cache_bytes(4096, "f16")
for unified in (True, False):
assert (
- b._estimate_kv_cache_bytes(4096, "f16", n_parallel = 0, kv_unified = unified)
+ b._estimate_kv_cache_bytes(
+ 4096, "f16", n_parallel = 0, kv_unified = unified
+ )
== baseline
)
@@ -1369,7 +1401,9 @@ class TestServerFlags:
per_token_swa = 4 * (256 + 256) * 2 # k_swa/val_swa fall back
per_slot_swa_cells = min(ctx, 2 * swa) # not clamped at parallel=1
global_bytes = sum(
- ctx * per_token_global for f in b._sliding_window_pattern[: b._n_layers] if not f
+ ctx * per_token_global
+ for f in b._sliding_window_pattern[: b._n_layers]
+ if not f
)
swa_bytes_per_slot = sum(
per_slot_swa_cells * per_token_swa
@@ -1380,12 +1414,16 @@ class TestServerFlags:
assert global_bytes + swa_bytes_per_slot == baseline
# Only the SWA portion scales by parallel
for slots in (1, 2, 3, 4):
- scaled = b._estimate_kv_cache_bytes(ctx, "f16", n_parallel = slots, kv_unified = False)
+ scaled = b._estimate_kv_cache_bytes(
+ ctx, "f16", n_parallel = slots, kv_unified = False
+ )
# SWA cells clamp to per_slot_ctx when ctx/slots < 2*swa
per_slot_ctx = max(1, ctx // slots)
cells = min(ctx, 2 * swa, per_slot_ctx)
swa_bps = sum(
- cells * per_token_swa for f in b._sliding_window_pattern[: b._n_layers] if f
+ cells * per_token_swa
+ for f in b._sliding_window_pattern[: b._n_layers]
+ if f
)
assert scaled == global_bytes + slots * swa_bps
@@ -1400,7 +1438,9 @@ class TestServerFlags:
for slots in (1, 2, 4, 8):
for unified in (True, False):
assert (
- b._estimate_kv_cache_bytes(8192, "f16", n_parallel = slots, kv_unified = unified)
+ b._estimate_kv_cache_bytes(
+ 8192, "f16", n_parallel = slots, kv_unified = unified
+ )
== baseline
)
@@ -1422,7 +1462,9 @@ class TestServerFlags:
baseline = b._estimate_kv_cache_bytes(ctx, "f16")
flagged = b._estimate_kv_cache_bytes(ctx, "f16", ctx_checkpoints = 4)
# 22 SWA layers * 4 cps * 512 cells * 4 heads * (256+256) * 2 bytes
- n_swa_layers = sum(1 for f in [True, True, True, True, True, False] * 4 + [True, True] if f)
+ n_swa_layers = sum(
+ 1 for f in [True, True, True, True, True, False] * 4 + [True, True] if f
+ )
per_layer = 4 * 512 * 4 * (256 + 256) * 2
assert flagged == baseline + n_swa_layers * per_layer
@@ -1456,7 +1498,9 @@ class TestServerFlags:
flagged = b._estimate_kv_cache_bytes(
ctx, "f16", ctx_checkpoints = 4, n_parallel = slots, kv_unified = False
)
- assert flagged == global_bytes + slots * (swa_bytes_per_slot + cp_extra_per_slot)
+ assert flagged == global_bytes + slots * (
+ swa_bytes_per_slot + cp_extra_per_slot
+ )
# ── --kv-offload (kv_on_gpu) ───────────────────────────────────
@@ -1578,7 +1622,9 @@ class TestParallelSWAScaling:
"_kv_value_length": 256,
"_sliding_window": 512,
# 15 SWA + 3 global, mirrors gemma-3-270m
- "_sliding_window_pattern": [t == "swa" for t in (["swa"] * 5 + ["global"]) * 3],
+ "_sliding_window_pattern": [
+ t == "swa" for t in (["swa"] * 5 + ["global"]) * 3
+ ],
}
defaults.update(overrides)
b = LlamaCppBackend()
@@ -1594,7 +1640,9 @@ class TestParallelSWAScaling:
for slots in (1, 2, 4, 8):
for unified in (True, False):
assert (
- b._estimate_kv_cache_bytes(8192, "f16", n_parallel = slots, kv_unified = unified)
+ b._estimate_kv_cache_bytes(
+ 8192, "f16", n_parallel = slots, kv_unified = unified
+ )
== baseline
)
@@ -1648,7 +1696,9 @@ class TestParallelSWAScaling:
cells = min(ctx, 2 * swa, per_slot_ctx)
swa_bps = n_swa * cells * per_token
for unified in (True, False):
- got = b._estimate_kv_cache_bytes(ctx, "f16", n_parallel = slots, kv_unified = unified)
+ got = b._estimate_kv_cache_bytes(
+ ctx, "f16", n_parallel = slots, kv_unified = unified
+ )
assert got == global_bytes + slots * swa_bps
def test_swa_fallback_scales_only_swa_portion(self):
@@ -1693,7 +1743,8 @@ class TestParallelSWAScaling:
baseline = b._estimate_kv_cache_bytes(ctx, "f16", swa_full = True)
for slots in (1, 2, 4, 8):
assert (
- b._estimate_kv_cache_bytes(ctx, "f16", swa_full = True, n_parallel = slots) == baseline
+ b._estimate_kv_cache_bytes(ctx, "f16", swa_full = True, n_parallel = slots)
+ == baseline
)
# ── kv_unified: no-op for memory math ──────────────────────────
@@ -1707,8 +1758,12 @@ class TestParallelSWAScaling:
]
for label, b in backends:
for slots in (1, 2, 4, 8):
- u = b._estimate_kv_cache_bytes(8192, "f16", n_parallel = slots, kv_unified = True)
- nu = b._estimate_kv_cache_bytes(8192, "f16", n_parallel = slots, kv_unified = False)
+ u = b._estimate_kv_cache_bytes(
+ 8192, "f16", n_parallel = slots, kv_unified = True
+ )
+ nu = b._estimate_kv_cache_bytes(
+ 8192, "f16", n_parallel = slots, kv_unified = False
+ )
assert u == nu, f"{label} parallel={slots} unified-mismatch"
# ── Empirical Gemma-3 270m formula ─────────────────────────────
@@ -1845,7 +1900,9 @@ class TestSharedKVLayers:
assert full_in_unshared == 4
kv_per = 4 * (256 + 256) * 2
swa_cells = min(ctx, 2 * 1024)
- expected = full_in_unshared * ctx * kv_per + sliding_in_unshared * swa_cells * kv_per
+ expected = (
+ full_in_unshared * ctx * kv_per + sliding_in_unshared * swa_cells * kv_per
+ )
assert b._estimate_kv_cache_bytes(ctx, "f16") == expected
def test_shared_layers_reduces_estimate(self):
@@ -1901,7 +1958,9 @@ class TestSharedKVLayers:
per_slot_ctx = max(1, ctx // slots)
swa_cells = min(ctx, 2 * swa, per_slot_ctx)
swa_bytes_per_slot = sliding_in_unshared * swa_cells * per_token
- flagged = b._estimate_kv_cache_bytes(ctx, "f16", n_parallel = slots, kv_unified = False)
+ flagged = b._estimate_kv_cache_bytes(
+ ctx, "f16", n_parallel = slots, kv_unified = False
+ )
assert flagged == global_bytes + slots * swa_bytes_per_slot
def test_composes_with_ctx_checkpoints(self):
diff --git a/studio/backend/tests/test_llama_cpp_context_fit.py b/studio/backend/tests/test_llama_cpp_context_fit.py
index 58226f938c..a792686748 100644
--- a/studio/backend/tests/test_llama_cpp_context_fit.py
+++ b/studio/backend/tests/test_llama_cpp_context_fit.py
@@ -218,7 +218,9 @@ def _drive(
elif gpus:
gpu_indices, use_fit = inst._select_gpus(model_size, gpus)
if use_fit and not explicit_ctx:
- effective_ctx = min(FALLBACK_CTX, effective_ctx) if effective_ctx > 0 else FALLBACK_CTX
+ effective_ctx = (
+ min(FALLBACK_CTX, effective_ctx) if effective_ctx > 0 else FALLBACK_CTX
+ )
return {
"c_arg": effective_ctx if effective_ctx > 0 else 0,
diff --git a/studio/backend/tests/test_llama_cpp_freshness.py b/studio/backend/tests/test_llama_cpp_freshness.py
index f90c4ba0e7..078c808f0f 100644
--- a/studio/backend/tests/test_llama_cpp_freshness.py
+++ b/studio/backend/tests/test_llama_cpp_freshness.py
@@ -189,7 +189,9 @@ def test_latest_published_release_returns_none_on_network_failure(monkeypatch):
assert fr.latest_published_release("unslothai/llama.cpp") is None
-def test_latest_published_release_keeps_old_cache_on_transient_failure(monkeypatch, tmp_path):
+def test_latest_published_release_keeps_old_cache_on_transient_failure(
+ monkeypatch, tmp_path
+):
# Disk entry older than TTL + network fail -> return cached value.
cache_dir = tmp_path / ".freshness"
cache_dir.mkdir()
@@ -203,7 +205,9 @@ def test_latest_published_release_keeps_old_cache_on_transient_failure(monkeypat
# check_prebuilt_freshness end-to-end.
-def test_check_prebuilt_freshness_reports_stale_when_old_and_behind(monkeypatch, tmp_path):
+def test_check_prebuilt_freshness_reports_stale_when_old_and_behind(
+ monkeypatch, tmp_path
+):
install_dir = tmp_path / "llama.cpp"
_write_marker(
install_dir,
@@ -213,7 +217,9 @@ def test_check_prebuilt_freshness_reports_stale_when_old_and_behind(monkeypatch,
.replace("+00:00", "Z"),
)
bin_path = _fake_binary(install_dir, layout = "root")
- monkeypatch.setattr(fr, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9300")
+ monkeypatch.setattr(
+ fr, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9300"
+ )
info = fr.check_prebuilt_freshness(str(bin_path))
assert info["has_marker"] is True
assert info["stale"] is True
@@ -233,7 +239,9 @@ def test_check_prebuilt_freshness_not_stale_when_tag_matches(monkeypatch, tmp_pa
.replace("+00:00", "Z"),
)
bin_path = _fake_binary(install_dir, layout = "root")
- monkeypatch.setattr(fr, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9300")
+ monkeypatch.setattr(
+ fr, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9300"
+ )
info = fr.check_prebuilt_freshness(str(bin_path))
assert info["stale"] is False
assert info["installed_tag"] == "b9300"
@@ -251,7 +259,9 @@ def test_check_prebuilt_freshness_not_stale_within_threshold(monkeypatch, tmp_pa
.replace("+00:00", "Z"),
)
bin_path = _fake_binary(install_dir, layout = "root")
- monkeypatch.setattr(fr, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9300")
+ monkeypatch.setattr(
+ fr, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9300"
+ )
info = fr.check_prebuilt_freshness(str(bin_path))
assert info["stale"] is False
assert info["age_days"] == 1
@@ -264,7 +274,9 @@ def test_check_prebuilt_freshness_fails_open_without_marker(tmp_path):
assert info["stale"] is False
-def test_check_prebuilt_freshness_fails_open_when_github_unreachable(monkeypatch, tmp_path):
+def test_check_prebuilt_freshness_fails_open_when_github_unreachable(
+ monkeypatch, tmp_path
+):
install_dir = tmp_path / "llama.cpp"
_write_marker(
install_dir,
@@ -281,11 +293,15 @@ def test_check_prebuilt_freshness_fails_open_when_github_unreachable(monkeypatch
assert info["latest_tag"] is None
-def test_check_prebuilt_freshness_handles_unparseable_install_timestamp(monkeypatch, tmp_path):
+def test_check_prebuilt_freshness_handles_unparseable_install_timestamp(
+ monkeypatch, tmp_path
+):
install_dir = tmp_path / "llama.cpp"
_write_marker(install_dir, tag = "b9190", installed_at_utc = "not-a-date")
bin_path = _fake_binary(install_dir, layout = "root")
- monkeypatch.setattr(fr, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9300")
+ monkeypatch.setattr(
+ fr, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9300"
+ )
info = fr.check_prebuilt_freshness(str(bin_path))
assert info["stale"] is False
assert info["age_days"] is None
@@ -301,7 +317,9 @@ def test_check_prebuilt_freshness_respects_custom_threshold(monkeypatch, tmp_pat
.replace("+00:00", "Z"),
)
bin_path = _fake_binary(install_dir, layout = "root")
- monkeypatch.setattr(fr, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9300")
+ monkeypatch.setattr(
+ fr, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9300"
+ )
info = fr.check_prebuilt_freshness(str(bin_path), threshold_days = 1)
assert info["stale"] is True
@@ -310,7 +328,9 @@ def test_check_prebuilt_freshness_respects_custom_threshold(monkeypatch, tmp_pat
def test_format_stale_warning_contains_actionable_command():
- msg = fr.format_stale_warning({"installed_tag": "b9190", "latest_tag": "b9300", "age_days": 5})
+ msg = fr.format_stale_warning(
+ {"installed_tag": "b9190", "latest_tag": "b9300", "age_days": 5}
+ )
assert "b9190" in msg
assert "b9300" in msg
assert "5 days" in msg
@@ -318,7 +338,9 @@ def test_format_stale_warning_contains_actionable_command():
def test_format_stale_warning_singular_day():
- msg = fr.format_stale_warning({"installed_tag": "b9190", "latest_tag": "b9300", "age_days": 1})
+ msg = fr.format_stale_warning(
+ {"installed_tag": "b9190", "latest_tag": "b9300", "age_days": 1}
+ )
assert "1 day" in msg
assert "1 days" not in msg
@@ -329,7 +351,9 @@ def test_format_stale_warning_singular_day():
def test_parse_base_build():
assert fr.parse_base_build("b9596") == 9596
assert fr.parse_base_build(" b9596 ") == 9596
- assert fr.parse_base_build("b9596-mix-e6f2453") == 9596 # mix suffix doesn't defeat it
+ assert (
+ fr.parse_base_build("b9596-mix-e6f2453") == 9596
+ ) # mix suffix doesn't defeat it
assert fr.parse_base_build("9596") is None
assert fr.parse_base_build("master-abc") is None
assert fr.parse_base_build("") is None
@@ -387,7 +411,9 @@ def test_check_prebuilt_freshness_downgrade_guard(monkeypatch, tmp_path):
.replace("+00:00", "Z"),
)
bin_path = _fake_binary(install_dir, layout = "root")
- monkeypatch.setattr(fr, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9518")
+ monkeypatch.setattr(
+ fr, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9518"
+ )
info = fr.check_prebuilt_freshness(str(bin_path))
assert info["behind"] is False
assert info["stale"] is False
@@ -431,7 +457,9 @@ def test_fetch_latest_release_tag_uses_publish_time(monkeypatch):
"published_at": "2026-06-12T00:00:00Z",
},
]
- monkeypatch.setattr(urllib.request, "urlopen", lambda req, timeout = 5.0: _Resp(payload))
+ monkeypatch.setattr(
+ urllib.request, "urlopen", lambda req, timeout = 5.0: _Resp(payload)
+ )
assert fr._fetch_latest_release_tag("unslothai/llama.cpp") == "b9596-mix-e6f2453"
@@ -443,7 +471,9 @@ def _seed_disk_cache(tmp_path: Path, latest_tag: str) -> Path:
cache_dir = tmp_path / ".freshness"
cache_dir.mkdir(exist_ok = True)
cache_file = cache_dir / "unslothai__llama.cpp.json"
- cache_file.write_text(json.dumps({"fetched_at": time.time(), "latest_tag": latest_tag}))
+ cache_file.write_text(
+ json.dumps({"fetched_at": time.time(), "latest_tag": latest_tag})
+ )
return cache_file
@@ -468,7 +498,9 @@ def test_reset_caches_drop_disk_on_missing_dir_is_noop(tmp_path):
fr.reset_caches(drop_disk = True) # must not raise
-def test_drop_disk_lets_banner_fail_open_after_same_base_mix_swap(monkeypatch, tmp_path):
+def test_drop_disk_lets_banner_fail_open_after_same_base_mix_swap(
+ monkeypatch, tmp_path
+):
# P2 #2: the disk cache holds a still-fresh same-base mix (b9596-mix-aaa)
# from before an update to a *different* same-base mix (b9596-mix-bbb).
# The post-install path drops the disk cache; if the forced refresh is then
diff --git a/studio/backend/tests/test_llama_cpp_mmproj_fallback.py b/studio/backend/tests/test_llama_cpp_mmproj_fallback.py
index 6ef94545fe..b8a65e98bc 100644
--- a/studio/backend/tests/test_llama_cpp_mmproj_fallback.py
+++ b/studio/backend/tests/test_llama_cpp_mmproj_fallback.py
@@ -31,7 +31,9 @@ _loggers_stub = _types.ModuleType("loggers")
_loggers_stub.get_logger = lambda name: __import__("logging").getLogger(name)
sys.modules.setdefault("loggers", _loggers_stub)
_structlog_stub = _types.ModuleType("structlog")
-_structlog_stub.get_logger = lambda *a, **k: __import__("logging").getLogger("structlog")
+_structlog_stub.get_logger = lambda *a, **k: __import__("logging").getLogger(
+ "structlog"
+)
sys.modules.setdefault("structlog", _structlog_stub)
if not hasattr(sys.modules["structlog"], "get_logger"):
sys.modules["structlog"].get_logger = _structlog_stub.get_logger
@@ -54,7 +56,9 @@ _OOM_OUT = (
"ggml_backend_cuda_buffer_type_alloc_buffer: allocating 12000.00 MiB on "
"device 0: cudaMalloc failed: out of memory"
)
-_BAD_ARCH_OUT = "llama_model_load: error loading model: unknown model architecture: 'qwen_image'"
+_BAD_ARCH_OUT = (
+ "llama_model_load: error loading model: unknown model architecture: 'qwen_image'"
+)
_PORT_OUT = "srv start: failed to bind: address already in use"
_MISSING_OUT = "error: failed to open GGUF file: no such file or directory"
# A healthy startup log that merely mentions the projector must not match.
diff --git a/studio/backend/tests/test_llama_cpp_mtp_detection.py b/studio/backend/tests/test_llama_cpp_mtp_detection.py
index b00cd7169e..a86e9823e4 100644
--- a/studio/backend/tests/test_llama_cpp_mtp_detection.py
+++ b/studio/backend/tests/test_llama_cpp_mtp_detection.py
@@ -77,7 +77,9 @@ def _enc_kv_string(key: str, value: str) -> bytes:
def _enc_kv_uint32(key: str, value: int) -> bytes:
- return _enc_string(key) + struct.pack(" forward --has-rocm.
- assert upd._rocm_install_args("llama-b9334-bin-ubuntu-rocm-6.4-x64.tar.gz") == ["--has-rocm"]
+ assert upd._rocm_install_args("llama-b9334-bin-ubuntu-rocm-6.4-x64.tar.gz") == [
+ "--has-rocm"
+ ]
def test_rocm_install_args_windows_hip():
- assert upd._rocm_install_args("llama-b9334-bin-win-hip-radeon-x64.zip") == ["--has-rocm"]
+ assert upd._rocm_install_args("llama-b9334-bin-win-hip-radeon-x64.zip") == [
+ "--has-rocm"
+ ]
def test_rocm_install_args_non_rocm_and_missing():
@@ -460,8 +488,12 @@ def _capture_install_cmd(
install_dir = tmp_path / "llama.cpp"
binary = _write_install(install_dir, tag, repo = repo, asset = asset)
monkeypatch.setattr(upd, "_find_binary", lambda: binary)
- monkeypatch.setattr(upd, "_installer_script", lambda: tmp_path / "install_llama_prebuilt.py")
- monkeypatch.setattr(freshness, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: latest)
+ monkeypatch.setattr(
+ upd, "_installer_script", lambda: tmp_path / "install_llama_prebuilt.py"
+ )
+ monkeypatch.setattr(
+ freshness, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: latest
+ )
captured = {}
@@ -545,7 +577,9 @@ def test_install_cmd_cuda_marker_minimal_and_backward_compatible(monkeypatch, tm
def test_start_update_already_running_refuses(monkeypatch, tmp_path):
binary = _write_install(tmp_path / "llama.cpp", "b9493")
monkeypatch.setattr(upd, "_find_binary", lambda: binary)
- monkeypatch.setattr(upd, "_installer_script", lambda: tmp_path / "install_llama_prebuilt.py")
+ monkeypatch.setattr(
+ upd, "_installer_script", lambda: tmp_path / "install_llama_prebuilt.py"
+ )
with upd._job_lock:
upd._job.update(state = upd._JOB_RUNNING)
res = upd.start_update()
@@ -590,8 +624,12 @@ def test_update_sets_maintenance_flag_and_unloads(monkeypatch, tmp_path):
install_dir = tmp_path / "llama.cpp"
binary = _write_install(install_dir, "b9493")
monkeypatch.setattr(upd, "_find_binary", lambda: binary)
- monkeypatch.setattr(upd, "_installer_script", lambda: tmp_path / "install_llama_prebuilt.py")
- monkeypatch.setattr(freshness, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9518")
+ monkeypatch.setattr(
+ upd, "_installer_script", lambda: tmp_path / "install_llama_prebuilt.py"
+ )
+ monkeypatch.setattr(
+ freshness, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9518"
+ )
backend = _FakeBackend()
_inject_backend(monkeypatch, backend)
@@ -623,8 +661,12 @@ def test_update_clears_maintenance_flag_on_installer_failure(monkeypatch, tmp_pa
install_dir = tmp_path / "llama.cpp"
binary = _write_install(install_dir, "b9493")
monkeypatch.setattr(upd, "_find_binary", lambda: binary)
- monkeypatch.setattr(upd, "_installer_script", lambda: tmp_path / "install_llama_prebuilt.py")
- monkeypatch.setattr(freshness, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9518")
+ monkeypatch.setattr(
+ upd, "_installer_script", lambda: tmp_path / "install_llama_prebuilt.py"
+ )
+ monkeypatch.setattr(
+ freshness, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9518"
+ )
backend = _FakeBackend()
_inject_backend(monkeypatch, backend)
@@ -646,8 +688,12 @@ def test_update_fails_open_when_backend_unavailable(monkeypatch, tmp_path):
install_dir = tmp_path / "llama.cpp"
binary = _write_install(install_dir, "b9493")
monkeypatch.setattr(upd, "_find_binary", lambda: binary)
- monkeypatch.setattr(upd, "_installer_script", lambda: tmp_path / "install_llama_prebuilt.py")
- monkeypatch.setattr(freshness, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9518")
+ monkeypatch.setattr(
+ upd, "_installer_script", lambda: tmp_path / "install_llama_prebuilt.py"
+ )
+ monkeypatch.setattr(
+ freshness, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9518"
+ )
def _raise():
raise RuntimeError("no backend")
@@ -659,7 +705,9 @@ def test_update_fails_open_when_backend_unavailable(monkeypatch, tmp_path):
monkeypatch.setitem(sys.modules, "routes", routes_pkg)
monkeypatch.setitem(sys.modules, "routes.inference", inference_mod)
- _patch_installer_popen(monkeypatch, on_start = lambda cmd: _write_install(install_dir, "b9518"))
+ _patch_installer_popen(
+ monkeypatch, on_start = lambda cmd: _write_install(install_dir, "b9518")
+ )
res = upd.start_update()
assert res["started"] is True
@@ -676,15 +724,15 @@ def test_update_fails_open_when_backend_unavailable(monkeypatch, tmp_path):
def test_resolve_prebuilt_parses_and_caches(monkeypatch, tmp_path):
- monkeypatch.setattr(upd, "_installer_script", lambda: tmp_path / "install_llama_prebuilt.py")
+ monkeypatch.setattr(
+ upd, "_installer_script", lambda: tmp_path / "install_llama_prebuilt.py"
+ )
calls = {"n": 0}
class _Proc:
returncode = 0
# stderr noise plus the JSON line on stdout (installer logs to stderr).
- stdout = (
- '{"prebuilt_available": true, "repo": "unslothai/llama.cpp", "release_tag": "b9585"}'
- )
+ stdout = '{"prebuilt_available": true, "repo": "unslothai/llama.cpp", "release_tag": "b9585"}'
stderr = "[llama-prebuilt] some log\n"
def _fake_run(cmd, **kwargs):
@@ -701,7 +749,9 @@ def test_resolve_prebuilt_parses_and_caches(monkeypatch, tmp_path):
def test_resolve_prebuilt_fails_open(monkeypatch, tmp_path):
- monkeypatch.setattr(upd, "_installer_script", lambda: tmp_path / "install_llama_prebuilt.py")
+ monkeypatch.setattr(
+ upd, "_installer_script", lambda: tmp_path / "install_llama_prebuilt.py"
+ )
def _boom(cmd, **kwargs):
raise OSError("subprocess failed")
@@ -777,7 +827,9 @@ def test_llama_install_root_ignores_inactive_env_root(monkeypatch, tmp_path):
assert upd._llama_install_root(str(binary)) == active
-def test_llama_install_root_refuses_pinned_checkout_under_llama_cpp(monkeypatch, tmp_path):
+def test_llama_install_root_refuses_pinned_checkout_under_llama_cpp(
+ monkeypatch, tmp_path
+):
# The LLAMA_SERVER_PATH pin guard must run before the ancestor scan, or a
# user's own llama.cpp checkout could be handed to the installer.
root = tmp_path / "my-project" / "llama.cpp"
@@ -797,7 +849,9 @@ def test_start_update_source_build_refuses_when_newer(monkeypatch, tmp_path):
binary.parent.mkdir(parents = True)
binary.write_text("stub") # no marker
monkeypatch.setattr(upd, "_find_binary", lambda: str(binary))
- monkeypatch.setattr(upd, "_installer_script", lambda: tmp_path / "install_llama_prebuilt.py")
+ monkeypatch.setattr(
+ upd, "_installer_script", lambda: tmp_path / "install_llama_prebuilt.py"
+ )
_prebuilt(monkeypatch, release_tag = "b9518")
monkeypatch.setattr(upd, "_installed_build_number", lambda b: 9600)
res = upd.start_update()
@@ -810,10 +864,14 @@ def test_start_update_source_build_refuses_when_newer(monkeypatch, tmp_path):
def test_status_not_offered_on_mix_latest(monkeypatch, tmp_path):
# Installed the mix latest; GitHub latest is that same full tag -> no banner.
- binary = _write_install(tmp_path / "llama.cpp", "b9596", release_tag = "b9596-mix-e6f2453")
+ binary = _write_install(
+ tmp_path / "llama.cpp", "b9596", release_tag = "b9596-mix-e6f2453"
+ )
monkeypatch.setattr(upd, "_find_binary", lambda: binary)
monkeypatch.setattr(
- freshness, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9596-mix-e6f2453"
+ freshness,
+ "_fetch_latest_release_tag",
+ lambda repo, timeout = 5.0: "b9596-mix-e6f2453",
)
st = upd.get_update_status()
assert st["update_available"] is False
@@ -825,18 +883,26 @@ def test_status_not_offered_when_latest_lags(monkeypatch, tmp_path):
# A lagging latest (older build than installed) must never be offered.
binary = _write_install(tmp_path / "llama.cpp", "b9585")
monkeypatch.setattr(upd, "_find_binary", lambda: binary)
- monkeypatch.setattr(freshness, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9518")
+ monkeypatch.setattr(
+ freshness, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9518"
+ )
st = upd.get_update_status()
assert st["update_available"] is False
def test_start_update_marked_refuses_when_not_behind(monkeypatch, tmp_path):
# A direct POST / stale banner must not reinstall when already on the latest.
- binary = _write_install(tmp_path / "llama.cpp", "b9596", release_tag = "b9596-mix-e6f2453")
+ binary = _write_install(
+ tmp_path / "llama.cpp", "b9596", release_tag = "b9596-mix-e6f2453"
+ )
monkeypatch.setattr(upd, "_find_binary", lambda: binary)
- monkeypatch.setattr(upd, "_installer_script", lambda: tmp_path / "install_llama_prebuilt.py")
monkeypatch.setattr(
- freshness, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9596-mix-e6f2453"
+ upd, "_installer_script", lambda: tmp_path / "install_llama_prebuilt.py"
+ )
+ monkeypatch.setattr(
+ freshness,
+ "_fetch_latest_release_tag",
+ lambda repo, timeout = 5.0: "b9596-mix-e6f2453",
)
res = upd.start_update()
assert res["started"] is False
diff --git a/studio/backend/tests/test_llama_cpp_wait_for_health.py b/studio/backend/tests/test_llama_cpp_wait_for_health.py
index 1ba6c9f7b5..277ce4b6d1 100644
--- a/studio/backend/tests/test_llama_cpp_wait_for_health.py
+++ b/studio/backend/tests/test_llama_cpp_wait_for_health.py
@@ -162,7 +162,9 @@ class TestCrashLogTail:
records: list = []
fake_logger = mock.Mock()
- fake_logger.error = mock.Mock(side_effect = lambda msg, *a, **k: records.append(msg))
+ fake_logger.error = mock.Mock(
+ side_effect = lambda msg, *a, **k: records.append(msg)
+ )
monkeypatch.setattr(_llama_mod, "logger", fake_logger)
return records
@@ -207,7 +209,10 @@ class TestRetryLogFilenameUnique:
def test_log_name_includes_attempt_index(self):
src = (
- Path(__file__).resolve().parent.parent / "core" / "inference" / "llama_cpp.py"
+ Path(__file__).resolve().parent.parent
+ / "core"
+ / "inference"
+ / "llama_cpp.py"
).read_text(encoding = "utf-8")
assert "-try{_spawn_attempt}.log" in src
diff --git a/studio/backend/tests/test_llama_cpp_wait_for_vram_settle.py b/studio/backend/tests/test_llama_cpp_wait_for_vram_settle.py
index 493bb93e8c..e603c742b4 100644
--- a/studio/backend/tests/test_llama_cpp_wait_for_vram_settle.py
+++ b/studio/backend/tests/test_llama_cpp_wait_for_vram_settle.py
@@ -127,7 +127,9 @@ def test_stale_kill_skips_wait():
LlamaCppBackend._wait_for_vram_settle(
**_kw(since_kill = long_ago, max_wait = 2.0, interval = 0.25)
)
- assert state["calls"] == 0, "kill older than _VRAM_SETTLE_WINDOW_S must skip the wait"
+ assert (
+ state["calls"] == 0
+ ), "kill older than _VRAM_SETTLE_WINDOW_S must skip the wait"
def test_empty_first_sample_returns_immediately():
@@ -215,7 +217,9 @@ def test_max_wait_respected_when_probe_is_slow():
elapsed = time.monotonic() - start
# First probe (0.30 s) + at most one clipped sleep + bail.
# Hard cap well below the old 0.30 + 0.25 + 0.30 = 0.85.
- assert elapsed < 0.85, f"helper exceeded the deadline due to slow probes: {elapsed:.3f}s"
+ assert (
+ elapsed < 0.85
+ ), f"helper exceeded the deadline due to slow probes: {elapsed:.3f}s"
def test_gpu_index_set_change_returns():
diff --git a/studio/backend/tests/test_llama_cpp_windows_nvidia_path.py b/studio/backend/tests/test_llama_cpp_windows_nvidia_path.py
index 957de4bad6..bdf6f8df75 100644
--- a/studio/backend/tests/test_llama_cpp_windows_nvidia_path.py
+++ b/studio/backend/tests/test_llama_cpp_windows_nvidia_path.py
@@ -177,13 +177,17 @@ class TestWindowsPipNvidiaDllDirs:
def test_missing_prefix_does_not_raise(self):
# Nonexistent sys.prefix: resolver must return [], not raise.
- result = LlamaCppBackend._windows_pip_nvidia_dll_dirs("/this/path/does/not/exist/anywhere")
+ result = LlamaCppBackend._windows_pip_nvidia_dll_dirs(
+ "/this/path/does/not/exist/anywhere"
+ )
assert result == []
def test_picks_up_cu13_bin_x86_64_layout(self, tmp_path):
# nvidia 13.x Windows wheels ship DLLs under nvidia/cu13/bin/x86_64/
# not nvidia//bin/; else the new CUDA 13 wheels hit #5106.
- dll_dir = tmp_path / "Lib" / "site-packages" / "nvidia" / "cu13" / "bin" / "x86_64"
+ dll_dir = (
+ tmp_path / "Lib" / "site-packages" / "nvidia" / "cu13" / "bin" / "x86_64"
+ )
dll_dir.mkdir(parents = True)
for name in ("cudart64_13.dll", "cublas64_13.dll", "cublasLt64_13.dll"):
(dll_dir / name).write_bytes(b"")
diff --git a/studio/backend/tests/test_llama_route_timeouts.py b/studio/backend/tests/test_llama_route_timeouts.py
index 5aee6198ba..cb3c132408 100644
--- a/studio/backend/tests/test_llama_route_timeouts.py
+++ b/studio/backend/tests/test_llama_route_timeouts.py
@@ -66,7 +66,9 @@ def test_preheader_send_cleanup_on_disconnect_and_cancel():
return state.disconnected
task = asyncio.create_task(
- inf_mod._send_stream_with_preheader_cancel(_Client(), object(), request = _Request())
+ inf_mod._send_stream_with_preheader_cancel(
+ _Client(), object(), request = _Request()
+ )
)
await started.wait()
if cancel_parent:
diff --git a/studio/backend/tests/test_llama_server_args.py b/studio/backend/tests/test_llama_server_args.py
index f3a3ea1ec4..bea76203d4 100644
--- a/studio/backend/tests/test_llama_server_args.py
+++ b/studio/backend/tests/test_llama_server_args.py
@@ -18,7 +18,12 @@ import pytest
# Load llama_server_args.py directly to avoid dragging in the full backend
# chain via core/inference/__init__.py. The validator is dependency-free.
-_LSA_PATH = Path(__file__).resolve().parent.parent / "core" / "inference" / "llama_server_args.py"
+_LSA_PATH = (
+ Path(__file__).resolve().parent.parent
+ / "core"
+ / "inference"
+ / "llama_server_args.py"
+)
_spec = importlib.util.spec_from_file_location("_lsa_test_only", _LSA_PATH)
_lsa = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_lsa)
@@ -487,7 +492,9 @@ def test_strip_shadowing_flags_jinja_boolean_preserves_positional():
def test_strip_shadowing_flags_no_jinja_boolean_preserves_positional():
- out = strip_shadowing_flags(["--no-jinja", "trailing-positional"], strip_template = True)
+ out = strip_shadowing_flags(
+ ["--no-jinja", "trailing-positional"], strip_template = True
+ )
assert out == ["trailing-positional"]
@@ -664,11 +671,15 @@ def test_strip_shadowing_flags_keeps_split_mode_when_not_requested():
def test_strip_shadowing_flags_drops_split_mode_short_alias_and_equals():
- assert strip_shadowing_flags(["-sm", "tensor", "--top-k", "20"], strip_split_mode = True) == [
+ assert strip_shadowing_flags(
+ ["-sm", "tensor", "--top-k", "20"], strip_split_mode = True
+ ) == [
"--top-k",
"20",
]
- assert strip_shadowing_flags(["--split-mode=row", "--seed", "-1"], strip_split_mode = True) == [
+ assert strip_shadowing_flags(
+ ["--split-mode=row", "--seed", "-1"], strip_split_mode = True
+ ) == [
"--seed",
"-1",
]
diff --git a/studio/backend/tests/test_llm_assist_startup_opt_in.py b/studio/backend/tests/test_llm_assist_startup_opt_in.py
index e81b1d3775..8b72105f25 100644
--- a/studio/backend/tests/test_llm_assist_startup_opt_in.py
+++ b/studio/backend/tests/test_llm_assist_startup_opt_in.py
@@ -72,9 +72,13 @@ def test_settings_route_persists_helper_precache_toggle(monkeypatch):
def test_main_startup_uses_helper_precache_gate_instead_of_unconditional_precache():
- source = (Path(__file__).resolve().parent.parent / "main.py").read_text(encoding = "utf-8")
+ source = (Path(__file__).resolve().parent.parent / "main.py").read_text(
+ encoding = "utf-8"
+ )
startup_section = source[
- source.index("cleanup_orphaned_runs") : source.index("# Initialize RSA key pair")
+ source.index("cleanup_orphaned_runs") : source.index(
+ "# Initialize RSA key pair"
+ )
]
assert "_start_helper_precache_if_enabled()" in startup_section
diff --git a/studio/backend/tests/test_login_rate_limit.py b/studio/backend/tests/test_login_rate_limit.py
index 14b10576da..1a58f4fa6c 100644
--- a/studio/backend/tests/test_login_rate_limit.py
+++ b/studio/backend/tests/test_login_rate_limit.py
@@ -101,22 +101,30 @@ class TestClientIp:
def test_xff_strips_ipv4_port(self, env_trust_proxy):
from routes.auth import _client_ip
- req = _FakeRequest("127.0.0.1", {"x-forwarded-for": "198.51.100.7:50001, 10.0.0.1"})
+ req = _FakeRequest(
+ "127.0.0.1", {"x-forwarded-for": "198.51.100.7:50001, 10.0.0.1"}
+ )
assert _client_ip(req) == "198.51.100.7"
def test_xff_strips_bracketed_ipv6_port(self, env_trust_proxy):
from routes.auth import _client_ip
- req = _FakeRequest("127.0.0.1", {"x-forwarded-for": "[2001:db8::1]:50001, 10.0.0.1"})
+ req = _FakeRequest(
+ "127.0.0.1", {"x-forwarded-for": "[2001:db8::1]:50001, 10.0.0.1"}
+ )
assert _client_ip(req) == "2001:db8::1"
def test_forwarded_strips_ipv4_port(self, env_trust_proxy):
from routes.auth import _client_ip
- req = _FakeRequest("127.0.0.1", {"forwarded": 'for="198.51.100.7:50001";proto=https'})
+ req = _FakeRequest(
+ "127.0.0.1", {"forwarded": 'for="198.51.100.7:50001";proto=https'}
+ )
assert _client_ip(req) == "198.51.100.7"
def test_forwarded_strips_bracketed_ipv6_port(self, env_trust_proxy):
from routes.auth import _client_ip
- req = _FakeRequest("127.0.0.1", {"forwarded": 'for="[2001:db8::1]:50001";proto=https'})
+ req = _FakeRequest(
+ "127.0.0.1", {"forwarded": 'for="[2001:db8::1]:50001";proto=https'}
+ )
assert _client_ip(req) == "2001:db8::1"
def test_forwarded_isolates_first_element(self, env_trust_proxy):
@@ -229,7 +237,9 @@ class TestLogin429Body:
import secrets as _secrets
monkeypatch.setattr(storage, "DB_PATH", tmp_path / "auth.db")
- monkeypatch.setattr(storage, "_BOOTSTRAP_PW_PATH", tmp_path / ".bootstrap_password")
+ monkeypatch.setattr(
+ storage, "_BOOTSTRAP_PW_PATH", tmp_path / ".bootstrap_password"
+ )
monkeypatch.setattr(storage, "_bootstrap_password", None)
storage.create_initial_user(
username = storage.DEFAULT_ADMIN_USERNAME,
diff --git a/studio/backend/tests/test_mcp_config_import.py b/studio/backend/tests/test_mcp_config_import.py
index 4082733490..86e0a61f18 100644
--- a/studio/backend/tests/test_mcp_config_import.py
+++ b/studio/backend/tests/test_mcp_config_import.py
@@ -102,7 +102,11 @@ def test_parse_windows_apostrophes_as_literals(monkeypatch):
"C:\\Users\\O'Reilly\\server.js",
]
assert mcp_client.parse_stdio_command("node 'draft'") == ["node", "'draft'"]
- assert mcp_client.parse_stdio_command("node 'open close'") == ["node", "'open", "close'"]
+ assert mcp_client.parse_stdio_command("node 'open close'") == [
+ "node",
+ "'open",
+ "close'",
+ ]
def test_parse_rejects_unterminated_windows_double_quote(monkeypatch):
@@ -187,11 +191,18 @@ def test_parse_accepts_cline_streamable_http_alias():
[
{"command": "node", "args": ["server.js"], "cwd": "/tmp/server"},
{"command": "node", "args": ["server.js"], "envFile": ".env"},
- {"command": "node", "args": ["server.js"], "env": {"API_KEY": "${input:api-key}"}},
+ {
+ "command": "node",
+ "args": ["server.js"],
+ "env": {"API_KEY": "${input:api-key}"},
+ },
{"command": "node", "args": ["${workspaceFolder}/server.js"]},
{"command": "node", "args": ["server.js"], "env": {"HTTP_PROXY": None}},
{"command": "node", "args": ["server.js"], "sandboxEnabled": True},
- {"url": "https://example.com/mcp", "headers": {"Authorization": "Bearer ${input:token}"}},
+ {
+ "url": "https://example.com/mcp",
+ "headers": {"Authorization": "Bearer ${input:token}"},
+ },
{"url": "https://example.com/mcp", "headers": {"Authorization": None}},
{"type": "http", "url": "https://example.com/sse"},
{"type": "http", "url": "https://example.com/sse "},
@@ -217,7 +228,9 @@ def test_servers_alias_key():
def test_env_and_args_values_coerced_to_str():
- cfg = {"mcpServers": {"fs": {"command": "node", "args": [8080], "env": {"PORT": 8080}}}}
+ cfg = {
+ "mcpServers": {"fs": {"command": "node", "args": [8080], "env": {"PORT": 8080}}}
+ }
entries, errors = parse_mcp_config(cfg)
assert errors == []
assert entries[0].headers == {"PORT": "8080"}
@@ -296,11 +309,18 @@ def test_import_route_creates_and_dedups(tmp_path, monkeypatch):
}
}
res = asyncio.run(
- routes_mcp.import_mcp_servers(McpServerImportRequest(config = cfg), current_subject = "u")
+ routes_mcp.import_mcp_servers(
+ McpServerImportRequest(config = cfg), current_subject = "u"
+ )
)
assert res.errors == []
assert res.skipped == []
- assert {c.display_name for c in res.created} == {"fs", "remote", "oauth", "disabled"}
+ assert {c.display_name for c in res.created} == {
+ "fs",
+ "remote",
+ "oauth",
+ "disabled",
+ }
fs = next(c for c in res.created if c.display_name == "fs")
assert fs.headers == {"API_KEY": "sk"}
assert fs.use_oauth is False
@@ -312,7 +332,9 @@ def test_import_route_creates_and_dedups(tmp_path, monkeypatch):
# Re-importing the same config skips both by url.
res2 = asyncio.run(
- routes_mcp.import_mcp_servers(McpServerImportRequest(config = cfg), current_subject = "u")
+ routes_mcp.import_mcp_servers(
+ McpServerImportRequest(config = cfg), current_subject = "u"
+ )
)
assert res2.created == []
assert set(res2.skipped) == {"fs", "remote", "oauth", "disabled"}
@@ -333,7 +355,9 @@ def test_import_route_gates_stdio_when_disabled(tmp_path, monkeypatch):
}
}
res = asyncio.run(
- routes_mcp.import_mcp_servers(McpServerImportRequest(config = cfg), current_subject = "u")
+ routes_mcp.import_mcp_servers(
+ McpServerImportRequest(config = cfg), current_subject = "u"
+ )
)
# Remote still imports; the stdio entry is rejected per-entry (gate off).
assert {c.display_name for c in res.created} == {"remote"}
diff --git a/studio/backend/tests/test_mcp_servers.py b/studio/backend/tests/test_mcp_servers.py
index 90b1ade03c..b22eb74a8e 100644
--- a/studio/backend/tests/test_mcp_servers.py
+++ b/studio/backend/tests/test_mcp_servers.py
@@ -45,7 +45,9 @@ def test_list_servers_ordered_by_created_at(tmp_path, monkeypatch):
def test_update_server_coerces_bools(tmp_path, monkeypatch):
_reset_db(tmp_path, monkeypatch)
mcp_servers_db.create_server(id = "srv1", display_name = "A", url = "https://a/m")
- assert mcp_servers_db.update_server("srv1", {"is_enabled": False, "use_oauth": True})
+ assert mcp_servers_db.update_server(
+ "srv1", {"is_enabled": False, "use_oauth": True}
+ )
row = mcp_servers_db.get_server("srv1")
assert row["is_enabled"] == 0
assert row["use_oauth"] == 1
@@ -87,7 +89,9 @@ def test_validate_url_rejects_bad(bad):
def test_normalize_headers():
from routes.mcp_servers import _normalize_headers
- assert _normalize_headers({" Auth ": "Bearer x", "": "ignored"}) == {"Auth": "Bearer x"}
+ assert _normalize_headers({" Auth ": "Bearer x", "": "ignored"}) == {
+ "Auth": "Bearer x"
+ }
assert _normalize_headers({"X": 42}) == {"X": "42"}
assert _normalize_headers({}) is None
assert _normalize_headers(None) is None
@@ -99,12 +103,15 @@ def test_changes_from_payload_tristate_headers():
from models.mcp_servers import McpServerUpdate
# omitted → key absent
- assert "headers_json" not in _changes_from_payload(McpServerUpdate(display_name = "x"))
+ assert "headers_json" not in _changes_from_payload(
+ McpServerUpdate(display_name = "x")
+ )
# null → stored as None (clear all headers)
assert _changes_from_payload(McpServerUpdate(headers = None))["headers_json"] is None
# dict → serialised JSON
assert (
- _changes_from_payload(McpServerUpdate(headers = {"a": "1"}))["headers_json"] == '{"a": "1"}'
+ _changes_from_payload(McpServerUpdate(headers = {"a": "1"}))["headers_json"]
+ == '{"a": "1"}'
)
@@ -134,7 +141,10 @@ def test_execute_tool_malformed_mcp_name():
def test_execute_tool_unknown_server(tmp_path, monkeypatch):
_reset_db(tmp_path, monkeypatch)
from core.inference.tools import execute_tool
- assert execute_tool("mcp__missing__do_thing", {}) == "Error: MCP server 'missing' not found"
+ assert (
+ execute_tool("mcp__missing__do_thing", {})
+ == "Error: MCP server 'missing' not found"
+ )
def test_execute_tool_disabled_server(tmp_path, monkeypatch):
@@ -147,7 +157,10 @@ def test_execute_tool_disabled_server(tmp_path, monkeypatch):
)
from core.inference.tools import execute_tool
- assert execute_tool("mcp__srv1__do_thing", {}) == "Error: MCP server 'srv1' is disabled"
+ assert (
+ execute_tool("mcp__srv1__do_thing", {})
+ == "Error: MCP server 'srv1' is disabled"
+ )
def test_mcp_specs_skip_invalid_openai_function_names():
@@ -616,7 +629,9 @@ def test_get_enabled_mcp_tools_caches_discovery(tmp_path, monkeypatch):
from core.inference import tools as tools_mod
monkeypatch.setattr(mcp_client, "_tool_cache", {})
- mcp_servers_db.create_server(id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True
+ )
calls: list[str] = []
@@ -649,7 +664,9 @@ def test_get_enabled_mcp_tools_does_not_cache_failures(tmp_path, monkeypatch):
monkeypatch.setattr(mcp_client, "_tool_cache", {})
monkeypatch.setattr(mcp_client, "_probe_cooloff_until", {})
- mcp_servers_db.create_server(id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True
+ )
attempts = {"n": 0}
@@ -684,7 +701,9 @@ def test_refresh_warms_tool_cache(tmp_path, monkeypatch):
import routes.mcp_servers as routes_mcp
monkeypatch.setattr(mcp_client, "_tool_cache", {})
- mcp_servers_db.create_server(id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True
+ )
async def fake_refresh(
url,
@@ -716,7 +735,9 @@ def test_update_url_evicts_tool_cache(tmp_path, monkeypatch):
import routes.mcp_servers as routes_mcp
monkeypatch.setattr(mcp_client, "_tool_cache", {"s1": _one_tool("stale")})
- mcp_servers_db.create_server(id = "s1", display_name = "A", url = "https://old/mcp", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "s1", display_name = "A", url = "https://old/mcp", is_enabled = True
+ )
asyncio.run(
routes_mcp.update_mcp_server(
@@ -737,10 +758,14 @@ def test_update_display_name_keeps_tool_cache(tmp_path, monkeypatch):
cached = _one_tool()
monkeypatch.setattr(mcp_client, "_tool_cache", {"s1": cached})
- mcp_servers_db.create_server(id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True
+ )
asyncio.run(
- routes_mcp.update_mcp_server("s1", McpServerUpdate(display_name = "B"), current_subject = "u")
+ routes_mcp.update_mcp_server(
+ "s1", McpServerUpdate(display_name = "B"), current_subject = "u"
+ )
)
assert mcp_client.get_cached_tools("s1") == cached
@@ -755,10 +780,14 @@ def test_update_disable_evicts_tool_cache(tmp_path, monkeypatch):
import routes.mcp_servers as routes_mcp
monkeypatch.setattr(mcp_client, "_tool_cache", {"s1": _one_tool()})
- mcp_servers_db.create_server(id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True
+ )
asyncio.run(
- routes_mcp.update_mcp_server("s1", McpServerUpdate(is_enabled = False), current_subject = "u")
+ routes_mcp.update_mcp_server(
+ "s1", McpServerUpdate(is_enabled = False), current_subject = "u"
+ )
)
assert mcp_client.get_cached_tools("s1") is None
@@ -772,7 +801,9 @@ def test_delete_evicts_tool_cache(tmp_path, monkeypatch):
import routes.mcp_servers as routes_mcp
monkeypatch.setattr(mcp_client, "_tool_cache", {"s1": _one_tool()})
- mcp_servers_db.create_server(id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True
+ )
asyncio.run(routes_mcp.delete_mcp_server("s1", current_subject = "u"))
assert mcp_client.get_cached_tools("s1") is None
@@ -795,8 +826,12 @@ def test_get_enabled_mcp_tools_probes_only_uncached(tmp_path, monkeypatch):
from core.inference import tools as tools_mod
monkeypatch.setattr(mcp_client, "_tool_cache", {"s1": _one_tool("cached")})
- mcp_servers_db.create_server(id = "s1", display_name = "A", url = "https://a/mcp", is_enabled = True)
- mcp_servers_db.create_server(id = "s2", display_name = "B", url = "https://b/mcp", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "s1", display_name = "A", url = "https://a/mcp", is_enabled = True
+ )
+ mcp_servers_db.create_server(
+ id = "s2", display_name = "B", url = "https://b/mcp", is_enabled = True
+ )
probed: list[str] = []
@@ -813,7 +848,10 @@ def test_get_enabled_mcp_tools_probes_only_uncached(tmp_path, monkeypatch):
specs = asyncio.run(tools_mod.get_enabled_mcp_tools())
assert probed == ["https://b/mcp"] # only the uncached server is probed
- assert sorted(t["function"]["name"] for t in specs) == ["mcp__s1__cached", "mcp__s2__fresh"]
+ assert sorted(t["function"]["name"] for t in specs) == [
+ "mcp__s1__cached",
+ "mcp__s2__fresh",
+ ]
def test_get_enabled_mcp_tools_partial_failure_caches_healthy(tmp_path, monkeypatch):
@@ -826,8 +864,12 @@ def test_get_enabled_mcp_tools_partial_failure_caches_healthy(tmp_path, monkeypa
monkeypatch.setattr(mcp_client, "_tool_cache", {})
monkeypatch.setattr(mcp_client, "_probe_cooloff_until", {})
- mcp_servers_db.create_server(id = "s1", display_name = "A", url = "https://bad/mcp", is_enabled = True)
- mcp_servers_db.create_server(id = "s2", display_name = "B", url = "https://good/mcp", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "s1", display_name = "A", url = "https://bad/mcp", is_enabled = True
+ )
+ mcp_servers_db.create_server(
+ id = "s2", display_name = "B", url = "https://good/mcp", is_enabled = True
+ )
async def fake(
url,
@@ -856,7 +898,9 @@ def test_get_enabled_mcp_tools_caches_empty_tool_list(tmp_path, monkeypatch):
from core.inference import tools as tools_mod
monkeypatch.setattr(mcp_client, "_tool_cache", {})
- mcp_servers_db.create_server(id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True
+ )
calls: list[str] = []
@@ -887,7 +931,9 @@ def test_update_headers_evicts_tool_cache(tmp_path, monkeypatch):
import routes.mcp_servers as routes_mcp
monkeypatch.setattr(mcp_client, "_tool_cache", {"s1": _one_tool()})
- mcp_servers_db.create_server(id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True
+ )
asyncio.run(
routes_mcp.update_mcp_server(
@@ -899,7 +945,9 @@ def test_update_headers_evicts_tool_cache(tmp_path, monkeypatch):
assert mcp_client.get_cached_tools("s1") is None
-def test_get_enabled_mcp_tools_skips_cache_when_config_changes_mid_probe(tmp_path, monkeypatch):
+def test_get_enabled_mcp_tools_skips_cache_when_config_changes_mid_probe(
+ tmp_path, monkeypatch
+):
"""A config edit landing during an in-flight probe must not be clobbered
by the now-stale probe result (TOCTOU on the cache write)."""
import asyncio
@@ -909,7 +957,9 @@ def test_get_enabled_mcp_tools_skips_cache_when_config_changes_mid_probe(tmp_pat
from core.inference import tools as tools_mod
monkeypatch.setattr(mcp_client, "_tool_cache", {})
- mcp_servers_db.create_server(id = "s1", display_name = "A", url = "https://old/mcp", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "s1", display_name = "A", url = "https://old/mcp", is_enabled = True
+ )
async def fake(
url,
@@ -942,7 +992,9 @@ def test_get_enabled_mcp_tools_no_cooloff_when_config_changes_mid_failed_probe(
monkeypatch.setattr(mcp_client, "_tool_cache", {})
monkeypatch.setattr(mcp_client, "_probe_cooloff_until", {})
- mcp_servers_db.create_server(id = "s1", display_name = "A", url = "https://old/mcp", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "s1", display_name = "A", url = "https://old/mcp", is_enabled = True
+ )
async def fake(
url,
@@ -974,7 +1026,9 @@ def test_get_enabled_mcp_tools_no_cooloff_when_server_deleted_mid_failed_probe(
monkeypatch.setattr(mcp_client, "_tool_cache", {})
monkeypatch.setattr(mcp_client, "_probe_cooloff_until", {})
- mcp_servers_db.create_server(id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True
+ )
async def fake(
url,
@@ -991,7 +1045,9 @@ def test_get_enabled_mcp_tools_no_cooloff_when_server_deleted_mid_failed_probe(
assert "s1" not in mcp_client._probe_cooloff_until # no orphan cool-off
-def test_get_enabled_mcp_tools_skips_failed_server_during_cooloff(tmp_path, monkeypatch):
+def test_get_enabled_mcp_tools_skips_failed_server_during_cooloff(
+ tmp_path, monkeypatch
+):
"""A down server is probed once, then skipped during the cool-off instead
of being re-probed (and re-hung) on every send."""
import asyncio
@@ -1002,7 +1058,9 @@ def test_get_enabled_mcp_tools_skips_failed_server_during_cooloff(tmp_path, monk
monkeypatch.setattr(mcp_client, "_tool_cache", {})
monkeypatch.setattr(mcp_client, "_probe_cooloff_until", {})
- mcp_servers_db.create_server(id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True
+ )
attempts = {"n": 0}
@@ -1043,7 +1101,10 @@ def test_oauth_failure_cools_off_longer_than_plain(monkeypatch):
monkeypatch.setattr(mcp_client, "_probe_cooloff_until", {})
mcp_client.record_probe_failure("plain", use_oauth = False)
mcp_client.record_probe_failure("oauth", use_oauth = True)
- assert mcp_client._probe_cooloff_until["oauth"] > mcp_client._probe_cooloff_until["plain"]
+ assert (
+ mcp_client._probe_cooloff_until["oauth"]
+ > mcp_client._probe_cooloff_until["plain"]
+ )
def test_invalidate_clears_failure_cooloff(monkeypatch):
@@ -1070,7 +1131,9 @@ def test_refresh_failure_records_cooloff(tmp_path, monkeypatch):
monkeypatch.setattr(mcp_client, "_tool_cache", {})
monkeypatch.setattr(mcp_client, "_probe_cooloff_until", {})
- mcp_servers_db.create_server(id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True
+ )
async def boom(
url,
@@ -1096,7 +1159,9 @@ def test_refresh_drops_result_when_config_changes_mid_probe(tmp_path, monkeypatc
import routes.mcp_servers as routes_mcp
monkeypatch.setattr(mcp_client, "_tool_cache", {})
- mcp_servers_db.create_server(id = "s1", display_name = "A", url = "https://old/mcp", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "s1", display_name = "A", url = "https://old/mcp", is_enabled = True
+ )
async def fake_refresh(
url,
@@ -1113,7 +1178,9 @@ def test_refresh_drops_result_when_config_changes_mid_probe(tmp_path, monkeypatc
assert mcp_client.get_cached_tools("s1") is None
-def test_refresh_failure_no_cooloff_when_config_changes_mid_probe(tmp_path, monkeypatch):
+def test_refresh_failure_no_cooloff_when_config_changes_mid_probe(
+ tmp_path, monkeypatch
+):
"""A manual refresh failure for an old config must not cool off the freshly
edited server."""
import asyncio
@@ -1124,7 +1191,9 @@ def test_refresh_failure_no_cooloff_when_config_changes_mid_probe(tmp_path, monk
monkeypatch.setattr(mcp_client, "_tool_cache", {})
monkeypatch.setattr(mcp_client, "_probe_cooloff_until", {})
- mcp_servers_db.create_server(id = "s1", display_name = "A", url = "https://old/mcp", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "s1", display_name = "A", url = "https://old/mcp", is_enabled = True
+ )
async def boom(
url,
@@ -1141,7 +1210,9 @@ def test_refresh_failure_no_cooloff_when_config_changes_mid_probe(tmp_path, monk
assert not mcp_client.in_failure_cooloff("s1")
-def test_get_enabled_mcp_tools_drops_result_when_server_deleted_mid_probe(tmp_path, monkeypatch):
+def test_get_enabled_mcp_tools_drops_result_when_server_deleted_mid_probe(
+ tmp_path, monkeypatch
+):
"""A delete landing while a probe is in flight must drop the now-orphan
result -- the `fresh is None` arm of the mid-probe TOCTOU guard. The
result is neither served nor cached under the since-removed id."""
@@ -1153,7 +1224,9 @@ def test_get_enabled_mcp_tools_drops_result_when_server_deleted_mid_probe(tmp_pa
monkeypatch.setattr(mcp_client, "_tool_cache", {})
monkeypatch.setattr(mcp_client, "_probe_cooloff_until", {})
- mcp_servers_db.create_server(id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "s1", display_name = "A", url = "https://x/mcp", is_enabled = True
+ )
async def fake(
url,
diff --git a/studio/backend/tests/test_mcp_stdio_improvements.py b/studio/backend/tests/test_mcp_stdio_improvements.py
index b0bfd45135..b997067034 100644
--- a/studio/backend/tests/test_mcp_stdio_improvements.py
+++ b/studio/backend/tests/test_mcp_stdio_improvements.py
@@ -62,7 +62,9 @@ def test_create_forces_oauth_off_for_stdio(tmp_path, monkeypatch):
_enable(monkeypatch)
resp = asyncio.run(
routes_mcp.create_mcp_server(
- McpServerCreate(display_name = "FS", url = "npx -y server /tmp", use_oauth = True),
+ McpServerCreate(
+ display_name = "FS", url = "npx -y server /tmp", use_oauth = True
+ ),
current_subject = "u",
)
)
@@ -92,8 +94,12 @@ def test_update_url_to_stdio_clears_oauth(tmp_path, monkeypatch):
_reset_db(tmp_path, monkeypatch)
_enable(monkeypatch)
monkeypatch.setattr(mcp_client, "_oauth_token_store", None)
- monkeypatch.setattr(routes_mcp, "clear_oauth_tokens_async", lambda *a, **k: asyncio.sleep(0))
- mcp_servers_db.create_server(id = "s1", display_name = "A", url = "https://a/mcp", use_oauth = True)
+ monkeypatch.setattr(
+ routes_mcp, "clear_oauth_tokens_async", lambda *a, **k: asyncio.sleep(0)
+ )
+ mcp_servers_db.create_server(
+ id = "s1", display_name = "A", url = "https://a/mcp", use_oauth = True
+ )
resp = asyncio.run(
routes_mcp.update_mcp_server(
"s1", McpServerUpdate(url = "npx -y server /tmp"), current_subject = "u"
@@ -142,7 +148,9 @@ def test_switch_keeps_explicitly_supplied_headers(tmp_path, monkeypatch):
resp = asyncio.run(
routes_mcp.update_mcp_server(
"s1",
- McpServerUpdate(url = "https://remote/mcp", headers = {"Authorization": "Bearer new"}),
+ McpServerUpdate(
+ url = "https://remote/mcp", headers = {"Authorization": "Bearer new"}
+ ),
current_subject = "u",
)
)
@@ -163,7 +171,9 @@ def test_same_transport_edit_keeps_headers(tmp_path, monkeypatch):
)
# editing only the display name (still stdio) must keep env vars
resp = asyncio.run(
- routes_mcp.update_mcp_server("s1", McpServerUpdate(display_name = "B"), current_subject = "u")
+ routes_mcp.update_mcp_server(
+ "s1", McpServerUpdate(display_name = "B"), current_subject = "u"
+ )
)
assert resp.headers == {"API_KEY": "secret"}
@@ -184,7 +194,9 @@ def test_validate_url_allows_url_in_argument(monkeypatch):
from routes.mcp_servers import _validate_url
_enable(monkeypatch)
# :// inside an ARGUMENT (not the first token) is a valid command
- assert _validate_url("npx server --url https://x/mcp") == ("npx server --url https://x/mcp")
+ assert _validate_url("npx server --url https://x/mcp") == (
+ "npx server --url https://x/mcp"
+ )
# ── P6: Data Recipe stdio path obeys the same host gate ─────────────
diff --git a/studio/backend/tests/test_mcp_stdio_pr5863.py b/studio/backend/tests/test_mcp_stdio_pr5863.py
index 9a3e8d6882..d821dde84b 100644
--- a/studio/backend/tests/test_mcp_stdio_pr5863.py
+++ b/studio/backend/tests/test_mcp_stdio_pr5863.py
@@ -84,7 +84,9 @@ def transport(monkeypatch):
monkeypatch.setattr(
mcp_client,
"_client",
- lambda url, headers, use_oauth = False: _RecordingClient(url, headers, use_oauth, recorder),
+ lambda url, headers, use_oauth = False: _RecordingClient(
+ url, headers, use_oauth, recorder
+ ),
)
return recorder
@@ -129,7 +131,9 @@ def test_parse_basic_argv():
def test_parse_keeps_url_argument_as_one_command():
# gemini "high": a :// inside an ARGUMENT must not break the command.
- assert mcp_client.parse_stdio_command("npx server --endpoint https://example.com/mcp") == [
+ assert mcp_client.parse_stdio_command(
+ "npx server --endpoint https://example.com/mcp"
+ ) == [
"npx",
"server",
"--endpoint",
@@ -160,7 +164,9 @@ def test_parse_windows_strips_wrapping_quotes(monkeypatch):
# gemini "medium": posix=False keeps backslash paths but also the
# wrapping quotes; the PR strips a matched pair so argv[0] is clean.
monkeypatch.setattr(sys, "platform", "win32")
- parts = mcp_client.parse_stdio_command(r'"C:\Program Files\node\node.exe" server.js')
+ parts = mcp_client.parse_stdio_command(
+ r'"C:\Program Files\node\node.exe" server.js'
+ )
assert parts[0] == r"C:\Program Files\node\node.exe"
assert parts[1] == "server.js"
@@ -242,10 +248,14 @@ def test_validate_url_gate_on_accepts_stdio(monkeypatch):
# http still works when stdio is on
assert _validate_url("https://x/mcp") == "https://x/mcp"
# url-bearing argument accepted as a command
- assert _validate_url("npx server --url https://x/mcp") == ("npx server --url https://x/mcp")
+ assert _validate_url("npx server --url https://x/mcp") == (
+ "npx server --url https://x/mcp"
+ )
# A lone token is ambiguous; accept it as a command rather than
# guessing it's a URL (no regression for single binaries).
- assert _validate_url("/usr/local/bin/my-mcp-server") == "/usr/local/bin/my-mcp-server"
+ assert (
+ _validate_url("/usr/local/bin/my-mcp-server") == "/usr/local/bin/my-mcp-server"
+ )
assert _validate_url("mcp-server-sqlite") == "mcp-server-sqlite"
# empty / unparseable still rejected
for bad in [" ", '"unclosed']:
@@ -332,7 +342,9 @@ def test_refresh_route_gate(tmp_path, monkeypatch, transport):
assert transport == []
_enable(monkeypatch)
- res = asyncio.run(routes_mcp.refresh_mcp_server_tools("stdio1", current_subject = "u"))
+ res = asyncio.run(
+ routes_mcp.refresh_mcp_server_tools("stdio1", current_subject = "u")
+ )
assert res.ok and res.tool_count == 2
assert len(transport) == 1
@@ -343,7 +355,9 @@ def test_discovery_gate(tmp_path, monkeypatch, transport):
from core.inference.tools import get_enabled_mcp_tools
_reset_db(tmp_path, monkeypatch)
- mcp_servers_db.create_server(id = "stdio1", display_name = "FS", url = "npx server", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "stdio1", display_name = "FS", url = "npx server", is_enabled = True
+ )
_disable(monkeypatch)
assert asyncio.run(get_enabled_mcp_tools()) == []
@@ -359,7 +373,9 @@ def test_execute_gate(tmp_path, monkeypatch, transport):
from core.inference.tools import execute_tool
_reset_db(tmp_path, monkeypatch)
- mcp_servers_db.create_server(id = "stdio1", display_name = "FS", url = "npx server", is_enabled = True)
+ mcp_servers_db.create_server(
+ id = "stdio1", display_name = "FS", url = "npx server", is_enabled = True
+ )
_disable(monkeypatch)
out = execute_tool("mcp__stdio1__list_directory", {"path": "/tmp"})
diff --git a/studio/backend/tests/test_middleware.py b/studio/backend/tests/test_middleware.py
index 1005431926..56c5b6288f 100644
--- a/studio/backend/tests/test_middleware.py
+++ b/studio/backend/tests/test_middleware.py
@@ -165,7 +165,9 @@ class TestMaxBodyMiddleware:
assert r.status_code == 200
assert r.json()["total"] == 512
- def test_upload_passthrough_rejects_declared_body_over_dedicated_cap(self, main_module):
+ def test_upload_passthrough_rejects_declared_body_over_dedicated_cap(
+ self, main_module
+ ):
app = _make_protected_app(
128,
main_module,
@@ -267,7 +269,9 @@ class TestSecurityHeadersMiddleware:
csp = r.headers["content-security-policy"]
assert f"'nonce-{nonce}'" in csp
# Internal handoff header must not leak to clients.
- assert main_module._CSP_SCRIPT_NONCE_HEADER not in {k.lower() for k in r.headers.keys()}
+ assert main_module._CSP_SCRIPT_NONCE_HEADER not in {
+ k.lower() for k in r.headers.keys()
+ }
def test_build_csp_helper_shape(self, main_module):
plain = main_module._build_csp()
diff --git a/studio/backend/tests/test_mlx_training_worker_config.py b/studio/backend/tests/test_mlx_training_worker_config.py
index 4402031467..afd141563a 100644
--- a/studio/backend/tests/test_mlx_training_worker_config.py
+++ b/studio/backend/tests/test_mlx_training_worker_config.py
@@ -45,8 +45,12 @@ def _load_worker_module():
setattr(wheel_utils, name, lambda *_args, **_kwargs: None)
sys.modules["utils.wheel_utils"] = wheel_utils
- worker_path = Path(__file__).resolve().parents[1] / "core" / "training" / "worker.py"
- spec = importlib.util.spec_from_file_location("mlx_training_worker_under_test", worker_path)
+ worker_path = (
+ Path(__file__).resolve().parents[1] / "core" / "training" / "worker.py"
+ )
+ spec = importlib.util.spec_from_file_location(
+ "mlx_training_worker_under_test", worker_path
+ )
module = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(module)
@@ -138,7 +142,9 @@ def test_mlx_vlm_resized_image_layout_probes_processor_contract():
== "chw"
)
assert (
- _mlx_vlm_resized_image_layout(types.SimpleNamespace(image_processor = HwcImageProcessor()))
+ _mlx_vlm_resized_image_layout(
+ types.SimpleNamespace(image_processor = HwcImageProcessor())
+ )
is None
)
@@ -157,7 +163,9 @@ def test_mlx_vlm_layout_probe_copies_image_processor():
image_processor = StatefulImageProcessor()
- layout = _mlx_vlm_resized_image_layout(types.SimpleNamespace(image_processor = image_processor))
+ layout = _mlx_vlm_resized_image_layout(
+ types.SimpleNamespace(image_processor = image_processor)
+ )
assert layout == "chw"
assert image_processor.calls == 0
diff --git a/studio/backend/tests/test_models_get_model_config_case_resolution.py b/studio/backend/tests/test_models_get_model_config_case_resolution.py
index 417ec74d17..3481e29948 100644
--- a/studio/backend/tests/test_models_get_model_config_case_resolution.py
+++ b/studio/backend/tests/test_models_get_model_config_case_resolution.py
@@ -50,7 +50,9 @@ def test_get_model_config_resolves_cached_case_before_model_checks(monkeypatch):
return _DummyModelConfig()
monkeypatch.setattr(models_route, "is_local_path", lambda _: False)
- monkeypatch.setattr(models_route, "resolve_cached_repo_id_case", lambda _: "Org/Model")
+ monkeypatch.setattr(
+ models_route, "resolve_cached_repo_id_case", lambda _: "Org/Model"
+ )
monkeypatch.setattr(models_route, "load_model_defaults", _record_load)
monkeypatch.setattr(models_route, "is_vision_model", _record_vision)
monkeypatch.setattr(models_route, "is_embedding_model", _record_embedding)
diff --git a/studio/backend/tests/test_mtp_drafter_companion.py b/studio/backend/tests/test_mtp_drafter_companion.py
index d5e8d13652..6451be2901 100644
--- a/studio/backend/tests/test_mtp_drafter_companion.py
+++ b/studio/backend/tests/test_mtp_drafter_companion.py
@@ -201,7 +201,9 @@ def test_detect_mtp_file_search_root(tmp_path):
sub.mkdir()
(sub / "gemma-4-12b-it-Q4_K_M.gguf").write_bytes(b"x")
(tmp_path / "mtp-gemma-4-12b-it.gguf").write_bytes(b"x")
- found = detect_mtp_file(str(sub / "gemma-4-12b-it-Q4_K_M.gguf"), search_root = str(tmp_path))
+ found = detect_mtp_file(
+ str(sub / "gemma-4-12b-it-Q4_K_M.gguf"), search_root = str(tmp_path)
+ )
assert found is not None and found.endswith("mtp-gemma-4-12b-it.gguf")
diff --git a/studio/backend/tests/test_multimodal_document.py b/studio/backend/tests/test_multimodal_document.py
index 5cd7c876cc..3d38d40b41 100644
--- a/studio/backend/tests/test_multimodal_document.py
+++ b/studio/backend/tests/test_multimodal_document.py
@@ -309,7 +309,11 @@ def test_openai_base64_pdf_becomes_input_file(monkeypatch):
user_msg = captured["body"]["input"][0]
parts = user_msg["content"]
fileblk = next(p for p in parts if p.get("type") == "input_file")
- assert fileblk == {"type": "input_file", "file_data": _PDF_DATA_URI, "filename": "paper.pdf"}
+ assert fileblk == {
+ "type": "input_file",
+ "file_data": _PDF_DATA_URI,
+ "filename": "paper.pdf",
+ }
def test_openai_url_pdf_becomes_input_file(monkeypatch):
@@ -490,7 +494,9 @@ def test_build_external_messages_passes_input_document_for_anthropic_and_openai(
)
]
for provider in ("anthropic", "openai"):
- out = _build_external_messages(msgs, supports_vision = True, provider_type = provider)
+ out = _build_external_messages(
+ msgs, supports_vision = True, provider_type = provider
+ )
assert len(out) == 1, (provider, out)
parts = out[0]["content"]
assert parts[0] == {"type": "text", "text": "summarise"}, provider
@@ -526,7 +532,9 @@ def test_build_external_messages_strips_input_document_for_unmapped_providers():
)
]
for provider in ("gemini", "mistral", "kimi", "openrouter", "deepseek", "qwen"):
- out = _build_external_messages(msgs, supports_vision = True, provider_type = provider)
+ out = _build_external_messages(
+ msgs, supports_vision = True, provider_type = provider
+ )
assert len(out) == 1, (provider, out)
parts = out[0]["content"]
types = [p.get("type") for p in parts if isinstance(p, dict)]
diff --git a/studio/backend/tests/test_native_context_length.py b/studio/backend/tests/test_native_context_length.py
index de1ca0649e..ba2337755d 100644
--- a/studio/backend/tests/test_native_context_length.py
+++ b/studio/backend/tests/test_native_context_length.py
@@ -334,7 +334,9 @@ class TestPydanticModels:
def test_status_response_chat_template_roundtrip(self):
"""chat_template serializes and validates as part of status."""
resp = InferenceStatusResponse(chat_template = "{{ messages }}")
- roundtripped = InferenceStatusResponse.model_validate_json(resp.model_dump_json())
+ roundtripped = InferenceStatusResponse.model_validate_json(
+ resp.model_dump_json()
+ )
assert roundtripped.chat_template == "{{ messages }}"
def test_roundtrip_preserves_value(self):
@@ -402,7 +404,9 @@ class TestRouteCompleteness:
def test_gguf_load_responses_have_field(self):
"""Every GGUF LoadResponse (is_gguf = True) includes native_context_length."""
blocks = self._find_construction_blocks("LoadResponse")
- gguf_blocks = [b for b in blocks if "is_gguf = True" in b or "is_gguf=True" in b]
+ gguf_blocks = [
+ b for b in blocks if "is_gguf = True" in b or "is_gguf=True" in b
+ ]
assert (
len(gguf_blocks) >= 2
), f"Expected at least 2 GGUF LoadResponse blocks, found {len(gguf_blocks)}"
@@ -414,7 +418,9 @@ class TestRouteCompleteness:
def test_non_gguf_load_responses_omit_field(self):
"""Non-GGUF LoadResponse blocks do not set native_context_length (defaults to None)."""
blocks = self._find_construction_blocks("LoadResponse")
- non_gguf = [b for b in blocks if "is_gguf = True" not in b and "is_gguf=True" not in b]
+ non_gguf = [
+ b for b in blocks if "is_gguf = True" not in b and "is_gguf=True" not in b
+ ]
# Non-GGUF paths shouldn't reference native_context_length
# (Pydantic defaults it to None, so omitting it is correct).
for block in non_gguf:
@@ -425,7 +431,9 @@ class TestRouteCompleteness:
def test_non_gguf_load_responses_set_runtime_context_length(self):
"""Non-GGUF LoadResponse blocks report runtime context_length."""
blocks = self._find_construction_blocks("LoadResponse")
- non_gguf = [b for b in blocks if "is_gguf = True" not in b and "is_gguf=True" not in b]
+ non_gguf = [
+ b for b in blocks if "is_gguf = True" not in b and "is_gguf=True" not in b
+ ]
assert non_gguf, "Expected at least one non-GGUF LoadResponse block"
for block in non_gguf:
assert (
@@ -440,9 +448,7 @@ class TestRouteCompleteness:
if "llama_backend" in block and "native_context_length" in block:
found = True
break
- assert (
- found
- ), "No InferenceStatusResponse block with llama_backend has native_context_length"
+ assert found, "No InferenceStatusResponse block with llama_backend has native_context_length"
def test_non_gguf_status_path_reports_runtime_context_length(self):
"""Non-GGUF InferenceStatusResponse reports context_length from model_info."""
diff --git a/studio/backend/tests/test_offline_gguf_cache_fallback.py b/studio/backend/tests/test_offline_gguf_cache_fallback.py
index aab58adfff..17916155d2 100644
--- a/studio/backend/tests/test_offline_gguf_cache_fallback.py
+++ b/studio/backend/tests/test_offline_gguf_cache_fallback.py
@@ -134,7 +134,8 @@ def _siblings(items: dict[str, int]):
"""Mock ``hf_model_info(...).siblings`` payload."""
return _types.SimpleNamespace(
siblings = [
- _types.SimpleNamespace(rfilename = name, size = size) for name, size in items.items()
+ _types.SimpleNamespace(rfilename = name, size = size)
+ for name, size in items.items()
],
)
@@ -158,8 +159,12 @@ class TestIterHfCacheSnapshots:
assert list(_iter_hf_cache_snapshots("unsloth/bare")) == []
def test_yields_newest_first(self, hf_cache):
- old = _build_cache(hf_cache, "unsloth/multi", {"x.gguf": 1}, snapshot_sha = "a" * 40)
- new = _build_cache(hf_cache, "unsloth/multi", {"y.gguf": 1}, snapshot_sha = "b" * 40)
+ old = _build_cache(
+ hf_cache, "unsloth/multi", {"x.gguf": 1}, snapshot_sha = "a" * 40
+ )
+ new = _build_cache(
+ hf_cache, "unsloth/multi", {"y.gguf": 1}, snapshot_sha = "b" * 40
+ )
os.utime(old, (1000, 1000))
os.utime(new, (2000, 2000))
out = list(_iter_hf_cache_snapshots("unsloth/multi"))
@@ -198,7 +203,9 @@ class TestListGgufVariantsFromCache:
class TestListGgufVariantsOffline:
- def test_offline_env_short_circuits_api(self, hf_cache, clean_offline_env, monkeypatch):
+ def test_offline_env_short_circuits_api(
+ self, hf_cache, clean_offline_env, monkeypatch
+ ):
_build_cache(hf_cache, "unsloth/a", {"a-UD-Q4_K_XL.gguf": 1})
monkeypatch.setenv("HF_HUB_OFFLINE", "1")
@@ -274,7 +281,9 @@ class TestDetectGgufFromCache:
class TestDetectGgufModelRemoteOffline:
- def test_offline_env_short_circuits_retries(self, hf_cache, clean_offline_env, monkeypatch):
+ def test_offline_env_short_circuits_retries(
+ self, hf_cache, clean_offline_env, monkeypatch
+ ):
_build_cache(hf_cache, "unsloth/a", {"a-Q4_K_M.gguf": 1})
monkeypatch.setenv("HF_HUB_OFFLINE", "1")
@@ -298,7 +307,9 @@ class TestDetectGgufModelRemoteOffline:
out = detect_gguf_model_remote("unsloth/a")
assert out == "a-Q4_K_M.gguf"
- def test_repository_not_found_does_not_consult_cache(self, hf_cache, clean_offline_env):
+ def test_repository_not_found_does_not_consult_cache(
+ self, hf_cache, clean_offline_env
+ ):
# Cache has a file but the API says the repo is gone.
_build_cache(hf_cache, "unsloth/a", {"a-Q4_K_M.gguf": 1})
@@ -393,7 +404,9 @@ class TestHfOfflineIfDnsDead:
assert did_set is False
assert "HF_HUB_OFFLINE" not in os.environ
- def test_user_set_hf_hub_offline_is_preserved(self, dns, clean_offline_env, monkeypatch):
+ def test_user_set_hf_hub_offline_is_preserved(
+ self, dns, clean_offline_env, monkeypatch
+ ):
# User explicitly set offline before launching Studio.
monkeypatch.setenv("HF_HUB_OFFLINE", "1")
dns.fail()
@@ -403,7 +416,9 @@ class TestHfOfflineIfDnsDead:
# Helper must not pop a variable it did not set.
assert os.environ.get("HF_HUB_OFFLINE") == "1"
- def test_user_set_transformers_offline_is_preserved(self, dns, clean_offline_env, monkeypatch):
+ def test_user_set_transformers_offline_is_preserved(
+ self, dns, clean_offline_env, monkeypatch
+ ):
monkeypatch.setenv("TRANSFORMERS_OFFLINE", "1")
dns.fail()
with _hf_offline_if_dns_dead():
@@ -447,7 +462,9 @@ class TestDownloadMmprojOfflineCacheFallback:
"""``_download_mmproj`` must resolve cached mmproj GGUFs offline, like
``_download_gguf``; else the offline vision load returns None despite a cache hit."""
- def test_cache_lookup_returns_cached_mmproj_when_list_repo_files_fails(self, hf_cache):
+ def test_cache_lookup_returns_cached_mmproj_when_list_repo_files_fails(
+ self, hf_cache
+ ):
_build_cache(
hf_cache,
"unsloth/vision-GGUF",
@@ -606,7 +623,9 @@ class TestListGgufVariantsPermanentErrors:
list_gguf_variants("u/gated-gguf")
assert type(exc_info.value).__name__ == "GatedRepoError"
- def test_transient_error_still_falls_back_to_cache(self, hf_cache, clean_offline_env):
+ def test_transient_error_still_falls_back_to_cache(
+ self, hf_cache, clean_offline_env
+ ):
from utils.models.model_config import list_gguf_variants
_build_cache(hf_cache, "u/transient-gguf", {"foo-Q4_K_M.gguf": 1})
diff --git a/studio/backend/tests/test_offline_inference_parent.py b/studio/backend/tests/test_offline_inference_parent.py
index 71331220d6..a116671ae1 100644
--- a/studio/backend/tests/test_offline_inference_parent.py
+++ b/studio/backend/tests/test_offline_inference_parent.py
@@ -115,7 +115,9 @@ class TestTransformersVersionOfflineShortCircuits:
with patch("urllib.request.urlopen", boom):
assert _check_tokenizer_config_needs_v5(unique) is False
- def test_config_550_skips_urllib_when_offline(self, monkeypatch, clean_offline_env, tmp_path):
+ def test_config_550_skips_urllib_when_offline(
+ self, monkeypatch, clean_offline_env, tmp_path
+ ):
monkeypatch.setenv("HF_HUB_OFFLINE", "1")
unique = f"unsloth/never-cached-{tmp_path.name}-cfg"
diff --git a/studio/backend/tests/test_openai_citation_markers_edge.py b/studio/backend/tests/test_openai_citation_markers_edge.py
index f44975ce33..d8bf0ad5c1 100644
--- a/studio/backend/tests/test_openai_citation_markers_edge.py
+++ b/studio/backend/tests/test_openai_citation_markers_edge.py
@@ -319,7 +319,9 @@ def test_split_helper_buffers_only_after_last_open_byte():
assert head == f"pre {complete} mid "
assert tail == partial
# Head, once rewritten, drops every private-use byte.
- rewritten = _replace_openai_citation_markers(head, [{"source_id": "done", "url": "https://d"}])
+ rewritten = _replace_openai_citation_markers(
+ head, [{"source_id": "done", "url": "https://d"}]
+ )
assert rewritten == "pre [[1]](https://d) mid "
diff --git a/studio/backend/tests/test_openai_code_execution.py b/studio/backend/tests/test_openai_code_execution.py
index 63e94613ed..8488979de3 100644
--- a/studio/backend/tests/test_openai_code_execution.py
+++ b/studio/backend/tests/test_openai_code_execution.py
@@ -250,7 +250,11 @@ def test_shell_call_emits_tool_start_and_end(monkeypatch):
assert starts[0]["tool_call_id"] == "scall_1"
# `_server_tool: True` marks a synthetic builtin so the frontend can tell
# hosted tools from user-declared functions on history replay.
- assert starts[0]["arguments"] == {"kind": "bash", "command": "ls -la", "_server_tool": True}
+ assert starts[0]["arguments"] == {
+ "kind": "bash",
+ "command": "ls -la",
+ "_server_tool": True,
+ }
assert ends[0]["tool_call_id"] == "scall_1"
assert "total 24" in ends[0]["result"]
@@ -509,5 +513,7 @@ def test_expired_container_retries_only_once(monkeypatch):
# Exactly two calls (first + one retry); a third would be a loop.
assert call_count["n"] == 2
# The second failure surfaces normally as an error SSE line.
- error_lines = [line for line in lines if '"error"' in line and "_toolEvent" not in line]
+ error_lines = [
+ line for line in lines if '"error"' in line and "_toolEvent" not in line
+ ]
assert len(error_lines) >= 1
diff --git a/studio/backend/tests/test_openai_container_crud.py b/studio/backend/tests/test_openai_container_crud.py
index e0604527fd..33280fd95a 100644
--- a/studio/backend/tests/test_openai_container_crud.py
+++ b/studio/backend/tests/test_openai_container_crud.py
@@ -79,7 +79,9 @@ def test_create_sends_openai_beta_header(monkeypatch):
return httpx.Response(200, json = {"id": "cntr_new", "name": "analysis"})
_mock_http_client(monkeypatch, handler)
- result = _drive(_make_client().create_openai_container(name = "analysis", ttl_minutes = 30))
+ result = _drive(
+ _make_client().create_openai_container(name = "analysis", ttl_minutes = 30)
+ )
assert result == {"id": "cntr_new", "name": "analysis"}
assert seen["headers"].get("openai-beta") == "containers=v1"
diff --git a/studio/backend/tests/test_openai_responses_translation.py b/studio/backend/tests/test_openai_responses_translation.py
index f7d7e83a43..eb09243a39 100644
--- a/studio/backend/tests/test_openai_responses_translation.py
+++ b/studio/backend/tests/test_openai_responses_translation.py
@@ -494,7 +494,8 @@ def test_responses_response_incomplete_maps_to_length_finish_reason(monkeypatch)
finish_reasons = [
json.loads(line[len("data:") :].strip())["choices"][0]["finish_reason"]
for line in lines
- if line.startswith("data:") and line[len("data:") :].strip() not in ("", "[DONE]")
+ if line.startswith("data:")
+ and line[len("data:") :].strip() not in ("", "[DONE]")
]
assert "length" in finish_reasons
@@ -726,7 +727,8 @@ def test_responses_reasoning_summary_wrapped_in_think_tags(monkeypatch):
data_lines = [
line[len("data:") :].strip()
for line in lines
- if line.startswith("data:") and line[len("data:") :].strip() not in ("", "[DONE]")
+ if line.startswith("data:")
+ and line[len("data:") :].strip() not in ("", "[DONE]")
]
payloads = [json.loads(raw) for raw in data_lines]
combined = "".join(
diff --git a/studio/backend/tests/test_openai_tool_passthrough.py b/studio/backend/tests/test_openai_tool_passthrough.py
index b54f4c130d..5c408f7e01 100644
--- a/studio/backend/tests/test_openai_tool_passthrough.py
+++ b/studio/backend/tests/test_openai_tool_passthrough.py
@@ -267,7 +267,10 @@ class TestChatCompletionRequestToolFields:
assert self._make(stop = "\nUser:").stop == "\nUser:"
def test_stop_list(self):
- assert self._make(stop = ["\nUser:", "\nAssistant:"]).stop == ["\nUser:", "\nAssistant:"]
+ assert self._make(stop = ["\nUser:", "\nAssistant:"]).stop == [
+ "\nUser:",
+ "\nAssistant:",
+ ]
def test_tools_default_none(self):
req = self._make()
@@ -307,7 +310,9 @@ class TestChatCompletionRequestToolFields:
req = self._make()
assert req.stream is False
- def test_post_without_stream_field_decodes_to_stream_false_over_http(self, monkeypatch):
+ def test_post_without_stream_field_decodes_to_stream_false_over_http(
+ self, monkeypatch
+ ):
# Wire-level guard: a POST body omitting `stream` must deserialise to
# stream=False and return application/json, never text/event-stream.
# Mounts the real router to catch middleware/aliasing regressions;
@@ -357,9 +362,13 @@ class TestChatCompletionRequestToolFields:
from auth.authentication import get_current_subject
from utils.api_errors import install_api_error_handlers
- monkeypatch.setattr(inference_route, "get_llama_cpp_backend", lambda: llama_backend)
+ monkeypatch.setattr(
+ inference_route, "get_llama_cpp_backend", lambda: llama_backend
+ )
if inference_backend is not None:
- monkeypatch.setattr(inference_route, "get_inference_backend", lambda: inference_backend)
+ monkeypatch.setattr(
+ inference_route, "get_inference_backend", lambda: inference_backend
+ )
app = FastAPI()
app.include_router(inference_route.router, prefix = "/v1")
@@ -514,7 +523,9 @@ class TestChatCompletionRequestToolFields:
)
self._assert_unsupported_n(resp)
- def test_confirm_tool_calls_requires_streaming_for_safetensors_tools(self, monkeypatch):
+ def test_confirm_tool_calls_requires_streaming_for_safetensors_tools(
+ self, monkeypatch
+ ):
import routes.inference as inference_route
class _NoGGUFBackend:
@@ -611,7 +622,9 @@ class TestAnthropicToolChoiceToOpenAI:
assert anthropic_tool_choice_to_openai({"type": "none"}) == "none"
def test_tool_named(self):
- result = anthropic_tool_choice_to_openai({"type": "tool", "name": "get_weather"})
+ result = anthropic_tool_choice_to_openai(
+ {"type": "tool", "name": "get_weather"}
+ )
assert result == {"type": "function", "function": {"name": "get_weather"}}
def test_tool_missing_name_returns_none(self):
@@ -856,11 +869,16 @@ class TestOpenAICompatibilityHelpers:
usage = {"prompt_tokens": 3, "completion_tokens": 2, "total_tokens": 5}
payload = SimpleNamespace(stream_options = None)
assert (
- _openai_stream_usage_chunk(payload, "chatcmpl-test", 123, "model", usage, None) is None
+ _openai_stream_usage_chunk(
+ payload, "chatcmpl-test", 123, "model", usage, None
+ )
+ is None
)
payload.stream_options = {"include_usage": True}
- line = _openai_stream_usage_chunk(payload, "chatcmpl-test", 123, "model", usage, None)
+ line = _openai_stream_usage_chunk(
+ payload, "chatcmpl-test", 123, "model", usage, None
+ )
assert line is not None
assert '"choices":[]' in line
assert '"usage"' in line
@@ -895,7 +913,9 @@ class TestOpenAICompatibilityHelpers:
if message.role == "developer":
message.role = "system"
- system_prompt, chat_messages, image_b64 = _extract_content_parts(payload.messages)
+ system_prompt, chat_messages, image_b64 = _extract_content_parts(
+ payload.messages
+ )
assert system_prompt == "original system\n\ndeveloper rules"
assert chat_messages == [{"role": "user", "content": "hi"}]
@@ -930,11 +950,15 @@ class TestFriendlyErrorHttpx:
def test_non_httpx_unchanged(self):
# Non-httpx exceptions still fall through to the substring heuristics
# — a context-size message must still produce "Message too long".
- ctx_msg = "request (4096 tokens) exceeds the available context size (2048 tokens)"
+ ctx_msg = (
+ "request (4096 tokens) exceeds the available context size (2048 tokens)"
+ )
assert "Message too long" in _friendly_error(ValueError(ctx_msg))
def test_generic_exception_returns_generic_message(self):
- assert _friendly_error(RuntimeError("unrelated")) == "An internal error occurred"
+ assert (
+ _friendly_error(RuntimeError("unrelated")) == "An internal error occurred"
+ )
from routes.inference import ( # noqa: E402
@@ -952,7 +976,10 @@ class TestDropEmptyAssistantSentinels:
{"role": "user", "content": "again"},
]
out = _drop_empty_assistant_sentinels(msgs)
- assert out == [{"role": "user", "content": "hi"}, {"role": "user", "content": "again"}]
+ assert out == [
+ {"role": "user", "content": "hi"},
+ {"role": "user", "content": "again"},
+ ]
def test_drops_assistant_with_no_content_key(self):
# exclude_none=True strips the content key entirely; filter must catch it.
@@ -962,7 +989,10 @@ class TestDropEmptyAssistantSentinels:
{"role": "user", "content": "ok"},
]
out = _drop_empty_assistant_sentinels(msgs)
- assert out == [{"role": "user", "content": "hi"}, {"role": "user", "content": "ok"}]
+ assert out == [
+ {"role": "user", "content": "hi"},
+ {"role": "user", "content": "ok"},
+ ]
def test_preserves_assistant_with_text(self):
msgs = [
@@ -1062,10 +1092,16 @@ class TestGgufVisionMessages:
messages, has_image = _openai_messages_for_gguf_chat(req, is_vision = True)
assert has_image is True
- assert messages[0]["content"][0] == {"type": "text", "text": "describe image one"}
+ assert messages[0]["content"][0] == {
+ "type": "text",
+ "text": "describe image one",
+ }
assert messages[0]["content"][1]["type"] == "image_url"
assert len(messages[0]["content"]) == 2
- assert messages[2]["content"][0] == {"type": "text", "text": "describe image two"}
+ assert messages[2]["content"][0] == {
+ "type": "text",
+ "text": "describe image two",
+ }
assert messages[2]["content"][1]["type"] == "image_url"
assert len(messages[2]["content"]) == 2
assert isinstance(messages[1]["content"], str)
@@ -1088,9 +1124,14 @@ class TestGgufVisionMessages:
messages, has_image = _openai_messages_for_gguf_chat(req, is_vision = True)
assert has_image is True
- assert messages[0]["content"][0] == {"type": "text", "text": "describe this image"}
+ assert messages[0]["content"][0] == {
+ "type": "text",
+ "text": "describe this image",
+ }
assert messages[0]["content"][1]["type"] == "image_url"
- assert messages[0]["content"][1]["image_url"]["url"].startswith("data:image/png;base64,")
+ assert messages[0]["content"][1]["image_url"]["url"].startswith(
+ "data:image/png;base64,"
+ )
def test_rejects_image_parts_for_text_only_gguf(self):
req = ChatCompletionRequest(
@@ -1156,7 +1197,9 @@ class TestGgufVisionMessages:
{"role": "user", "content": "now"},
]
- updated = _set_or_prepend_system_message(messages, "Mid instructions.\n\nUse tools.")
+ updated = _set_or_prepend_system_message(
+ messages, "Mid instructions.\n\nUse tools."
+ )
assert [m["role"] for m in updated] == ["system", "user", "user"]
assert updated[0]["content"] == "Mid instructions.\n\nUse tools."
@@ -1216,7 +1259,9 @@ class TestGgufVisionToolRouting:
{
"type": "image_url",
"image_url": {
- "url": (f"data:image/png;base64,{TestGgufVisionMessages._PNG_B64}"),
+ "url": (
+ f"data:image/png;base64,{TestGgufVisionMessages._PNG_B64}"
+ ),
},
},
],
@@ -1225,7 +1270,9 @@ class TestGgufVisionToolRouting:
)
response = self._drive(
- openai_chat_completions(payload, request = self._Request(), current_subject = "test")
+ openai_chat_completions(
+ payload, request = self._Request(), current_subject = "test"
+ )
)
self._consume_response(response)
@@ -1268,7 +1315,9 @@ class TestGgufVisionToolRouting:
)
response = self._drive(
- openai_chat_completions(payload, request = self._Request(), current_subject = "test")
+ openai_chat_completions(
+ payload, request = self._Request(), current_subject = "test"
+ )
)
self._consume_response(response)
@@ -1323,7 +1372,11 @@ class TestGgufVisionToolRouting:
yield "done"
yield {
"type": "metadata",
- "usage": {"prompt_tokens": 3, "completion_tokens": 1, "total_tokens": 4},
+ "usage": {
+ "prompt_tokens": 3,
+ "completion_tokens": 1,
+ "total_tokens": 4,
+ },
"finish_reason": "stop",
}
@@ -1346,7 +1399,9 @@ class TestGgufVisionToolRouting:
)
self._drive(
- openai_chat_completions(payload, request = self._Request(), current_subject = "test")
+ openai_chat_completions(
+ payload, request = self._Request(), current_subject = "test"
+ )
)
assert captured["messages"] == [
@@ -1361,7 +1416,9 @@ class TestGgufVisionToolRouting:
(-1, [-1, -1, -1]),
],
)
- def test_gguf_n_choices_vary_explicit_non_negative_seed(self, monkeypatch, seed, expected):
+ def test_gguf_n_choices_vary_explicit_non_negative_seed(
+ self, monkeypatch, seed, expected
+ ):
import routes.inference as inf_mod
seen_seeds = []
@@ -1396,7 +1453,9 @@ class TestGgufVisionToolRouting:
)
response = self._drive(
- openai_chat_completions(payload, request = self._Request(), current_subject = "test")
+ openai_chat_completions(
+ payload, request = self._Request(), current_subject = "test"
+ )
)
body = json.loads(response.body)
diff --git a/studio/backend/tests/test_pricing.py b/studio/backend/tests/test_pricing.py
index 8cd7796f14..198b9d453f 100644
--- a/studio/backend/tests/test_pricing.py
+++ b/studio/backend/tests/test_pricing.py
@@ -245,7 +245,9 @@ def test_openai_cache_read_subtracted_from_input_at_discount():
)
# 20k charged at full price, 80k charged at 0.1x
assert _isclose(out["input_usd"], 20_000 / 1_000_000.0 * base)
- assert _isclose(out["cache_read_usd"], 80_000 / 1_000_000.0 * base * OPENAI_CACHE_READ_MULT)
+ assert _isclose(
+ out["cache_read_usd"], 80_000 / 1_000_000.0 * base * OPENAI_CACHE_READ_MULT
+ )
def test_openai_billable_input_tokens_does_not_double_count_cache_read():
@@ -392,7 +394,9 @@ def test_openai_web_search_charged_per_thousand():
"openai_tool_use": {"web_search_requests": 250},
},
)
- assert _isclose(out["server_tools_usd"], 250 / 1_000.0 * OPENAI_WEB_SEARCH_USD_PER_1K)
+ assert _isclose(
+ out["server_tools_usd"], 250 / 1_000.0 * OPENAI_WEB_SEARCH_USD_PER_1K
+ )
assert _isclose(out["total_usd"], 250 / 1_000.0 * OPENAI_WEB_SEARCH_USD_PER_1K)
@@ -426,7 +430,8 @@ def test_openai_tool_surcharges_added_to_total():
expected_input = 100_000 / 1_000_000.0 * 5.0
expected_output = 5_000 / 1_000_000.0 * 30.0
expected_tools = (
- 3 / 1_000.0 * OPENAI_WEB_SEARCH_USD_PER_1K + 0.25 * OPENAI_CONTAINER_USD_PER_HOUR
+ 3 / 1_000.0 * OPENAI_WEB_SEARCH_USD_PER_1K
+ + 0.25 * OPENAI_CONTAINER_USD_PER_HOUR
)
assert _isclose(
out["total_usd"],
@@ -599,7 +604,10 @@ def test_openai_chat_style_envelope_reads_cache_from_prompt_tokens_details():
)
# Both envelopes must price identically.
assert _isclose(chat_style["input_usd"], raw["input_usd"]), (chat_style, raw)
- assert _isclose(chat_style["cache_read_usd"], raw["cache_read_usd"]), (chat_style, raw)
+ assert _isclose(chat_style["cache_read_usd"], raw["cache_read_usd"]), (
+ chat_style,
+ raw,
+ )
# 80k at 0.1x base, 20k at full.
assert _isclose(
chat_style["cache_read_usd"],
diff --git a/studio/backend/tests/test_pricing_edge.py b/studio/backend/tests/test_pricing_edge.py
index 1fcc428f90..6c7f4038f1 100644
--- a/studio/backend/tests/test_pricing_edge.py
+++ b/studio/backend/tests/test_pricing_edge.py
@@ -191,7 +191,9 @@ def test_anthropic_chat_cache_read_exceeds_prompt_no_negative_billable():
assert out["billable_input_tokens"] == 500 # 0 uncached + 500 cache_read
# cache_read still priced at the discount rate.
base = ANTHROPIC_PRICING["claude-opus-4-7"]["input_per_mtok"]
- assert _isclose(out["cache_read_usd"], 500 / 1_000_000.0 * base * ANTHROPIC_CACHE_READ_MULT)
+ assert _isclose(
+ out["cache_read_usd"], 500 / 1_000_000.0 * base * ANTHROPIC_CACHE_READ_MULT
+ )
def test_openai_raw_cached_tokens_exceeds_input_clamp_non_cached():
@@ -208,7 +210,9 @@ def test_openai_raw_cached_tokens_exceeds_input_clamp_non_cached():
)
assert out["input_usd"] == 0.0
# Cache read still priced (the 0.1x bucket).
- assert _isclose(out["cache_read_usd"], 500 / 1_000_000.0 * base * OPENAI_CACHE_READ_MULT)
+ assert _isclose(
+ out["cache_read_usd"], 500 / 1_000_000.0 * base * OPENAI_CACHE_READ_MULT
+ )
# ── long-context tier crosses on billable, including cache_creation ──
diff --git a/studio/backend/tests/test_providers_api.py b/studio/backend/tests/test_providers_api.py
index 5e24ed752d..dff539e8e6 100644
--- a/studio/backend/tests/test_providers_api.py
+++ b/studio/backend/tests/test_providers_api.py
@@ -211,7 +211,9 @@ class TestAuth:
json = {"username": USERNAME, "password": PASSWORD},
timeout = 10,
)
- assert resp.status_code == 200, f"Login failed ({resp.status_code}): {resp.text}"
+ assert (
+ resp.status_code == 200
+ ), f"Login failed ({resp.status_code}): {resp.text}"
body = resp.json()
assert body.get("access_token"), "access_token is missing or empty"
assert body.get("token_type") == "bearer"
@@ -221,7 +223,9 @@ class TestAuth:
class TestPublicKey:
- def test_public_key_is_valid_pem(self, auth_headers: dict[str, str], public_key_pem: str):
+ def test_public_key_is_valid_pem(
+ self, auth_headers: dict[str, str], public_key_pem: str
+ ):
"""GET /api/providers/public-key returns an importable RSA PEM key."""
pem_bytes = public_key_pem.encode("utf-8")
key = serialization.load_pem_public_key(pem_bytes)
@@ -243,7 +247,9 @@ class TestRegistry:
)
assert resp.status_code == 200, f"Registry failed: {resp.text}"
providers = resp.json()
- assert len(providers) == 9, f"Expected 9 providers, got {len(providers)}: {providers}"
+ assert (
+ len(providers) == 9
+ ), f"Expected 9 providers, got {len(providers)}: {providers}"
print(f"\n {'Provider':<12} {'Base URL'}")
print(f" {'-'*12} {'-'*45}")
for p in providers:
@@ -263,7 +269,9 @@ class TestRegistry:
def test_registry_entries_have_required_fields(self, auth_headers: dict[str, str]):
"""Each registry entry has provider_type, display_name, base_url, default_models."""
- resp = requests.get(_url("/api/providers/registry"), headers = auth_headers, timeout = 10)
+ resp = requests.get(
+ _url("/api/providers/registry"), headers = auth_headers, timeout = 10
+ )
assert resp.status_code == 200
for entry in resp.json():
for field in (
@@ -298,7 +306,9 @@ class TestProviderCRUD:
json = {"provider_type": "openai", "display_name": "Test OpenAI (pytest)"},
timeout = 10,
)
- assert resp.status_code == 201, f"Create failed ({resp.status_code}): {resp.text}"
+ assert (
+ resp.status_code == 201
+ ), f"Create failed ({resp.status_code}): {resp.text}"
body = resp.json()
assert body.get("id"), "No id in response"
assert body["provider_type"] == "openai"
@@ -309,7 +319,9 @@ class TestProviderCRUD:
def test_list_includes_created(self, auth_headers: dict[str, str]):
"""GET /api/providers/ includes the newly created config."""
- assert TestProviderCRUD._created_id, "No created_id (run test_create_provider first)"
+ assert (
+ TestProviderCRUD._created_id
+ ), "No created_id (run test_create_provider first)"
resp = requests.get(_url("/api/providers/"), headers = auth_headers, timeout = 10)
assert resp.status_code == 200
ids = [p["id"] for p in resp.json()]
@@ -328,7 +340,9 @@ class TestProviderCRUD:
json = {"display_name": new_name},
timeout = 10,
)
- assert resp.status_code == 200, f"Update failed ({resp.status_code}): {resp.text}"
+ assert (
+ resp.status_code == 200
+ ), f"Update failed ({resp.status_code}): {resp.text}"
assert resp.json()["display_name"] == new_name
print(f"\n updated display_name to '{new_name}'")
@@ -340,10 +354,14 @@ class TestProviderCRUD:
headers = auth_headers,
timeout = 10,
)
- assert resp.status_code == 204, f"Delete failed ({resp.status_code}): {resp.text}"
+ assert (
+ resp.status_code == 204
+ ), f"Delete failed ({resp.status_code}): {resp.text}"
# Confirm gone
- list_resp = requests.get(_url("/api/providers/"), headers = auth_headers, timeout = 10)
+ list_resp = requests.get(
+ _url("/api/providers/"), headers = auth_headers, timeout = 10
+ )
ids = [p["id"] for p in list_resp.json()]
assert TestProviderCRUD._created_id not in ids, "Deleted provider still in list"
print(f"\n deleted id={TestProviderCRUD._created_id} confirmed gone")
@@ -391,7 +409,9 @@ class TestProviderInference:
json = {"provider_type": provider_type, "encrypted_api_key": encrypted},
timeout = 30,
)
- assert resp.status_code == 200, f"Request failed ({resp.status_code}): {resp.text}"
+ assert (
+ resp.status_code == 200
+ ), f"Request failed ({resp.status_code}): {resp.text}"
body = resp.json()
assert (
body["success"] is True
@@ -415,7 +435,9 @@ class TestProviderInference:
json = {"provider_type": provider_type, "encrypted_api_key": encrypted},
timeout = 30,
)
- assert resp.status_code == 200, f"Request failed ({resp.status_code}): {resp.text}"
+ assert (
+ resp.status_code == 200
+ ), f"Request failed ({resp.status_code}): {resp.text}"
models = resp.json()
assert isinstance(models, list), f"Expected list, got {type(models)}"
assert len(models) > 0, f"No models returned for {provider_type}"
@@ -462,7 +484,9 @@ class TestProviderInference:
# ── TestVisionInference ─────────────────────────────────────────────
# Sloth photo for testing vision routing across providers
-_VISION_IMAGE_URL = "https://www.travelexcellence.com/images/where-to-see-sloths-in-costa-rica.jpg"
+_VISION_IMAGE_URL = (
+ "https://www.travelexcellence.com/images/where-to-see-sloths-in-costa-rica.jpg"
+)
_VISION_PARAMS = [
pytest.param(
@@ -562,6 +586,8 @@ class TestLocalInferenceUnaffected:
f"This likely means the provider fields broke the base request schema."
)
status_label = (
- "local model responded" if resp.status_code == 200 else "no model loaded (expected)"
+ "local model responded"
+ if resp.status_code == 200
+ else "no model loaded (expected)"
)
print(f"\n status={resp.status_code} ({status_label}) — local path unaffected")
diff --git a/studio/backend/tests/test_rag_captioning.py b/studio/backend/tests/test_rag_captioning.py
index 5d83a7d38d..b124c691f1 100644
--- a/studio/backend/tests/test_rag_captioning.py
+++ b/studio/backend/tests/test_rag_captioning.py
@@ -21,17 +21,28 @@ def test_caption_images_disabled_by_default(monkeypatch):
def test_caption_images_groups_by_page(monkeypatch):
monkeypatch.setattr(captioner.config, "CAPTION_IMAGES", True)
monkeypatch.setattr(captioner.config, "CAPTION_MAX_IMAGES", 8)
- monkeypatch.setattr(captioner, "_caption_one", lambda base, model, b, t: "a chart of results")
- out = captioner.caption_images([_img(1), _img(1), _img(3)], endpoint = ("http://x", "local"))
- assert out == {1: ["a chart of results", "a chart of results"], 3: ["a chart of results"]}
+ monkeypatch.setattr(
+ captioner, "_caption_one", lambda base, model, b, t: "a chart of results"
+ )
+ out = captioner.caption_images(
+ [_img(1), _img(1), _img(3)], endpoint = ("http://x", "local")
+ )
+ assert out == {
+ 1: ["a chart of results", "a chart of results"],
+ 3: ["a chart of results"],
+ }
def test_caption_images_respects_cap(monkeypatch):
monkeypatch.setattr(captioner.config, "CAPTION_IMAGES", True)
monkeypatch.setattr(captioner.config, "CAPTION_MAX_IMAGES", 2)
calls = []
- monkeypatch.setattr(captioner, "_caption_one", lambda *a: (calls.append(1) or "cap"))
- captioner.caption_images([_img(i) for i in range(5)], endpoint = ("http://x", "local"))
+ monkeypatch.setattr(
+ captioner, "_caption_one", lambda *a: (calls.append(1) or "cap")
+ )
+ captioner.caption_images(
+ [_img(i) for i in range(5)], endpoint = ("http://x", "local")
+ )
assert len(calls) == 2
diff --git a/studio/backend/tests/test_rag_chunking.py b/studio/backend/tests/test_rag_chunking.py
index 3d3c9eedd8..e562a9a59f 100644
--- a/studio/backend/tests/test_rag_chunking.py
+++ b/studio/backend/tests/test_rag_chunking.py
@@ -27,12 +27,16 @@ def test_chunk_never_exceeds_max_with_overlap_carry():
"""Overlap carry is trimmed so no chunk exceeds max_tokens (else the embedder overflows)."""
s1 = " ".join("a" for _ in range(10))
s2 = " ".join("b" for _ in range(95)) # near max
- chunks = chunk_pages([_page(f"{s1}. {s2}")], max_tokens = 100, overlap = 24, count = WORDS)
+ chunks = chunk_pages(
+ [_page(f"{s1}. {s2}")], max_tokens = 100, overlap = 24, count = WORDS
+ )
assert all(c.token_count <= 100 for c in chunks), [c.token_count for c in chunks]
def test_chunk_indices_are_sequential():
- chunks = chunk_pages([_page("alpha. " * 200)], max_tokens = 32, overlap = 0, count = WORDS)
+ chunks = chunk_pages(
+ [_page("alpha. " * 200)], max_tokens = 32, overlap = 0, count = WORDS
+ )
assert [c.chunk_index for c in chunks] == list(range(len(chunks)))
diff --git a/studio/backend/tests/test_rag_embed_llama_server.py b/studio/backend/tests/test_rag_embed_llama_server.py
index 8321068afd..6c63c0890f 100644
--- a/studio/backend/tests/test_rag_embed_llama_server.py
+++ b/studio/backend/tests/test_rag_embed_llama_server.py
@@ -52,8 +52,12 @@ def _mock_auto(monkeypatch, *, gpus, binary):
from core.inference.llama_cpp import LlamaCppBackend
monkeypatch.setattr(config, "EMBED_BACKEND", "auto")
- monkeypatch.setattr(LlamaCppBackend, "_get_gpu_free_memory", staticmethod(lambda: gpus))
- monkeypatch.setattr(LlamaCppBackend, "_find_llama_server_binary", staticmethod(lambda: binary))
+ monkeypatch.setattr(
+ LlamaCppBackend, "_get_gpu_free_memory", staticmethod(lambda: gpus)
+ )
+ monkeypatch.setattr(
+ LlamaCppBackend, "_find_llama_server_binary", staticmethod(lambda: binary)
+ )
def _stub_st_load(monkeypatch):
@@ -117,7 +121,9 @@ def test_llama_backend_imports_no_torch():
"RAG_EMBED_BACKEND": "llama-server",
"PYTHONPATH": str(backend_dir),
}
- proc = subprocess.run([sys.executable, "-c", code], capture_output = True, text = True, env = env)
+ proc = subprocess.run(
+ [sys.executable, "-c", code], capture_output = True, text = True, env = env
+ )
assert proc.returncode == 0, proc.stderr
assert "OK" in proc.stdout
@@ -163,16 +169,22 @@ def test_use_gpu_explicit_modes(monkeypatch):
def test_use_gpu_auto_follows_probe(monkeypatch):
b = LlamaServerBackend()
monkeypatch.setattr(config, "EMBED_DEVICE", "auto")
- monkeypatch.setattr(LlamaServerBackend, "_gpu_available", staticmethod(lambda: True))
+ monkeypatch.setattr(
+ LlamaServerBackend, "_gpu_available", staticmethod(lambda: True)
+ )
assert b._use_gpu() is True
- monkeypatch.setattr(LlamaServerBackend, "_gpu_available", staticmethod(lambda: False))
+ monkeypatch.setattr(
+ LlamaServerBackend, "_gpu_available", staticmethod(lambda: False)
+ )
assert b._use_gpu() is False
def test_use_gpu_sticky_cpu_fallback(monkeypatch):
b = LlamaServerBackend()
monkeypatch.setattr(config, "EMBED_DEVICE", "auto")
- monkeypatch.setattr(LlamaServerBackend, "_gpu_available", staticmethod(lambda: True))
+ monkeypatch.setattr(
+ LlamaServerBackend, "_gpu_available", staticmethod(lambda: True)
+ )
b._force_cpu = True # a prior GPU start failed
assert b._use_gpu() is False
@@ -183,11 +195,17 @@ def test_gpu_available_reuses_studio_probe(monkeypatch):
monkeypatch.setattr(uh, "is_apple_silicon", lambda: False)
# Ample free VRAM -> GPU; nearly full -> CPU; none -> CPU.
- monkeypatch.setattr(LlamaCppBackend, "_get_gpu_free_memory", staticmethod(lambda: [(0, 40000)]))
+ monkeypatch.setattr(
+ LlamaCppBackend, "_get_gpu_free_memory", staticmethod(lambda: [(0, 40000)])
+ )
assert LlamaServerBackend._gpu_available() is True
- monkeypatch.setattr(LlamaCppBackend, "_get_gpu_free_memory", staticmethod(lambda: [(0, 100)]))
+ monkeypatch.setattr(
+ LlamaCppBackend, "_get_gpu_free_memory", staticmethod(lambda: [(0, 100)])
+ )
assert LlamaServerBackend._gpu_available() is False
- monkeypatch.setattr(LlamaCppBackend, "_get_gpu_free_memory", staticmethod(lambda: []))
+ monkeypatch.setattr(
+ LlamaCppBackend, "_get_gpu_free_memory", staticmethod(lambda: [])
+ )
assert LlamaServerBackend._gpu_available() is False
@@ -205,9 +223,15 @@ def _patch_spawn_deps(
):
# Force CPU so spawn never depends on a host GPU.
monkeypatch.setattr(config, "EMBED_DEVICE", "cpu")
- monkeypatch.setattr(LlamaServerBackend, "_resolve_binary", lambda self: "/bin/llama-server")
- monkeypatch.setattr(LlamaServerBackend, "_resolve_model_path", lambda self: "/m/bge.gguf")
- monkeypatch.setattr(LlamaServerBackend, "_find_free_port", staticmethod(lambda: free_port))
+ monkeypatch.setattr(
+ LlamaServerBackend, "_resolve_binary", lambda self: "/bin/llama-server"
+ )
+ monkeypatch.setattr(
+ LlamaServerBackend, "_resolve_model_path", lambda self: "/m/bge.gguf"
+ )
+ monkeypatch.setattr(
+ LlamaServerBackend, "_find_free_port", staticmethod(lambda: free_port)
+ )
monkeypatch.setattr(mod.subprocess, "Popen", lambda *a, **k: proc)
@@ -239,7 +263,9 @@ def test_spawn_fails_loud_on_early_exit(monkeypatch):
def test_spawn_auto_falls_back_to_cpu_on_gpu_failure(monkeypatch):
monkeypatch.setattr(config, "EMBED_DEVICE", "auto")
- monkeypatch.setattr(LlamaServerBackend, "_gpu_available", staticmethod(lambda: True))
+ monkeypatch.setattr(
+ LlamaServerBackend, "_gpu_available", staticmethod(lambda: True)
+ )
b = LlamaServerBackend()
calls = []
@@ -311,7 +337,9 @@ def test_encode_empty_returns_zero_rows(monkeypatch):
def test_encode_rejects_count_mismatch(monkeypatch):
b = LlamaServerBackend()
monkeypatch.setattr(b, "_ensure_ready", lambda: None)
- monkeypatch.setattr(b, "_post", lambda p, pl: {"data": [{"index": 0, "embedding": [1.0]}]})
+ monkeypatch.setattr(
+ b, "_post", lambda p, pl: {"data": [{"index": 0, "embedding": [1.0]}]}
+ )
with pytest.raises(RuntimeError, match = "vectors for"):
b.encode(["a", "b"], normalize = False)
@@ -390,7 +418,9 @@ def test_post_restarts_once_on_connect_error(monkeypatch):
b._port = 9000
monkeypatch.setattr(b, "_ensure_ready", lambda: None)
restarts = {"n": 0}
- monkeypatch.setattr(b, "_restart", lambda: restarts.__setitem__("n", restarts["n"] + 1))
+ monkeypatch.setattr(
+ b, "_restart", lambda: restarts.__setitem__("n", restarts["n"] + 1)
+ )
attempts = {"n": 0}
@@ -423,7 +453,9 @@ def test_post_restarts_once_on_read_timeout(monkeypatch):
b._port = 9000
monkeypatch.setattr(b, "_ensure_ready", lambda: None)
restarts = {"n": 0}
- monkeypatch.setattr(b, "_restart", lambda: restarts.__setitem__("n", restarts["n"] + 1))
+ monkeypatch.setattr(
+ b, "_restart", lambda: restarts.__setitem__("n", restarts["n"] + 1)
+ )
attempts = {"n": 0}
diff --git a/studio/backend/tests/test_rag_embeddings.py b/studio/backend/tests/test_rag_embeddings.py
index 28a2f69426..0d22b6cd54 100644
--- a/studio/backend/tests/test_rag_embeddings.py
+++ b/studio/backend/tests/test_rag_embeddings.py
@@ -147,7 +147,9 @@ def _patch_llama_backend(monkeypatch, *, binary):
from core.inference.llama_cpp import LlamaCppBackend
from core.rag import embed_llama_server
- monkeypatch.setattr(LlamaCppBackend, "_find_llama_server_binary", staticmethod(lambda: binary))
+ monkeypatch.setattr(
+ LlamaCppBackend, "_find_llama_server_binary", staticmethod(lambda: binary)
+ )
monkeypatch.setattr(embed_llama_server, "LlamaServerBackend", _SentinelLlamaBackend)
@@ -189,7 +191,9 @@ class _BoomOnEncodeModel:
def test_st_encode_runtime_failure_switches_to_llama(monkeypatch):
# encode() blows up mid-run -> switch to llama-server and stay switched.
- monkeypatch.setattr(embeddings, "_get", lambda model_name = None: _BoomOnEncodeModel())
+ monkeypatch.setattr(
+ embeddings, "_get", lambda model_name = None: _BoomOnEncodeModel()
+ )
_patch_llama_backend(monkeypatch, binary = "/fake/llama-server")
calls = {}
@@ -203,7 +207,9 @@ def test_st_encode_runtime_failure_switches_to_llama(monkeypatch):
calls["used"] = True
return np.zeros((len(texts), 4), dtype = np.float32)
- monkeypatch.setattr(_SentinelLlamaBackend, "encode", _sentinel_encode, raising = False)
+ monkeypatch.setattr(
+ _SentinelLlamaBackend, "encode", _sentinel_encode, raising = False
+ )
embeddings._reset_backend()
out = embeddings.encode(["alpha", "beta"])
@@ -215,7 +221,9 @@ def test_st_encode_runtime_failure_switches_to_llama(monkeypatch):
def test_st_encode_failure_without_llama_binary_reraises(monkeypatch):
# No llama-server binary -> surface the encode error.
- monkeypatch.setattr(embeddings, "_get", lambda model_name = None: _BoomOnEncodeModel())
+ monkeypatch.setattr(
+ embeddings, "_get", lambda model_name = None: _BoomOnEncodeModel()
+ )
_patch_llama_backend(monkeypatch, binary = None)
embeddings._reset_backend()
with pytest.raises(RuntimeError, match = "CUDA error during encode"):
diff --git a/studio/backend/tests/test_rag_ingestion.py b/studio/backend/tests/test_rag_ingestion.py
index f0b71bc23b..ce2e418eba 100644
--- a/studio/backend/tests/test_rag_ingestion.py
+++ b/studio/backend/tests/test_rag_ingestion.py
@@ -39,7 +39,11 @@ def test_ingestion_lifecycle_pending_to_completed(rag_home, stub_embeddings, tmp
conn = rag_db.get_connection()
try:
- assert store.get_document(conn, doc_id)["status"] in {"pending", "running", "completed"}
+ assert store.get_document(conn, doc_id)["status"] in {
+ "pending",
+ "running",
+ "completed",
+ }
finally:
conn.close()
@@ -233,7 +237,9 @@ def test_ingestion_rejects_unsupported_ext(rag_home, stub_embeddings, tmp_path):
ingestion.start_ingestion(store.kb_scope("K1"), "K1", None, "doc.xyz", path)
-def test_ingestion_empty_doc_completes_with_zero_chunks(rag_home, stub_embeddings, tmp_path):
+def test_ingestion_empty_doc_completes_with_zero_chunks(
+ rag_home, stub_embeddings, tmp_path
+):
path = _write(tmp_path, "empty.txt", " \n ")
scope = store.kb_scope("K1")
doc_id, job_id = ingestion.start_ingestion(scope, "K1", None, "empty.txt", path)
@@ -249,7 +255,9 @@ def test_ingestion_empty_doc_completes_with_zero_chunks(rag_home, stub_embedding
reason = "set RAG_REAL_EMBEDDER=1 to run the real sentence-transformers test",
)
def test_ingestion_with_real_embedder(rag_home, tmp_path):
- path = _write(tmp_path, "doc.txt", "The Kestrel-9 turbine is rated at 9.5 megawatts.")
+ path = _write(
+ tmp_path, "doc.txt", "The Kestrel-9 turbine is rated at 9.5 megawatts."
+ )
scope = store.kb_scope("K1")
doc_id, job_id = ingestion.start_ingestion(scope, "K1", None, "doc.txt", path)
_drain(job_id)
@@ -260,7 +268,9 @@ def test_ingestion_with_real_embedder(rag_home, tmp_path):
conn = rag_db.get_connection()
try:
- hits = retrieval.retrieve_hybrid(conn, scope, "how much power does the turbine make?", k = 5)
+ hits = retrieval.retrieve_hybrid(
+ conn, scope, "how much power does the turbine make?", k = 5
+ )
assert hits and hits[0].chunk_id == f"{doc_id}:0"
finally:
conn.close()
diff --git a/studio/backend/tests/test_rag_preview.py b/studio/backend/tests/test_rag_preview.py
index e7f2a39792..68832fd20e 100644
--- a/studio/backend/tests/test_rag_preview.py
+++ b/studio/backend/tests/test_rag_preview.py
@@ -112,7 +112,9 @@ def test_preview_routes_and_signed_file(rag_home, stub_embeddings):
assert res
chunk_id = res[0]["chunkId"]
- pt = c.get(f"/api/rag/documents/{doc_id}/preview-target", params = {"chunk_id": chunk_id}).json()
+ pt = c.get(
+ f"/api/rag/documents/{doc_id}/preview-target", params = {"chunk_id": chunk_id}
+ ).json()
assert pt["mediaKind"] == "pdf"
assert pt["text"]
@@ -148,7 +150,9 @@ def test_locator_handles_midword_anchor_and_locates_line():
doc = pymupdf.open()
page = doc.new_page()
- page.insert_text((72, 200), "alpha beta gamma delta epsilon zeta eta theta", fontsize = 12)
+ page.insert_text(
+ (72, 200), "alpha beta gamma delta epsilon zeta eta theta", fontsize = 12
+ )
page_text = doc[0].get_text("text") # mirrors what the parser stores
start = page_text.index("lpha")
end = page_text.index("theta") + 3
@@ -170,5 +174,7 @@ def test_sign_verify_roundtrip(rag_home):
tok = rag_routes._sign_document("doc-123")
assert rag_routes._verify_document_token(tok) == "doc-123"
- assert rag_routes._verify_document_token("doc-123.0.deadbeef") is None # expired/bad
+ assert (
+ rag_routes._verify_document_token("doc-123.0.deadbeef") is None
+ ) # expired/bad
assert rag_routes._verify_document_token("garbage") is None
diff --git a/studio/backend/tests/test_rag_retrieval.py b/studio/backend/tests/test_rag_retrieval.py
index 69d9e90871..6cad207bb5 100644
--- a/studio/backend/tests/test_rag_retrieval.py
+++ b/studio/backend/tests/test_rag_retrieval.py
@@ -57,7 +57,9 @@ def _add_doc(
text,
page = None,
):
- store.create_document(conn, scope = scope, filename = filename, sha256 = sha, document_id = doc_id)
+ store.create_document(
+ conn, scope = scope, filename = filename, sha256 = sha, document_id = doc_id
+ )
store.add_chunks(conn, scope, doc_id, [_chunk(text, 0, page)], [_embed(text)])
@@ -150,7 +152,9 @@ def test_tool_formats_chunks_and_sources(rag_conn, bow_embeddings, monkeypatch):
def test_tool_kb_scope_retrieves_from_db(rag_conn, bow_embeddings):
# End-to-end (no retrieve stub): doc found via its scope_kb_id (#8).
_add_doc(rag_conn, "kb_K", "d1", "kb.pdf", "h1", "alpha bravo charlie", page = 1)
- text, sources = tool.search_knowledge_base_with_sources(query = "alpha bravo", scope_kb_id = "K")
+ text, sources = tool.search_knowledge_base_with_sources(
+ query = "alpha bravo", scope_kb_id = "K"
+ )
assert "No matching chunks" not in text
assert sources and sources[0]["chunkId"] == "d1:0"
assert sources[0]["filename"] == "kb.pdf"
@@ -192,11 +196,15 @@ def test_dispatcher_no_sentinel_when_no_hits(rag_home, monkeypatch):
assert tools.RAG_SOURCES_SENTINEL not in out
-def test_search_for_autoinject_gates_on_dense_score(rag_conn, bow_embeddings, monkeypatch):
+def test_search_for_autoinject_gates_on_dense_score(
+ rag_conn, bow_embeddings, monkeypatch
+):
_add_doc(rag_conn, "kb_a", "d1", "paper.pdf", "h1", "body text here", page = 3)
def _hits(score, **kw):
- return lambda conn, scope, q, **k: [retrieval.Hit("d1:0", 1.0, **{kw["key"]: score})]
+ return lambda conn, scope, q, **k: [
+ retrieval.Hit("d1:0", 1.0, **{kw["key"]: score})
+ ]
# Strong dense hit -> injected.
monkeypatch.setattr(retrieval, "retrieve_hybrid", _hits(0.8, key = "dense_score"))
@@ -207,14 +215,22 @@ def test_search_for_autoinject_gates_on_dense_score(rag_conn, bow_embeddings, mo
# Dense below floor -> nothing injected.
monkeypatch.setattr(retrieval, "retrieve_hybrid", _hits(0.30, key = "dense_score"))
- assert tool.search_for_autoinject(query = "q", scope_kb_id = "a", min_dense_score = 0.55) is None
+ assert (
+ tool.search_for_autoinject(query = "q", scope_kb_id = "a", min_dense_score = 0.55)
+ is None
+ )
# Lexical-only hit (no dense score) does not auto-inject.
monkeypatch.setattr(retrieval, "retrieve_hybrid", _hits(1.0, key = "lexical_score"))
- assert tool.search_for_autoinject(query = "q", scope_kb_id = "a", min_dense_score = 0.55) is None
+ assert (
+ tool.search_for_autoinject(query = "q", scope_kb_id = "a", min_dense_score = 0.55)
+ is None
+ )
-def test_search_for_autoinject_bm25_gates_on_dense_probe(rag_conn, bow_embeddings, monkeypatch):
+def test_search_for_autoinject_bm25_gates_on_dense_probe(
+ rag_conn, bow_embeddings, monkeypatch
+):
# BM25 hits carry no cosine, so the gate uses a dense 1-NN probe (#5).
_add_doc(rag_conn, "kb_a", "d1", "paper.pdf", "h1", "body text here", page = 3)
monkeypatch.setattr(
@@ -226,7 +242,9 @@ def test_search_for_autoinject_bm25_gates_on_dense_probe(rag_conn, bow_embedding
monkeypatch.setattr(
retrieval,
"retrieve_dense",
- lambda conn, scope, q, k = None, **kw: [retrieval.Hit("d1:0", 0.82, dense_score = 0.82)],
+ lambda conn, scope, q, k = None, **kw: [
+ retrieval.Hit("d1:0", 0.82, dense_score = 0.82)
+ ],
)
found = tool.search_for_autoinject(
query = "q", scope_kb_id = "a", mode = "lexical", min_dense_score = 0.70
@@ -236,10 +254,14 @@ def test_search_for_autoinject_bm25_gates_on_dense_probe(rag_conn, bow_embedding
monkeypatch.setattr(
retrieval,
"retrieve_dense",
- lambda conn, scope, q, k = None, **kw: [retrieval.Hit("d1:0", 0.40, dense_score = 0.40)],
+ lambda conn, scope, q, k = None, **kw: [
+ retrieval.Hit("d1:0", 0.40, dense_score = 0.40)
+ ],
)
assert (
- tool.search_for_autoinject(query = "q", scope_kb_id = "a", mode = "lexical", min_dense_score = 0.70)
+ tool.search_for_autoinject(
+ query = "q", scope_kb_id = "a", mode = "lexical", min_dense_score = 0.70
+ )
is None
)
@@ -271,7 +293,10 @@ def test_build_rag_autoinject_emits_pipeline(monkeypatch):
te = next(e for e in out["events"] if e["type"] == "tool_end")
assert te["tool_name"] == "search_knowledge_base"
assert tools.RAG_SOURCES_SENTINEL in te["result"]
- assert out["messages"][0]["tool_calls"][0]["function"]["name"] == "search_knowledge_base"
+ assert (
+ out["messages"][0]["tool_calls"][0]["function"]["name"]
+ == "search_knowledge_base"
+ )
assert "__RAG_SOURCES__" not in out["messages"][1]["content"]
@@ -282,7 +307,10 @@ def test_build_rag_autoinject_skips_without_hit(monkeypatch):
monkeypatch.setattr(rag_db, "RAG_AVAILABLE", True, raising = False)
monkeypatch.setattr(tool, "search_for_autoinject", lambda **k: None)
assert (
- tools.build_rag_autoinject([{"role": "user", "content": "hi"}], {"thread_id": "t1"}) is None
+ tools.build_rag_autoinject(
+ [{"role": "user", "content": "hi"}], {"thread_id": "t1"}
+ )
+ is None
)
@@ -300,7 +328,9 @@ def test_build_rag_autoinject_enabled_by_default(monkeypatch):
return ("x", [{"citationId": 1}])
monkeypatch.setattr(tool, "search_for_autoinject", fake)
- out = tools.build_rag_autoinject([{"role": "user", "content": "hi"}], {"thread_id": "t1"})
+ out = tools.build_rag_autoinject(
+ [{"role": "user", "content": "hi"}], {"thread_id": "t1"}
+ )
assert out is not None
assert seen["min_dense_score"] == 0.70 # high-precision floor by default
@@ -331,7 +361,10 @@ def test_build_rag_autoinject_disabled_by_env(monkeypatch):
monkeypatch.setenv("RAG_AUTOINJECT", "0")
assert (
- tools.build_rag_autoinject([{"role": "user", "content": "hi"}], {"thread_id": "t1"}) is None
+ tools.build_rag_autoinject(
+ [{"role": "user", "content": "hi"}], {"thread_id": "t1"}
+ )
+ is None
)
# No scope -> also a no-op.
monkeypatch.delenv("RAG_AUTOINJECT", raising = False)
@@ -429,4 +462,7 @@ def test_build_rag_autoinject_scope_overrides_env(monkeypatch):
# Explicit False disables even with the env default on.
monkeypatch.setenv("RAG_AUTOINJECT", "1")
- assert tools.build_rag_autoinject(conv, {"thread_id": "t1", "autoinject": False}) is None
+ assert (
+ tools.build_rag_autoinject(conv, {"thread_id": "t1", "autoinject": False})
+ is None
+ )
diff --git a/studio/backend/tests/test_rag_store.py b/studio/backend/tests/test_rag_store.py
index 4c54b02ea7..5d216af228 100644
--- a/studio/backend/tests/test_rag_store.py
+++ b/studio/backend/tests/test_rag_store.py
@@ -36,7 +36,9 @@ def _chunk(
def _add_doc(conn, scope, doc_id, filename, sha, texts):
chunks = [_chunk(t, i) for i, t in enumerate(texts)]
vectors = [embed(t) for t in texts]
- store.create_document(conn, scope = scope, filename = filename, sha256 = sha, document_id = doc_id)
+ store.create_document(
+ conn, scope = scope, filename = filename, sha256 = sha, document_id = doc_id
+ )
store.add_chunks(conn, scope, doc_id, chunks, vectors)
@@ -50,7 +52,9 @@ def test_lexical_returns_only_matching_docs(rag_conn):
def test_scope_isolation(rag_conn):
_add_doc(rag_conn, "kb_a", "d1", "f", "h1", ["alpha bravo"])
_add_doc(rag_conn, "kb_b", "d2", "f", "h2", ["alpha bravo"])
- assert [cid for cid, _ in store.search_lexical(rag_conn, "kb_b", "alpha", 10)] == ["d2:0"]
+ assert [cid for cid, _ in store.search_lexical(rag_conn, "kb_b", "alpha", 10)] == [
+ "d2:0"
+ ]
def test_match_query_sanitizes_special_chars():
@@ -100,7 +104,9 @@ def test_incremental_add_is_flat(rag_conn):
after = rag_conn.execute(
"SELECT rowid, chunk_id FROM chunks_fts WHERE scope='kb_a' AND chunk_id LIKE 'd1:%'"
).fetchall()
- before_d1 = [(r["rowid"], r["chunk_id"]) for r in before if r["chunk_id"].startswith("d1:")]
+ before_d1 = [
+ (r["rowid"], r["chunk_id"]) for r in before if r["chunk_id"].startswith("d1:")
+ ]
after_d1 = [(r["rowid"], r["chunk_id"]) for r in after]
assert before_d1 == after_d1
diff --git a/studio/backend/tests/test_responses_api.py b/studio/backend/tests/test_responses_api.py
index 693e832113..2ad5aeab94 100644
--- a/studio/backend/tests/test_responses_api.py
+++ b/studio/backend/tests/test_responses_api.py
@@ -168,7 +168,9 @@ class TestResponsesResponse:
resp = ResponsesResponse(
model = "test-model",
output = [
- ResponsesOutputMessage(content = [ResponsesOutputTextContent(text = "Hello!")]),
+ ResponsesOutputMessage(
+ content = [ResponsesOutputTextContent(text = "Hello!")]
+ ),
],
usage = ResponsesUsage(input_tokens = 10, output_tokens = 5, total_tokens = 15),
)
diff --git a/studio/backend/tests/test_responses_tool_passthrough.py b/studio/backend/tests/test_responses_tool_passthrough.py
index ae7ff729bd..e2e71b1d30 100644
--- a/studio/backend/tests/test_responses_tool_passthrough.py
+++ b/studio/backend/tests/test_responses_tool_passthrough.py
@@ -169,7 +169,9 @@ class TestResponsesMultiTurnInput:
def test_function_call_output_missing_call_id_rejected(self):
with pytest.raises(ValidationError):
- ResponsesFunctionCallOutputInputItem(type = "function_call_output", output = "x")
+ ResponsesFunctionCallOutputInputItem(
+ type = "function_call_output", output = "x"
+ )
def test_function_call_output_accepts_content_array(self):
item = ResponsesFunctionCallOutputInputItem(
@@ -229,7 +231,9 @@ class TestToolsTranslation:
assert _translate_responses_tools_to_chat([]) is None
def test_only_builtin_tools_returns_none(self):
- assert _translate_responses_tools_to_chat([{"type": "web_search_preview"}]) is None
+ assert (
+ _translate_responses_tools_to_chat([{"type": "web_search_preview"}]) is None
+ )
def test_description_optional(self):
out = _translate_responses_tools_to_chat(
@@ -261,7 +265,9 @@ class TestToolChoiceTranslation:
"""A client sending the Chat Completions nested shape isn't
double-wrapped."""
already_nested = {"type": "function", "function": {"name": "get_weather"}}
- assert _translate_responses_tool_choice_to_chat(already_nested) == already_nested
+ assert (
+ _translate_responses_tool_choice_to_chat(already_nested) == already_nested
+ )
def test_unknown_shape_passes_through(self):
obj = {"type": "allowed_tools", "tools": [{"type": "function", "name": "x"}]}
@@ -650,14 +656,20 @@ class TestResponsesNonStreamingAdapter:
)
assert [item["type"] for item in body["output"]] == ["reasoning", "message"]
- assert body["output"][0]["content"] == [{"type": "reasoning_text", "text": "plan"}]
+ assert body["output"][0]["content"] == [
+ {"type": "reasoning_text", "text": "plan"}
+ ]
assert body["output"][0]["summary"] == []
assert body["output"][1]["content"][0]["text"] == "33"
assert "" not in body["output"][1]["content"][0]["text"]
assert "" not in body["output"][1]["content"][0]["text"]
- def test_literal_think_tags_remain_visible_without_reasoning_request(self, monkeypatch):
- body = self._run_with_message(monkeypatch, {"content": "show x tags"})
+ def test_literal_think_tags_remain_visible_without_reasoning_request(
+ self, monkeypatch
+ ):
+ body = self._run_with_message(
+ monkeypatch, {"content": "show x tags"}
+ )
assert [item["type"] for item in body["output"]] == ["message"]
assert body["output"][0]["content"][0]["text"] == "show x tags"
@@ -691,7 +703,9 @@ class TestResponsesNonStreamingAdapter:
)
assert [item["type"] for item in body["output"]] == ["reasoning", "message"]
- assert body["output"][0]["content"] == [{"type": "reasoning_text", "text": "plan next"}]
+ assert body["output"][0]["content"] == [
+ {"type": "reasoning_text", "text": "plan next"}
+ ]
assert body["output"][1]["content"][0]["text"] == "33"
def test_plain_content_remains_message_only(self, monkeypatch):
@@ -779,12 +793,16 @@ class TestResponsesStreamAdapter:
supports_reasoning = supports_reasoning,
reasoning_always_on = reasoning_always_on,
_request_reasoning_kwargs = (
- lambda enable_thinking = None, reasoning_effort = None, preserve_thinking = None: None
+ lambda enable_thinking = None,
+ reasoning_effort = None,
+ preserve_thinking = None: None
),
),
)
- def test_split_think_markers_stream_as_reasoning_and_visible_text(self, monkeypatch):
+ def test_split_think_markers_stream_as_reasoning_and_visible_text(
+ self, monkeypatch
+ ):
chunks = [
{"choices": [{"delta": {"content": "pla"}}]},
@@ -793,7 +811,9 @@ class TestResponsesStreamAdapter:
{"choices": [], "usage": {"prompt_tokens": 2, "completion_tokens": 3}},
]
self._install_stream_mock(monkeypatch, chunks)
- payload = ResponsesRequest(input = "hi", stream = True, reasoning = {"effort": "high"})
+ payload = ResponsesRequest(
+ input = "hi", stream = True, reasoning = {"effort": "high"}
+ )
messages = [ChatMessage(role = "user", content = "hi")]
async def run():
@@ -814,7 +834,9 @@ class TestResponsesStreamAdapter:
assert completed["response"]["output"][0]["content"][0]["text"] == "plan"
assert completed["response"]["output"][1]["content"][0]["text"] == "33"
- def test_literal_think_tags_stream_as_visible_text_without_reasoning_request(self, monkeypatch):
+ def test_literal_think_tags_stream_as_visible_text_without_reasoning_request(
+ self, monkeypatch
+ ):
chunks = [
{"choices": [{"delta": {"content": "show x tags"}}]},
@@ -833,21 +855,28 @@ class TestResponsesStreamAdapter:
reasoning_deltas = self._payloads(lines, "response.reasoning_text.delta")
text_deltas = self._payloads(lines, "response.output_text.delta")
assert reasoning_deltas == []
- assert "".join(event["delta"] for event in text_deltas) == "show x tags"
+ assert (
+ "".join(event["delta"] for event in text_deltas)
+ == "show x tags"
+ )
completed = self._payloads(lines, "response.completed")[0]
assert [item["type"] for item in completed["response"]["output"]] == ["message"]
assert completed["response"]["output"][0]["content"][0]["text"] == (
"show x tags"
)
- def test_non_reasoning_gguf_stream_keeps_literal_think_tags_visible(self, monkeypatch):
+ def test_non_reasoning_gguf_stream_keeps_literal_think_tags_visible(
+ self, monkeypatch
+ ):
chunks = [
{"choices": [{"delta": {"content": "show x tags"}}]},
{"choices": [], "usage": {"prompt_tokens": 2, "completion_tokens": 3}},
]
self._install_stream_mock(monkeypatch, chunks, supports_reasoning = False)
- payload = ResponsesRequest(input = "hi", stream = True, reasoning = {"effort": "high"})
+ payload = ResponsesRequest(
+ input = "hi", stream = True, reasoning = {"effort": "high"}
+ )
messages = [ChatMessage(role = "user", content = "hi")]
async def run():
@@ -859,7 +888,10 @@ class TestResponsesStreamAdapter:
reasoning_deltas = self._payloads(lines, "response.reasoning_text.delta")
text_deltas = self._payloads(lines, "response.output_text.delta")
assert reasoning_deltas == []
- assert "".join(event["delta"] for event in text_deltas) == "show x tags"
+ assert (
+ "".join(event["delta"] for event in text_deltas)
+ == "show x tags"
+ )
completed = self._payloads(lines, "response.completed")[0]
assert [item["type"] for item in completed["response"]["output"]] == ["message"]
assert completed["response"]["output"][0]["content"][0]["text"] == (
@@ -872,7 +904,9 @@ class TestResponsesStreamAdapter:
{"choices": [], "usage": {"prompt_tokens": 2, "completion_tokens": 3}},
]
self._install_stream_mock(monkeypatch, chunks)
- payload = ResponsesRequest(input = "hi", stream = True, reasoning = {"effort": "high"})
+ payload = ResponsesRequest(
+ input = "hi", stream = True, reasoning = {"effort": "high"}
+ )
messages = [ChatMessage(role = "user", content = "hi")]
async def run():
@@ -950,7 +984,9 @@ class TestResponsesStreamAdapter:
text_deltas = self._payloads(lines, "response.output_text.delta")
assert "".join(event["delta"] for event in reasoning_deltas) == "plan next"
assert "".join(event["delta"] for event in text_deltas) == "33"
- assert "reasoning_text" not in "".join(event["delta"] for event in reasoning_deltas)
+ assert "reasoning_text" not in "".join(
+ event["delta"] for event in reasoning_deltas
+ )
completed = self._payloads(lines, "response.completed")[0]
assert completed["response"]["output"][0]["content"][0]["text"] == "plan next"
assert completed["response"]["output"][1]["content"][0]["text"] == "33"
@@ -988,7 +1024,10 @@ class TestResponsesStreamAdapter:
done_events = self._payloads(lines, "response.output_item.done")
assert [event["output_index"] for event in done_events] == [0, 1]
- assert [event["item"]["type"] for event in done_events] == ["function_call", "message"]
+ assert [event["item"]["type"] for event in done_events] == [
+ "function_call",
+ "message",
+ ]
completed = self._payloads(lines, "response.completed")[0]
assert [item["type"] for item in completed["response"]["output"]] == [
"function_call",
@@ -1012,13 +1051,19 @@ class TestResponsesStreamAdapter:
"index": 0,
"id": "call_0",
"type": "function",
- "function": {"name": "first", "arguments": "{}"},
+ "function": {
+ "name": "first",
+ "arguments": "{}",
+ },
},
{
"index": 1,
"id": "call_1",
"type": "function",
- "function": {"name": "second", "arguments": "{}"},
+ "function": {
+ "name": "second",
+ "arguments": "{}",
+ },
},
]
}
@@ -1055,7 +1100,9 @@ class TestResponsesStreamAdapter:
base_url = "http://llama.test",
# Non-reasoning template: the real backend returns None here.
_request_reasoning_kwargs = (
- lambda enable_thinking = None, reasoning_effort = None, preserve_thinking = None: None
+ lambda enable_thinking = None,
+ reasoning_effort = None,
+ preserve_thinking = None: None
),
),
)
@@ -1104,7 +1151,9 @@ class TestResponsesStreamAdapter:
class TestResponsesOutputFunctionCall:
def test_reasoning_output_item_serialises_full_reasoning_content(self):
- item = ResponsesOutputReasoning(content = [{"type": "reasoning_text", "text": "plan"}])
+ item = ResponsesOutputReasoning(
+ content = [{"type": "reasoning_text", "text": "plan"}]
+ )
d = item.model_dump()
assert d["type"] == "reasoning"
assert d["id"].startswith("rs_")
@@ -1230,7 +1279,9 @@ class TestCodexStyleRequestShapes:
msgs = _normalise_responses_input(payload)
assert [m.role for m in msgs] == ["user", "assistant", "user"]
- assert all("plan" not in (m.content or "") for m in msgs if isinstance(m.content, str))
+ assert all(
+ "plan" not in (m.content or "") for m in msgs if isinstance(m.content, str)
+ )
def test_unknown_content_part_type_accepted(self):
"""Unknown content-part types (e.g. future input_audio) validate as
@@ -1321,7 +1372,9 @@ class TestCodexStyleRequestShapes:
input = [
{
"role": "assistant",
- "content": [{"type": "output_text", "text": "ok", "annotations": []}],
+ "content": [
+ {"type": "output_text", "text": "ok", "annotations": []}
+ ],
},
{"role": "user", "content": "next"},
],
diff --git a/studio/backend/tests/test_rocm_oom_guard.py b/studio/backend/tests/test_rocm_oom_guard.py
index 6e70c7cde4..767a3e4caf 100644
--- a/studio/backend/tests/test_rocm_oom_guard.py
+++ b/studio/backend/tests/test_rocm_oom_guard.py
@@ -172,7 +172,9 @@ class TestDeviceNameFallback:
props = _props(name = device_name)
gcn, is_unified = _rocm_classify_unified_memory(props)
assert gcn == "", f"expected empty gcn_arch, got {gcn!r}"
- assert is_unified is True, f"device {device_name!r} should be classified as unified-memory"
+ assert (
+ is_unified is True
+ ), f"device {device_name!r} should be classified as unified-memory"
# --- discrete devices that must NOT be mis-classified ---
diff --git a/studio/backend/tests/test_s3_dataset.py b/studio/backend/tests/test_s3_dataset.py
index f47db565ff..791954b8f4 100644
--- a/studio/backend/tests/test_s3_dataset.py
+++ b/studio/backend/tests/test_s3_dataset.py
@@ -37,7 +37,9 @@ class _FakePaginator:
def paginate(self, **kwargs):
prefix = kwargs.get("Prefix")
- contents = [{"Key": k} for k in self._keys if prefix is None or k.startswith(prefix)]
+ contents = [
+ {"Key": k} for k in self._keys if prefix is None or k.startswith(prefix)
+ ]
# Emit in two pages to exercise pagination handling.
mid = len(contents) // 2
yield {"Contents": contents[:mid]}
diff --git a/studio/backend/tests/test_safetensors_capability_advertise.py b/studio/backend/tests/test_safetensors_capability_advertise.py
index 1e8fb9e2b2..dd5a44c91f 100644
--- a/studio/backend/tests/test_safetensors_capability_advertise.py
+++ b/studio/backend/tests/test_safetensors_capability_advertise.py
@@ -363,7 +363,11 @@ def test_worker_load_reply_payload_includes_chat_template_info():
"is_gguf": False,
}
_bm = getattr(backend, "models", {}) or {}
- _entry = _bm.get(mc.identifier) or _bm.get(getattr(backend, "active_model_name", None)) or {}
+ _entry = (
+ _bm.get(mc.identifier)
+ or _bm.get(getattr(backend, "active_model_name", None))
+ or {}
+ )
_tpl_info = _entry.get("chat_template_info")
if isinstance(_tpl_info, dict):
model_info["chat_template_info"] = {
diff --git a/studio/backend/tests/test_safetensors_tool_loop.py b/studio/backend/tests/test_safetensors_tool_loop.py
index 12731783a0..7b9e35bb2c 100644
--- a/studio/backend/tests/test_safetensors_tool_loop.py
+++ b/studio/backend/tests/test_safetensors_tool_loop.py
@@ -40,7 +40,9 @@ from utils.datasets import is_gpt_oss_model_name
class TestParser:
def test_json_tool_call(self):
- text = '{"name":"web_search","arguments":{"query":"hello"}}'
+ text = (
+ '{"name":"web_search","arguments":{"query":"hello"}}'
+ )
result = parse_tool_calls_from_text(text)
assert len(result) == 1
tc = result[0]
@@ -85,9 +87,7 @@ class TestParser:
def test_code_with_embedded_xml(self):
# A code parameter with a literal must not truncate: the
# parser uses end-of-body as the only boundary for single-param calls.
- text = (
- "html = ''\nprint('hi')"
- )
+ text = "html = ''\nprint('hi')"
result = parse_tool_calls_from_text(text)
assert len(result) == 1
assert "print('hi')" in result[0]["function"]["arguments"]
@@ -293,7 +293,10 @@ def test_active_tools_are_passed_to_single_turn_after_render_html_success():
assert exec_fn.calls == [("render_html", {"code": "one"})]
assert captured_tool_names == [["render_html", "web_search"], ["web_search"]]
- assert any(event.get("type") == "content" and event.get("text") == "Done." for event in events)
+ assert any(
+ event.get("type") == "content" and event.get("text") == "Done."
+ for event in events
+ )
class TestLoopBasic:
@@ -392,7 +395,9 @@ class TestLoopBasic:
assert exec_fn.calls[0][0] == "render_html"
assert "" in exec_fn.calls[0][1]["code"]
- def test_python_tool_containing_render_html_signal_does_not_emit_provisional_start(self):
+ def test_python_tool_containing_render_html_signal_does_not_emit_provisional_start(
+ self,
+ ):
loop, exec_fn = _make_loop(
turns = [
[
@@ -409,7 +414,9 @@ class TestLoopBasic:
assert len(tool_starts) == 1
assert tool_starts[0]["tool_name"] == "python"
- assert exec_fn.calls == [("python", {"code": "print('')"})]
+ assert exec_fn.calls == [
+ ("python", {"code": "print('')"})
+ ]
def test_render_html_success_blocks_second_artifact_call(self):
exec_fn = FakeExecuteTool(["Rendered HTML artifact."])
@@ -444,7 +451,10 @@ class TestLoopBasic:
tool_starts = [e for e in events if e["type"] == "tool_start"]
assert exec_fn.calls == [("render_html", {"code": "one"})]
- assert [e["arguments"] for e in tool_starts] == [{}, {"code": "one"}]
+ assert [e["arguments"] for e in tool_starts] == [
+ {},
+ {"code": "one"},
+ ]
def test_truncated_unclosed_tool_call(self):
loop, exec_fn = _make_loop(
@@ -464,7 +474,9 @@ class TestLoopBasic:
loop, exec_fn = _make_loop(
turns = [
# ``arguments`` is a string _coerce_arguments can't parse, so heal runs.
- ['{"name":"web_search","arguments":"hello world"}'],
+ [
+ '{"name":"web_search","arguments":"hello world"}'
+ ],
["ok"],
],
exec_results = ["..."],
@@ -479,8 +491,12 @@ class TestLoopBehaviour:
captured_messages: list[list[dict]] = []
turns = iter(
[
- ['{"name":"web_search","arguments":{"query":"x"}}'],
- ['{"name":"web_search","arguments":{"query":"x"}}'],
+ [
+ '{"name":"web_search","arguments":{"query":"x"}}'
+ ],
+ [
+ '{"name":"web_search","arguments":{"query":"x"}}'
+ ],
["final"],
]
)
@@ -505,11 +521,14 @@ class TestLoopBehaviour:
)
assert exec_fn.calls == [("web_search", {"query": "x"})]
- assert [e["tool_call_id"] for e in events if e["type"] == "tool_end"] == ["call_0"]
+ assert [e["tool_call_id"] for e in events if e["type"] == "tool_end"] == [
+ "call_0"
+ ]
assert not [
e
for e in events
- if e.get("tool_call_id") == "call_1" and e.get("type") in {"tool_start", "tool_end"}
+ if e.get("tool_call_id") == "call_1"
+ and e.get("type") in {"tool_start", "tool_end"}
]
duplicate_nudges = [
message
@@ -524,9 +543,15 @@ class TestLoopBehaviour:
captured_tool_names: list[list[str]] = []
turns = iter(
[
- ['{"name":"web_search","arguments":{"query":"x"}}'],
- ['{"name":"web_search","arguments":{"query":"x"}}'],
- ['{"name":"python","arguments":{"code":"print(1)"}}'],
+ [
+ '{"name":"web_search","arguments":{"query":"x"}}'
+ ],
+ [
+ '{"name":"web_search","arguments":{"query":"x"}}'
+ ],
+ [
+ '{"name":"python","arguments":{"code":"print(1)"}}'
+ ],
["final"],
]
)
@@ -571,7 +596,8 @@ class TestLoopBehaviour:
assert not [
e
for e in events
- if e.get("tool_call_id") == "call_1" and e.get("type") in {"tool_start", "tool_end"}
+ if e.get("tool_call_id") == "call_1"
+ and e.get("type") in {"tool_start", "tool_end"}
]
duplicate_nudges = [
message
@@ -586,9 +612,15 @@ class TestLoopBehaviour:
captured_tool_names: list[list[str]] = []
turns = iter(
[
- ['{"name":"web_search","arguments":{"query":"x"}}'],
- ['{"name":"web_search","arguments":{"query":"x"}}'],
- ['{"name":"web_search","arguments":{"query":"x"}}'],
+ [
+ '{"name":"web_search","arguments":{"query":"x"}}'
+ ],
+ [
+ '{"name":"web_search","arguments":{"query":"x"}}'
+ ],
+ [
+ '{"name":"web_search","arguments":{"query":"x"}}'
+ ],
["final from first result"],
]
)
@@ -620,11 +652,14 @@ class TestLoopBehaviour:
assert exec_fn.calls == [("web_search", {"query": "x"})]
assert [
- event.get("tool_call_id") for event in events if event.get("type") == "tool_end"
+ event.get("tool_call_id")
+ for event in events
+ if event.get("type") == "tool_end"
] == ["call_0"]
assert captured_tool_names[-1] == []
assert any(
- event.get("type") == "content" and "final from first result" in event.get("text", "")
+ event.get("type") == "content"
+ and "final from first result" in event.get("text", "")
for event in events
)
@@ -671,7 +706,9 @@ class TestLoopBehaviour:
# carries the raw result for the UI.
loop, exec_fn = _make_loop(
turns = [
- ['{"name":"python","arguments":{"code":"plot()"}}'],
+ [
+ '{"name":"python","arguments":{"code":"plot()"}}'
+ ],
["see chart"],
],
exec_results = ["chart\n__IMAGES__:/tmp/chart.png"],
@@ -709,7 +746,9 @@ class TestLoopBehaviour:
tool_msgs = [m for m in captured[1] if m.get("role") == "tool"]
assert tool_msgs, "no tool message reached the model"
for tm in tool_msgs:
- assert "__IMAGES__" not in tm["content"], f"sentinel leaked to model: {tm['content']!r}"
+ assert (
+ "__IMAGES__" not in tm["content"]
+ ), f"sentinel leaked to model: {tm['content']!r}"
def test_image_sentinel_stripped_with_multiple_markers(self):
# Consecutive sentinels: cut at the first, nothing leaks.
@@ -739,13 +778,19 @@ class TestLoopBehaviour:
tool_msgs = [m for m in captured[1] if m.get("role") == "tool"]
assert tool_msgs
for tm in tool_msgs:
- assert "__IMAGES__" not in tm["content"], f"second sentinel leaked: {tm['content']!r}"
- assert tm["content"] == "panel", f"expected payload-only 'panel', got {tm['content']!r}"
+ assert (
+ "__IMAGES__" not in tm["content"]
+ ), f"second sentinel leaked: {tm['content']!r}"
+ assert (
+ tm["content"] == "panel"
+ ), f"expected payload-only 'panel', got {tm['content']!r}"
def test_tool_execution_error_is_emitted_but_loop_continues(self):
loop, exec_fn = _make_loop(
turns = [
- ['{"name":"web_search","arguments":{"query":"x"}}'],
+ [
+ '{"name":"web_search","arguments":{"query":"x"}}'
+ ],
["sorry, that failed"],
],
exec_results = ["Error: network unreachable"],
@@ -760,7 +805,9 @@ class TestLoopBehaviour:
def test_exception_in_executor_does_not_raise(self):
loop, exec_fn = _make_loop(
turns = [
- ['{"name":"web_search","arguments":{"query":"x"}}'],
+ [
+ '{"name":"web_search","arguments":{"query":"x"}}'
+ ],
["recovered"],
],
exec_results = [RuntimeError("boom")],
@@ -796,7 +843,9 @@ class TestLoopControl:
loop, exec_fn = _make_loop(
turns = [
# Tool call (executes once).
- ['{"name":"web_search","arguments":{"query":"a"}}'],
+ [
+ '{"name":"web_search","arguments":{"query":"a"}}'
+ ],
# Model gives a final answer when nudged.
["here is the final answer"],
],
@@ -813,19 +862,24 @@ class TestStatusFormatting:
def test_status_for_known_tools(self):
# Call the private helper directly to verify status formatting.
assert (
- safetensors_agentic._status_for_tool("web_search", {"query": "abc"}) == "Searching: abc"
+ safetensors_agentic._status_for_tool("web_search", {"query": "abc"})
+ == "Searching: abc"
)
assert (
- safetensors_agentic._status_for_tool("web_search", {"url": "https://www.example.com/x"})
+ safetensors_agentic._status_for_tool(
+ "web_search", {"url": "https://www.example.com/x"}
+ )
== "Reading: example.com"
)
- assert safetensors_agentic._status_for_tool("python", {"code": "x = 1"}).startswith(
- "Running Python:"
+ assert safetensors_agentic._status_for_tool(
+ "python", {"code": "x = 1"}
+ ).startswith("Running Python:")
+ assert safetensors_agentic._status_for_tool(
+ "terminal", {"command": "ls"}
+ ).startswith("Running:")
+ assert safetensors_agentic._status_for_tool("unknown_tool", {}).startswith(
+ "Calling:"
)
- assert safetensors_agentic._status_for_tool("terminal", {"command": "ls"}).startswith(
- "Running:"
- )
- assert safetensors_agentic._status_for_tool("unknown_tool", {}).startswith("Calling:")
class TestProseMentioningToolCall:
@@ -835,7 +889,9 @@ class TestProseMentioningToolCall:
loop, exec_fn = _make_loop(
turns = [
# A real tool call so the loop advances a turn.
- ['{"name":"web_search","arguments":{"query":"x"}}'],
+ [
+ '{"name":"web_search","arguments":{"query":"x"}}'
+ ],
# Prose that mentions the literal text.
["the docs say means an LLM tool call wrapper"],
],
@@ -854,7 +910,9 @@ class TestProseMentioningToolCall:
# loop parses only model output, so exactly one call.
loop, exec_fn = _make_loop(
turns = [
- ['{"name":"web_search","arguments":{"query":"x"}}'],
+ [
+ '{"name":"web_search","arguments":{"query":"x"}}'
+ ],
["the docs mention wrappers"],
],
exec_results = ["Page text: appears here in the docs"],
@@ -955,18 +1013,23 @@ class TestGuardrails:
)
assert exec_fn.calls == []
- assert not [event for event in events if event.get("type") in {"tool_start", "tool_end"}]
+ assert not [
+ event for event in events if event.get("type") in {"tool_start", "tool_end"}
+ ]
disabled_nudges = [
message
for message in captured_messages[-1]
- if message.get("role") == "user" and "not enabled" in message.get("content", "")
+ if message.get("role") == "user"
+ and "not enabled" in message.get("content", "")
]
assert len(disabled_nudges) == 1
def test_empty_tools_list_means_allow_all_in_core_loop(self):
turns = iter(
[
- ['{"name":"python","arguments":{"code":"print(1)"}}'],
+ [
+ '{"name":"python","arguments":{"code":"print(1)"}}'
+ ],
["done"],
]
)
@@ -993,7 +1056,11 @@ class TestGuardrails:
def test_max_iterations_zero_executes_no_tools(self):
loop, exec_fn = _make_loop(
- turns = [['{"name":"web_search","arguments":{"query":"x"}}']],
+ turns = [
+ [
+ '{"name":"web_search","arguments":{"query":"x"}}'
+ ]
+ ],
exec_results = ["OK"],
max_tool_iterations = 0,
)
@@ -1024,7 +1091,9 @@ class TestGuardrails:
def test_auto_heal_disabled_still_parses_valid_tool_call(self):
loop, exec_fn = _make_loop(
turns = [
- ['{"name":"web_search","arguments":{"query":"x"}}'],
+ [
+ '{"name":"web_search","arguments":{"query":"x"}}'
+ ],
["done"],
],
exec_results = ["OK"],
@@ -1039,7 +1108,11 @@ class TestGuardrails:
monkeypatch.setattr(safetensors_agentic, "new_approval_id", lambda: approval_id)
loop, exec_fn = _make_loop(
- turns = [['{"name":"python","arguments":{"code":"print(1)"}}']],
+ turns = [
+ [
+ '{"name":"python","arguments":{"code":"print(1)"}}'
+ ]
+ ],
exec_results = ["OK"],
confirm_tool_calls = True,
session_id = "sess",
@@ -1068,21 +1141,30 @@ class TestGuardrails:
def fail_autoinject(*_args, **_kwargs):
raise AssertionError("RAG autoinject must not run before approval")
- monkeypatch.setattr("core.inference.tools.build_rag_autoinject", fail_autoinject)
+ monkeypatch.setattr(
+ "core.inference.tools.build_rag_autoinject", fail_autoinject
+ )
loop, exec_fn = _make_loop(
turns = [["plain answer"]],
confirm_tool_calls = True,
rag_scope = {"thread_id": "t1"},
)
events = _collect_events(loop)
- assert any(e.get("type") == "content" and e.get("text") == "plain answer" for e in events)
+ assert any(
+ e.get("type") == "content" and e.get("text") == "plain answer"
+ for e in events
+ )
assert exec_fn.calls == []
def test_auto_heal_disabled_preserves_xml_on_final_no_tools_pass(self):
turns = iter(
[
- ['{"name":"web_search","arguments":{"query":"x"}}'],
- ['{"name":"web_search","arguments":{"query":"literal"}}'],
+ [
+ '{"name":"web_search","arguments":{"query":"x"}}'
+ ],
+ [
+ '{"name":"web_search","arguments":{"query":"literal"}}'
+ ],
]
)
@@ -1142,18 +1224,29 @@ class TestGuardrails:
def test_non_consecutive_duplicate_is_short_circuited(self):
loop, exec_fn = _make_loop(
turns = [
- ['{"name":"web_search","arguments":{"query":"A"}}'],
- ['{"name":"web_search","arguments":{"query":"B"}}'],
- ['{"name":"web_search","arguments":{"query":"A"}}'],
+ [
+ '{"name":"web_search","arguments":{"query":"A"}}'
+ ],
+ [
+ '{"name":"web_search","arguments":{"query":"B"}}'
+ ],
+ [
+ '{"name":"web_search","arguments":{"query":"A"}}'
+ ],
["final"],
],
exec_results = ["res-A", "res-B"],
max_tool_iterations = 4,
)
events = _collect_events(loop)
- assert exec_fn.calls == [("web_search", {"query": "A"}), ("web_search", {"query": "B"})]
+ assert exec_fn.calls == [
+ ("web_search", {"query": "A"}),
+ ("web_search", {"query": "B"}),
+ ]
assert [
- event.get("tool_call_id") for event in events if event.get("type") == "tool_end"
+ event.get("tool_call_id")
+ for event in events
+ if event.get("type") == "tool_end"
] == ["call_0", "call_1"]
assert not [
event
@@ -1177,7 +1270,9 @@ class TestGuardrails:
events = _collect_events(loop)
assert exec_fn.calls == [("web_search", {"query": "A"})]
assert [
- event.get("tool_call_id") for event in events if event.get("type") == "tool_end"
+ event.get("tool_call_id")
+ for event in events
+ if event.get("type") == "tool_end"
] == ["call_0"]
assert not [
event
@@ -1187,16 +1282,24 @@ class TestGuardrails:
]
def test_coerce_string_args_python_uses_code_key(self):
- assert _coerce_arguments("print(1)", heal = True, tool_name = "python") == {"code": "print(1)"}
+ assert _coerce_arguments("print(1)", heal = True, tool_name = "python") == {
+ "code": "print(1)"
+ }
def test_coerce_string_args_terminal_uses_command_key(self):
- assert _coerce_arguments("ls -la", heal = True, tool_name = "terminal") == {"command": "ls -la"}
+ assert _coerce_arguments("ls -la", heal = True, tool_name = "terminal") == {
+ "command": "ls -la"
+ }
def test_tool_call_ids_unique_across_loop_iterations(self):
loop, _exec = _make_loop(
turns = [
- ['{"name":"web_search","arguments":{"query":"A"}}'],
- ['{"name":"web_search","arguments":{"query":"B"}}'],
+ [
+ '{"name":"web_search","arguments":{"query":"A"}}'
+ ],
+ [
+ '{"name":"web_search","arguments":{"query":"B"}}'
+ ],
["done"],
],
exec_results = ["A", "B"],
diff --git a/studio/backend/tests/test_sandbox_tools.py b/studio/backend/tests/test_sandbox_tools.py
index 24b1da1772..5578e9dd97 100644
--- a/studio/backend/tests/test_sandbox_tools.py
+++ b/studio/backend/tests/test_sandbox_tools.py
@@ -88,7 +88,9 @@ class TestTrustedHostAllowlist:
_ok(f"import requests; requests.get({url!r})")
def test_wikipedia_subdomain_passes(self):
- _ok('import urllib.request; urllib.request.urlopen("https://m.en.wikipedia.org/wiki/Foo")')
+ _ok(
+ 'import urllib.request; urllib.request.urlopen("https://m.en.wikipedia.org/wiki/Foo")'
+ )
def test_hf_co_short_form_passes(self):
_ok('import requests; requests.get("https://hf.co/unsloth/Qwen3.5-4B-GGUF")')
@@ -219,7 +221,10 @@ class TestUploadDenylist:
)
def test_plain_post_json_not_blocked(self):
- _ok("import requests\n" 'requests.post("https://api.weather.gov/lookup", json={"k": "v"})')
+ _ok(
+ "import requests\n"
+ 'requests.post("https://api.weather.gov/lookup", json={"k": "v"})'
+ )
class TestSandboxEnvIsolation:
diff --git a/studio/backend/tests/test_server_disk_logging.py b/studio/backend/tests/test_server_disk_logging.py
index 05d03d869c..d4f6f9f229 100644
--- a/studio/backend/tests/test_server_disk_logging.py
+++ b/studio/backend/tests/test_server_disk_logging.py
@@ -91,7 +91,9 @@ class TestSetupServerDiskLogging:
def test_run_server_wires_logging_before_main_import(self):
src = (Path(_BACKEND_DIR) / "run.py").read_text(encoding = "utf-8")
- call_idx = src.index("_setup_server_disk_logging()", src.index("def run_server"))
+ call_idx = src.index(
+ "_setup_server_disk_logging()", src.index("def run_server")
+ )
main_import_idx = src.index("from main import app", src.index("def run_server"))
assert call_idx < main_import_idx, (
"disk logging must be armed before importing main so import-time "
diff --git a/studio/backend/tests/test_studio_api.py b/studio/backend/tests/test_studio_api.py
index 928b636e3e..ea4c55fa9c 100644
--- a/studio/backend/tests/test_studio_api.py
+++ b/studio/backend/tests/test_studio_api.py
@@ -72,7 +72,11 @@ DEFAULT_VARIANT = "UD-Q4_K_XL"
PORT = 18222 # high port unlikely to collide
HOST = "127.0.0.1"
STARTUP_TIMEOUT = 120 # seconds
-LOG_FILE = Path(__file__).resolve().parent.parent.parent.parent / "temp" / "test_studio_api.log"
+LOG_FILE = (
+ Path(__file__).resolve().parent.parent.parent.parent
+ / "temp"
+ / "test_studio_api.log"
+)
# Helpers
@@ -216,7 +220,9 @@ def test_openai_sdk(base_url: str, api_key: str):
client = OpenAI(base_url = f"{base_url}/v1", api_key = api_key)
response = client.chat.completions.create(
model = "current",
- messages = [{"role": "user", "content": "What is 2+2? Answer with just the number."}],
+ messages = [
+ {"role": "user", "content": "What is 2+2? Answer with just the number."}
+ ],
stream = True,
)
content_parts = []
@@ -379,7 +385,9 @@ def test_openai_tools_nonstream(base_url: str, api_key: str):
assert "city" in parsed, f"Tool call missing required 'city' arg: {parsed}"
# Usage must be non-zero (was 0 before the fix)
usage = data.get("usage") or {}
- assert usage.get("prompt_tokens", 0) > 0, f"Expected non-zero prompt_tokens; got {usage}"
+ assert (
+ usage.get("prompt_tokens", 0) > 0
+ ), f"Expected non-zero prompt_tokens; got {usage}"
assert data.get("id"), "Missing response id"
print(
f" PASS openai tools non-stream: "
@@ -404,7 +412,8 @@ def test_openai_tools_stream(base_url: str, api_key: str):
assert status == 200, f"Expected 200, got {status}"
assert len(chunks) > 0, "No SSE chunks received"
assert _final_finish_reason(chunks) == "tool_calls", (
- f"Expected final finish_reason='tool_calls', got " f"{_final_finish_reason(chunks)!r}"
+ f"Expected final finish_reason='tool_calls', got "
+ f"{_final_finish_reason(chunks)!r}"
)
assembled = _collect_streamed_tool_calls(chunks)
assert len(assembled) >= 1, "No tool_calls reassembled from stream"
@@ -487,7 +496,8 @@ def test_openai_sdk_tool_calling(base_url: str, api_key: str):
stream = False,
)
assert resp.choices[0].finish_reason == "tool_calls", (
- f"Expected finish_reason='tool_calls', got " f"{resp.choices[0].finish_reason!r}"
+ f"Expected finish_reason='tool_calls', got "
+ f"{resp.choices[0].finish_reason!r}"
)
tool_calls = resp.choices[0].message.tool_calls
assert tool_calls and len(tool_calls) >= 1, "No tool_calls from SDK"
@@ -495,7 +505,9 @@ def test_openai_sdk_tool_calling(base_url: str, api_key: str):
assert tc.function.name == "get_weather"
parsed = json.loads(tc.function.arguments)
assert "city" in parsed
- print(f" PASS openai SDK tool calling: " f"tool={tc.function.name}, args={parsed}")
+ print(
+ f" PASS openai SDK tool calling: " f"tool={tc.function.name}, args={parsed}"
+ )
def test_invalid_key_rejected(base_url: str):
@@ -638,7 +650,9 @@ def test_anthropic_sdk(base_url: str, api_key: str):
message = client.messages.create(
model = "default",
max_tokens = 100,
- messages = [{"role": "user", "content": "What is 2+2? Answer with just the number."}],
+ messages = [
+ {"role": "user", "content": "What is 2+2? Answer with just the number."}
+ ],
)
assert message.role == "assistant"
assert len(message.content) > 0, "Empty content"
@@ -689,7 +703,9 @@ def test_anthropic_with_tools(base_url: str, api_key: str):
assert "message_stop" in event_types, "Missing message_stop"
full = _collect_anthropic_text(events)
- print(f" PASS anthropic with tools: {len(events)} events, {len(full)} chars content")
+ print(
+ f" PASS anthropic with tools: {len(events)} events, {len(full)} chars content"
+ )
def test_anthropic_tool_choice_any(base_url: str, api_key: str):
@@ -749,7 +765,8 @@ def test_anthropic_tool_choice_any(base_url: str, api_key: str):
tool_use_starts = [
e
for e in events
- if e[0] == "content_block_start" and e[1].get("content_block", {}).get("type") == "tool_use"
+ if e[0] == "content_block_start"
+ and e[1].get("content_block", {}).get("type") == "tool_use"
]
assert len(tool_use_starts) >= 1, "No tool_use content block emitted"
print(
@@ -799,7 +816,9 @@ def _start_server(model: str, variant: str | None) -> tuple[subprocess.Popen, st
if proc.poll() is not None:
log_fh.flush()
log_text = LOG_FILE.read_text()
- raise RuntimeError(f"Server exited early (code {proc.returncode}):\n{log_text[-2000:]}")
+ raise RuntimeError(
+ f"Server exited early (code {proc.returncode}):\n{log_text[-2000:]}"
+ )
log_text = LOG_FILE.read_text()
m = re.search(r"API Key:\s+(sk-unsloth-[a-f0-9]+)", log_text)
if m:
@@ -809,7 +828,9 @@ def _start_server(model: str, variant: str | None) -> tuple[subprocess.Popen, st
if not api_key:
log_text = LOG_FILE.read_text()
_kill_server(proc)
- raise RuntimeError(f"Timed out waiting for API key in server output:\n{log_text[-2000:]}")
+ raise RuntimeError(
+ f"Timed out waiting for API key in server output:\n{log_text[-2000:]}"
+ )
# Wait a moment for the model to be fully loaded
time.sleep(2)
@@ -836,7 +857,9 @@ def _kill_server(proc: subprocess.Popen):
def main():
- parser = argparse.ArgumentParser(description = "End-to-end tests for unsloth studio run")
+ parser = argparse.ArgumentParser(
+ description = "End-to-end tests for unsloth studio run"
+ )
parser.add_argument(
"--model",
default = DEFAULT_MODEL,
@@ -870,7 +893,9 @@ def main():
run_test(test_help_output)
# 2-16. Start server and run API tests
- print(f"\nStarting server: {args.model} (variant={args.gguf_variant}) on port {PORT}...")
+ print(
+ f"\nStarting server: {args.model} (variant={args.gguf_variant}) on port {PORT}..."
+ )
proc = None
try:
proc, api_key = _start_server(args.model, args.gguf_variant)
diff --git a/studio/backend/tests/test_tensor_parallel.py b/studio/backend/tests/test_tensor_parallel.py
index 30bfb91a08..c2f5ccbc9c 100644
--- a/studio/backend/tests/test_tensor_parallel.py
+++ b/studio/backend/tests/test_tensor_parallel.py
@@ -87,7 +87,9 @@ def test_load_request_accepts_tensor_parallel():
def test_load_request_round_trips_json_key():
# The frontend sends the snake_case key verbatim.
- req = LoadRequest.model_validate({"model_path": "owner/repo", "tensor_parallel": True})
+ req = LoadRequest.model_validate(
+ {"model_path": "owner/repo", "tensor_parallel": True}
+ )
assert req.tensor_parallel is True
assert req.model_dump()["tensor_parallel"] is True
@@ -259,7 +261,9 @@ def test_proportional_tensor_split_is_emitted_in_tensor_mode():
gate = src.find("if tensor_parallel:")
ts = src.find('"--tensor-split"')
nxt_else = src.find("self._tensor_parallel = False")
- assert 0 <= gate < ts < nxt_else, "--tensor-split must be emitted under `if tensor_parallel:`"
+ assert (
+ 0 <= gate < ts < nxt_else
+ ), "--tensor-split must be emitted under `if tensor_parallel:`"
# ── tensor-mode allocation: conservative VRAM budget ─────────────────
@@ -282,11 +286,15 @@ def test_fit_context_budget_frac_override_is_tighter():
pool_mib = 24 * 1024 # tight enough that KV capping bites
fit_default = backend._fit_context_to_vram(131072, pool_mib, model_size, "f16")
- fit_tp = backend._fit_context_to_vram(131072, pool_mib, model_size, "f16", budget_frac = 0.80)
+ fit_tp = backend._fit_context_to_vram(
+ 131072, pool_mib, model_size, "f16", budget_frac = 0.80
+ )
assert fit_tp < 131072, "expected the context to be capped at this VRAM tier"
assert fit_tp <= fit_default, "a tighter budget must not allow MORE context"
# Omitting the override must reproduce the default budget exactly.
- assert backend._fit_context_to_vram(131072, pool_mib, model_size, "f16") == fit_default
+ assert (
+ backend._fit_context_to_vram(131072, pool_mib, model_size, "f16") == fit_default
+ )
# ── unsupported-arch load failure -> clean message ───────────────────
@@ -326,12 +334,16 @@ def _plan(
mtp = False,
):
b = _kv_seeded_backend()
- return b, b._plan_tensor_parallel(gpus, int(model_gb * _GB), target, mtp_engaged = mtp)
+ return b, b._plan_tensor_parallel(
+ gpus, int(model_gb * _GB), target, mtp_engaged = mtp
+ )
def _kv_budget_b(model_gb, gpus = _ASYM):
reserve = LlamaCppBackend._TENSOR_PARALLEL_BUFFER_RESERVE_MIB
- return (sum(f for _, f in gpus) - len(gpus) * reserve) * 1024 * 1024 - int(model_gb * _GB)
+ return (sum(f for _, f in gpus) - len(gpus) * reserve) * 1024 * 1024 - int(
+ model_gb * _GB
+ )
def test_tp_plan_weighted_split_on_asymmetric_big_model():
@@ -396,7 +408,9 @@ def test_tp_plan_max_available_ctx_reports_native_not_explicit_ctx():
# An explicit small ctx caps effective_ctx but the UI ceiling
# (max_available_ctx) must reflect the native/hardware cap, not the request.
b = _kv_seeded_backend()
- ec, mac, _gi, _ts = b._plan_tensor_parallel(_ASYM, int(50 * _GB), 8192, max_target_ctx = 131072)
+ ec, mac, _gi, _ts = b._plan_tensor_parallel(
+ _ASYM, int(50 * _GB), 8192, max_target_ctx = 131072
+ )
_, native_mac, *_ = b._plan_tensor_parallel(_ASYM, int(50 * _GB), 131072)
assert ec == 8192 # explicit request honored for the load
assert mac == native_mac > ec # ceiling reflects the hardware cap
@@ -436,7 +450,9 @@ def test_tp_plan_drops_gpu_below_buffer_reserve():
# split (and gpu_indices reflects only the usable device).
b = _kv_seeded_backend()
reserve = LlamaCppBackend._TENSOR_PARALLEL_BUFFER_RESERVE_MIB
- ec, mac, gi, ts = b._plan_tensor_parallel([(0, 48000), (1, reserve - 1)], int(8 * _GB), 8192)
+ ec, mac, gi, ts = b._plan_tensor_parallel(
+ [(0, 48000), (1, reserve - 1)], int(8 * _GB), 8192
+ )
assert gi == [0]
assert ts is None
@@ -458,7 +474,9 @@ class _RecordingLoader:
self.calls: list[tuple] = []
async def __call__(self, tensor_parallel, extra_args):
- self.calls.append((tensor_parallel, list(extra_args) if extra_args else extra_args))
+ self.calls.append(
+ (tensor_parallel, list(extra_args) if extra_args else extra_args)
+ )
if resolve_tensor_parallel(extra_args, tensor_parallel):
raise RuntimeError("llama-server failed to start")
return True
@@ -467,7 +485,9 @@ class _RecordingLoader:
def test_tensor_fallback_retries_layer_on_crash():
loader = _RecordingLoader()
ok = asyncio.run(
- load_with_tensor_fallback(loader, requested_tensor = True, extra_args = None, label = "m")
+ load_with_tensor_fallback(
+ loader, requested_tensor = True, extra_args = None, label = "m"
+ )
)
assert ok is True
# tensor first (crashes), then layer split.
@@ -482,7 +502,9 @@ def test_tensor_fallback_no_retry_on_success():
return True
ok = asyncio.run(
- load_with_tensor_fallback(_ok, requested_tensor = True, extra_args = None, label = "m")
+ load_with_tensor_fallback(
+ _ok, requested_tensor = True, extra_args = None, label = "m"
+ )
)
assert ok is True
assert calls == [True] # no fallback when the tensor load succeeds
@@ -516,7 +538,9 @@ def test_tensor_fallback_returns_false_when_both_attempts_fail():
return False
ok = asyncio.run(
- load_with_tensor_fallback(_always_false, requested_tensor = True, extra_args = None, label = "m")
+ load_with_tensor_fallback(
+ _always_false, requested_tensor = True, extra_args = None, label = "m"
+ )
)
assert ok is False
assert calls == [True, False] # tried tensor, then layer split
@@ -559,7 +583,9 @@ def test_tensor_fallback_strips_split_mode_from_extras_on_retry(extras):
# else resolve_tensor_parallel re-enables tensor and relaunches the crash.
loader = _RecordingLoader()
ok = asyncio.run(
- load_with_tensor_fallback(loader, requested_tensor = False, extra_args = extras, label = "m")
+ load_with_tensor_fallback(
+ loader, requested_tensor = False, extra_args = extras, label = "m"
+ )
)
assert ok is True
assert len(loader.calls) == 2
diff --git a/studio/backend/tests/test_tool_approvals.py b/studio/backend/tests/test_tool_approvals.py
index af792e652c..9a5af18893 100644
--- a/studio/backend/tests/test_tool_approvals.py
+++ b/studio/backend/tests/test_tool_approvals.py
@@ -246,7 +246,9 @@ def test_concurrent_distinct_calls_route_their_own_decisions():
for i in range(n):
aid = new_approval_id()
waiters[aid] = _Waiter(f"s{i}", aid).start()
- expected = {aid: ("allow" if i % 2 == 0 else "deny") for i, aid in enumerate(waiters)}
+ expected = {
+ aid: ("allow" if i % 2 == 0 else "deny") for i, aid in enumerate(waiters)
+ }
for aid, decision in expected.items():
assert resolve_tool_decision(aid, decision) is True
for aid, w in waiters.items():
diff --git a/studio/backend/tests/test_tool_call_parser_strict.py b/studio/backend/tests/test_tool_call_parser_strict.py
index 8ff41342d7..2f7e7eb8a7 100644
--- a/studio/backend/tests/test_tool_call_parser_strict.py
+++ b/studio/backend/tests/test_tool_call_parser_strict.py
@@ -40,7 +40,9 @@ class TestFunctionStyleTrailingText:
assert call == {"name": "web_search", "arguments": {"query": "weather london"}}
def test_closed_function_with_trailing_whitespace_is_accepted(self):
- text = "cats \n\n"
+ text = (
+ "cats \n\n"
+ )
call = _only(text)
assert call == {"name": "web_search", "arguments": {"query": "cats"}}
diff --git a/studio/backend/tests/test_tool_confirm_loop.py b/studio/backend/tests/test_tool_confirm_loop.py
index ce7852c95f..9e3aa90e2e 100644
--- a/studio/backend/tests/test_tool_confirm_loop.py
+++ b/studio/backend/tests/test_tool_confirm_loop.py
@@ -99,7 +99,9 @@ def _drive(
if ev["type"] == "tool_start" and ev.get("awaiting_confirmation"):
# Slot is already registered (begin ran before this yield), so
# the decision lands before the loop enters its blocking wait.
- resolve_tool_decision(ev["approval_id"], next(decision_iter), session_id = _SESSION)
+ resolve_tool_decision(
+ ev["approval_id"], next(decision_iter), session_id = _SESSION
+ )
return events, exec_fn.calls
diff --git a/studio/backend/tests/test_tool_confirm_stream.py b/studio/backend/tests/test_tool_confirm_stream.py
index b8e0472e12..986f0cb2fd 100644
--- a/studio/backend/tests/test_tool_confirm_stream.py
+++ b/studio/backend/tests/test_tool_confirm_stream.py
@@ -69,7 +69,9 @@ def _build_app() -> FastAPI:
"approval_id": approval_id,
"awaiting_confirmation": True,
}
- denied = wait_tool_decision(slot, approval_id, cancel_event = cancel_event) == "deny"
+ denied = (
+ wait_tool_decision(slot, approval_id, cancel_event = cancel_event) == "deny"
+ )
result = TOOL_REJECTED_MESSAGE if denied else _EXECUTED_RESULT
yield {"type": "tool_end", "tool_name": "python", "result": result}
@@ -116,7 +118,9 @@ class _Server:
def __init__(self, app):
self.port = _free_port()
- config = uvicorn.Config(app, host = "127.0.0.1", port = self.port, log_level = "warning")
+ config = uvicorn.Config(
+ app, host = "127.0.0.1", port = self.port, log_level = "warning"
+ )
self.server = uvicorn.Server(config)
self._thread = threading.Thread(target = self.server.run, daemon = True)
@@ -159,7 +163,9 @@ async def _drive(base_url, session_id, decision):
resolved = None
timeout = httpx.Timeout(10.0)
async with httpx.AsyncClient(base_url = base_url, timeout = timeout) as client:
- async with client.stream("POST", "/stream", json = {"session_id": session_id}) as resp:
+ async with client.stream(
+ "POST", "/stream", json = {"session_id": session_id}
+ ) as resp:
assert resp.status_code == 200
async for line in resp.aiter_lines():
if not line.startswith("data: "):
diff --git a/studio/backend/tests/test_tool_loop_controller.py b/studio/backend/tests/test_tool_loop_controller.py
index dea5de6d6e..6783a59af6 100644
--- a/studio/backend/tests/test_tool_loop_controller.py
+++ b/studio/backend/tests/test_tool_loop_controller.py
@@ -69,7 +69,10 @@ def test_status_and_provenance_match_local_event_conventions():
status_for_tool("web_search", {"url": "https://www.example.com/a"})
== "Reading: example.com"
)
- assert status_for_tool("python", {"code": "print(1)\nprint(2)"}) == "Running Python: print(1)"
+ assert (
+ status_for_tool("python", {"code": "print(1)\nprint(2)"})
+ == "Running Python: print(1)"
+ )
assert tool_event_provenance(healed = True, forced = False, provisional = None) == {
"source": "local",
"healed": True,
@@ -85,7 +88,10 @@ def test_prepare_execute_builds_visible_events_and_model_tool_message():
assert decision.status_text == "Searching: gpu prices"
assert decision.tool_start_payload()["arguments"] == {"query": "gpu prices"}
assert decision.tool_start_event()["type"] == "tool_start"
- assert decision.as_assistant_tool_call()["function"]["arguments"] == '{"query":"gpu prices"}'
+ assert (
+ decision.as_assistant_tool_call()["function"]["arguments"]
+ == '{"query":"gpu prices"}'
+ )
completion = controller.record_result(decision, "Search result\n__IMAGES__:{...}")
@@ -101,10 +107,14 @@ def test_prepare_execute_builds_visible_events_and_model_tool_message():
def test_successful_duplicate_is_internal_noop_and_keeps_remaining_tools():
controller = ToolLoopController(tools = [_tool("web_search"), _tool("python")])
- first = controller.prepare_call(_call("web_search", {"query": "gpu prices"}, "call_a"))
+ first = controller.prepare_call(
+ _call("web_search", {"query": "gpu prices"}, "call_a")
+ )
controller.record_result(first, "ok")
- duplicate = controller.prepare_call(_call("web_search", {"query": "gpu prices"}, "call_b"))
+ duplicate = controller.prepare_call(
+ _call("web_search", {"query": "gpu prices"}, "call_b")
+ )
completion = controller.record_noop(duplicate)
assert duplicate.action == "duplicate"
@@ -123,10 +133,14 @@ def test_successful_duplicate_is_internal_noop_and_keeps_remaining_tools():
def test_repeated_successful_duplicate_becomes_terminal_after_one_recovery_nudge():
controller = ToolLoopController(tools = [_tool("web_search"), _tool("python")])
- first = controller.prepare_call(_call("web_search", {"query": "gpu prices"}, "call_a"))
+ first = controller.prepare_call(
+ _call("web_search", {"query": "gpu prices"}, "call_a")
+ )
controller.record_result(first, "ok")
- duplicate_one = controller.prepare_call(_call("web_search", {"query": "gpu prices"}, "call_b"))
+ duplicate_one = controller.prepare_call(
+ _call("web_search", {"query": "gpu prices"}, "call_b")
+ )
completion_one = controller.record_noop(duplicate_one)
assert duplicate_one.action == "duplicate"
@@ -137,7 +151,9 @@ def test_repeated_successful_duplicate_becomes_terminal_after_one_recovery_nudge
"python",
]
- duplicate_two = controller.prepare_call(_call("web_search", {"query": "gpu prices"}, "call_c"))
+ duplicate_two = controller.prepare_call(
+ _call("web_search", {"query": "gpu prices"}, "call_c")
+ )
completion_two = controller.record_noop(duplicate_two)
assert duplicate_two.action == "duplicate"
@@ -190,12 +206,16 @@ def test_render_html_success_filters_active_tools_and_repeat_is_internal():
"web_search",
]
- first = controller.prepare_call(_call("render_html", {"code": ""}, "call_html_1"))
+ first = controller.prepare_call(
+ _call("render_html", {"code": ""}, "call_html_1")
+ )
controller.record_result(first, "Rendered HTML artifact: Demo")
assert [t["function"]["name"] for t in controller.active_tools()] == ["web_search"]
- repeat = controller.prepare_call(_call("render_html", {"code": ""}, "call_html_2"))
+ repeat = controller.prepare_call(
+ _call("render_html", {"code": ""}, "call_html_2")
+ )
completion = controller.record_noop(repeat)
assert repeat.action == "render_html_repeat"
diff --git a/studio/backend/tests/test_tool_xml_strip.py b/studio/backend/tests/test_tool_xml_strip.py
index 2ba3310fbe..4e3350144f 100644
--- a/studio/backend/tests/test_tool_xml_strip.py
+++ b/studio/backend/tests/test_tool_xml_strip.py
@@ -43,7 +43,9 @@ _strip_tool_xml_for_display = _ns["_strip_tool_xml_for_display"]
def test_route_display_strip_respects_disabled_auto_heal_contract():
text = 'literal {"name":"web_search"} survives'
assert _strip_tool_xml_for_display(text, auto_heal_tool_calls = False) == text
- assert "" not in _strip_tool_xml_for_display(text, auto_heal_tool_calls = True)
+ assert "" not in _strip_tool_xml_for_display(
+ text, auto_heal_tool_calls = True
+ )
def test_strips_well_formed_tool_call():
@@ -91,7 +93,9 @@ def test_strips_orphan_tool_call_no_close():
def test_strips_orphan_function_no_close():
- text = "I'll call python:\n\n\nprint(1)\n"
+ text = (
+ "I'll call python:\n\n\nprint(1)\n"
+ )
cleaned = _TOOL_XML_RE.sub("", text)
assert "")
self.assertEqual(result.dataset[1]["text"], "world")
self.assertTrue(
- any("null or non-string 'text' values" in notice.message for notice in result.notices)
+ any(
+ "null or non-string 'text' values" in notice.message
+ for notice in result.notices
+ )
)
diff --git a/studio/backend/tests/test_training_resume.py b/studio/backend/tests/test_training_resume.py
index 91fdac9961..40df3c8f4b 100644
--- a/studio/backend/tests/test_training_resume.py
+++ b/studio/backend/tests/test_training_resume.py
@@ -67,7 +67,9 @@ def test_can_resume_run_rejects_s3_dataset_source(monkeypatch):
def test_can_resume_run_rejects_s3_metadata_marker(monkeypatch):
monkeypatch.setattr(resume, "has_resume_state", lambda _path: True)
- run = _stopped_run(config_json = json.dumps({"s3_dataset": {"bucket": "training-data"}}))
+ run = _stopped_run(
+ config_json = json.dumps({"s3_dataset": {"bucket": "training-data"}})
+ )
assert resume.can_resume_run(run) is False
@@ -77,7 +79,9 @@ def test_list_runs_includes_config_json_for_resume_policy(monkeypatch, tmp_path)
monkeypatch.setenv("UNSLOTH_STUDIO_HOME", str(tmp_path))
monkeypatch.setattr(studio_db, "_schema_ready", False)
- config_json = json.dumps({"dataset_source": "s3", "s3_dataset": {"bucket": "training-data"}})
+ config_json = json.dumps(
+ {"dataset_source": "s3", "s3_dataset": {"bucket": "training-data"}}
+ )
studio_db.create_run(
id = "run-s3",
diff --git a/studio/backend/tests/test_training_worker_flash_attn.py b/studio/backend/tests/test_training_worker_flash_attn.py
index 3c5d6cd094..363bf133a4 100644
--- a/studio/backend/tests/test_training_worker_flash_attn.py
+++ b/studio/backend/tests/test_training_worker_flash_attn.py
@@ -49,7 +49,9 @@ def _missing_module_import(missing: str):
def test_should_try_runtime_flash_attn_install_threshold_and_skip(monkeypatch):
monkeypatch.delenv(worker._FLASH_ATTN_SKIP_ENV, raising = False)
assert worker._should_try_runtime_flash_attn_install(32767) is False
- assert worker._should_try_runtime_flash_attn_install(32768) is sys.platform.startswith("linux")
+ assert worker._should_try_runtime_flash_attn_install(
+ 32768
+ ) is sys.platform.startswith("linux")
monkeypatch.setenv(worker._FLASH_ATTN_SKIP_ENV, "1")
assert worker._should_try_runtime_flash_attn_install(32768) is False
@@ -146,7 +148,9 @@ def test_runtime_flash_attn_skips_on_blackwell(monkeypatch):
install_mock = mock.Mock()
monkeypatch.delenv(worker._FLASH_ATTN_SKIP_ENV, raising = False)
- monkeypatch.setattr(worker, "_should_try_runtime_flash_attn_install", lambda max_seq: True)
+ monkeypatch.setattr(
+ worker, "_should_try_runtime_flash_attn_install", lambda max_seq: True
+ )
monkeypatch.setattr(worker, "has_blackwell_gpu", lambda: True)
monkeypatch.setattr(worker, "_install_package_wheel_first", install_mock)
monkeypatch.setattr(
@@ -512,10 +516,14 @@ def test_tilelang_backend_reinstalls_when_tvm_ffi_is_broken(monkeypatch):
# Repair: --force-reinstall --no-deps, apache-tvm-ffi ONLY.
assert "--force-reinstall" in repair_args
- assert "--no-deps" in repair_args, "Repair MUST use --no-deps to avoid replacing torch / CUDA"
+ assert (
+ "--no-deps" in repair_args
+ ), "Repair MUST use --no-deps to avoid replacing torch / CUDA"
assert "--only-binary=:all:" in repair_args
assert f"apache-tvm-ffi=={worker._APACHE_TVM_FFI_PACKAGE_VERSION}" in repair_args
- assert all("tilelang" not in a for a in repair_args), "Repair MUST only touch apache-tvm-ffi"
+ assert all(
+ "tilelang" not in a for a in repair_args
+ ), "Repair MUST only touch apache-tvm-ffi"
# Install: regular dep-resolving install, no --force-reinstall.
assert "--force-reinstall" not in install_args
@@ -689,12 +697,16 @@ def test_hook_installs_when_gate_returns_false(monkeypatch):
conv_install = mock.Mock(side_effect = _conv_install_side_effect)
- monkeypatch.setattr(worker, "_ensure_flash_linear_attention_unconditional", fla_install)
+ monkeypatch.setattr(
+ worker, "_ensure_flash_linear_attention_unconditional", fla_install
+ )
monkeypatch.setattr(worker, "_ensure_tilelang_backend_unconditional", tile_install)
monkeypatch.setattr(worker, "_install_package_wheel_first", conv_install)
monkeypatch.delenv(worker._FAST_PATH_HOOKS_SKIP_ENV, raising = False)
- worker._install_fast_path_hooks(event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B")
+ worker._install_fast_path_hooks(
+ event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B"
+ )
from transformers.utils import import_utils as _iu
@@ -718,7 +730,9 @@ def test_hook_skips_install_when_gate_already_true(monkeypatch):
fla_install = mock.Mock()
tile_install = mock.Mock()
conv_install = mock.Mock()
- monkeypatch.setattr(worker, "_ensure_flash_linear_attention_unconditional", fla_install)
+ monkeypatch.setattr(
+ worker, "_ensure_flash_linear_attention_unconditional", fla_install
+ )
monkeypatch.setattr(worker, "_ensure_tilelang_backend_unconditional", tile_install)
monkeypatch.setattr(worker, "_install_package_wheel_first", conv_install)
# Tilelang healthy -> post_available path is a no-op (otherwise it
@@ -727,7 +741,9 @@ def test_hook_skips_install_when_gate_already_true(monkeypatch):
monkeypatch.setattr(worker, "_installed_tvm_ffi_version", lambda: "0.1.9")
monkeypatch.delenv(worker._FAST_PATH_HOOKS_SKIP_ENV, raising = False)
- worker._install_fast_path_hooks(event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B")
+ worker._install_fast_path_hooks(
+ event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B"
+ )
from transformers.utils import import_utils as _iu
@@ -755,12 +771,16 @@ def test_hook_idempotent_on_repeat_call(monkeypatch):
return True
conv_install = mock.Mock(side_effect = _conv_install_side_effect)
- monkeypatch.setattr(worker, "_ensure_flash_linear_attention_unconditional", fla_install)
+ monkeypatch.setattr(
+ worker, "_ensure_flash_linear_attention_unconditional", fla_install
+ )
monkeypatch.setattr(worker, "_ensure_tilelang_backend_unconditional", tile_install)
monkeypatch.setattr(worker, "_install_package_wheel_first", conv_install)
monkeypatch.delenv(worker._FAST_PATH_HOOKS_SKIP_ENV, raising = False)
- worker._install_fast_path_hooks(event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B")
+ worker._install_fast_path_hooks(
+ event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B"
+ )
from transformers.utils import import_utils as _iu
@@ -781,12 +801,18 @@ def test_hook_handles_install_failure_gracefully(monkeypatch):
def raising_install(eq):
raise RuntimeError("pip failed to fetch wheel")
- monkeypatch.setattr(worker, "_ensure_flash_linear_attention_unconditional", raising_install)
- monkeypatch.setattr(worker, "_ensure_tilelang_backend_unconditional", lambda eq: None)
+ monkeypatch.setattr(
+ worker, "_ensure_flash_linear_attention_unconditional", raising_install
+ )
+ monkeypatch.setattr(
+ worker, "_ensure_tilelang_backend_unconditional", lambda eq: None
+ )
monkeypatch.setattr(worker, "_install_package_wheel_first", lambda **kw: None)
monkeypatch.delenv(worker._FAST_PATH_HOOKS_SKIP_ENV, raising = False)
- worker._install_fast_path_hooks(event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B")
+ worker._install_fast_path_hooks(
+ event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B"
+ )
from transformers.utils import import_utils as _iu
@@ -800,10 +826,14 @@ def test_hook_can_be_disabled_via_env(monkeypatch):
_patch_iu_gates(monkeypatch, fla_gate, conv_gate)
fla_install = mock.Mock()
- monkeypatch.setattr(worker, "_ensure_flash_linear_attention_unconditional", fla_install)
+ monkeypatch.setattr(
+ worker, "_ensure_flash_linear_attention_unconditional", fla_install
+ )
monkeypatch.setenv(worker._FAST_PATH_HOOKS_SKIP_ENV, "1")
- worker._install_fast_path_hooks(event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B")
+ worker._install_fast_path_hooks(
+ event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B"
+ )
from transformers.utils import import_utils as _iu
@@ -818,12 +848,18 @@ def test_hook_clears_lru_cache_before_first_check(monkeypatch):
conv_gate = _make_fake_gate(initial_return = True)
_patch_iu_gates(monkeypatch, fla_gate, conv_gate)
- monkeypatch.setattr(worker, "_ensure_flash_linear_attention_unconditional", lambda eq: None)
- monkeypatch.setattr(worker, "_ensure_tilelang_backend_unconditional", lambda eq: None)
+ monkeypatch.setattr(
+ worker, "_ensure_flash_linear_attention_unconditional", lambda eq: None
+ )
+ monkeypatch.setattr(
+ worker, "_ensure_tilelang_backend_unconditional", lambda eq: None
+ )
monkeypatch.setattr(worker, "_install_package_wheel_first", lambda **kw: None)
monkeypatch.delenv(worker._FAST_PATH_HOOKS_SKIP_ENV, raising = False)
- worker._install_fast_path_hooks(event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B")
+ worker._install_fast_path_hooks(
+ event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B"
+ )
from transformers.utils import import_utils as _iu
_iu.is_flash_linear_attention_available()
@@ -850,12 +886,18 @@ def test_hook_rewrites_previously_imported_module_bindings(monkeypatch):
fla_gate.next_return = True
return True
- monkeypatch.setattr(worker, "_ensure_flash_linear_attention_unconditional", fake_install)
- monkeypatch.setattr(worker, "_ensure_tilelang_backend_unconditional", lambda eq: True)
+ monkeypatch.setattr(
+ worker, "_ensure_flash_linear_attention_unconditional", fake_install
+ )
+ monkeypatch.setattr(
+ worker, "_ensure_tilelang_backend_unconditional", lambda eq: True
+ )
monkeypatch.setattr(worker, "_install_package_wheel_first", lambda **kw: True)
monkeypatch.delenv(worker._FAST_PATH_HOOKS_SKIP_ENV, raising = False)
- worker._install_fast_path_hooks(event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B")
+ worker._install_fast_path_hooks(
+ event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B"
+ )
# The fake module's local binding is rewritten to the wrapper.
assert fake_mod.is_flash_linear_attention_available is not fla_gate
@@ -879,20 +921,30 @@ def test_hook_skips_when_import_utils_unavailable(monkeypatch):
monkeypatch.delenv(worker._FAST_PATH_HOOKS_SKIP_ENV, raising = False)
# Should not raise.
- worker._install_fast_path_hooks(event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B")
+ worker._install_fast_path_hooks(
+ event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B"
+ )
def test_substring_fallback_unchanged_when_hook_skipped(monkeypatch):
"""Hook disabled -> legacy gate falls back to auto-discovered types."""
install_mock = mock.Mock()
- monkeypatch.setattr(worker, "_ensure_flash_linear_attention_unconditional", install_mock)
- monkeypatch.setattr(worker, "_discover_fla_model_types", lambda: frozenset({"qwen3_5"}))
+ monkeypatch.setattr(
+ worker, "_ensure_flash_linear_attention_unconditional", install_mock
+ )
+ monkeypatch.setattr(
+ worker, "_discover_fla_model_types", lambda: frozenset({"qwen3_5"})
+ )
monkeypatch.setenv(worker._FAST_PATH_HOOKS_SKIP_ENV, "1")
- worker._ensure_flash_linear_attention(event_queue = [], model_name = "unsloth/Qwen3.5-2B")
+ worker._ensure_flash_linear_attention(
+ event_queue = [], model_name = "unsloth/Qwen3.5-2B"
+ )
assert install_mock.call_count == 1
- worker._ensure_flash_linear_attention(event_queue = [], model_name = "meta-llama/Llama-3.1-8B")
+ worker._ensure_flash_linear_attention(
+ event_queue = [], model_name = "meta-llama/Llama-3.1-8B"
+ )
assert install_mock.call_count == 1
@@ -920,9 +972,13 @@ def test_hook_does_not_install_tilelang_for_model_outside_allowlist(monkeypatch)
fla_install = mock.Mock(side_effect = _fla_install)
tile_install = mock.Mock(return_value = True)
- monkeypatch.setattr(worker, "_ensure_flash_linear_attention_unconditional", fla_install)
+ monkeypatch.setattr(
+ worker, "_ensure_flash_linear_attention_unconditional", fla_install
+ )
monkeypatch.setattr(worker, "_ensure_tilelang_backend_unconditional", tile_install)
- monkeypatch.setattr(worker, "_install_package_wheel_first", mock.Mock(return_value = True))
+ monkeypatch.setattr(
+ worker, "_install_package_wheel_first", mock.Mock(return_value = True)
+ )
monkeypatch.delenv(worker._FAST_PATH_HOOKS_SKIP_ENV, raising = False)
# Hermetize the auto-discovered set so the test stays valid as new
# transformers releases add FLA-using model_types (eg olmo_hybrid in
@@ -957,12 +1013,18 @@ def test_hook_does_install_tilelang_for_qwen35(monkeypatch):
fla_install = mock.Mock(side_effect = _fla_install)
tile_install = mock.Mock(return_value = True)
- monkeypatch.setattr(worker, "_ensure_flash_linear_attention_unconditional", fla_install)
+ monkeypatch.setattr(
+ worker, "_ensure_flash_linear_attention_unconditional", fla_install
+ )
monkeypatch.setattr(worker, "_ensure_tilelang_backend_unconditional", tile_install)
- monkeypatch.setattr(worker, "_install_package_wheel_first", mock.Mock(return_value = True))
+ monkeypatch.setattr(
+ worker, "_install_package_wheel_first", mock.Mock(return_value = True)
+ )
monkeypatch.delenv(worker._FAST_PATH_HOOKS_SKIP_ENV, raising = False)
- worker._install_fast_path_hooks(event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B")
+ worker._install_fast_path_hooks(
+ event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B"
+ )
from transformers.utils import import_utils as _iu
@@ -1011,14 +1073,20 @@ def test_hook_trusts_installer_bool_not_metadata(monkeypatch):
return False # but deep import is broken
fake_fla_install = mock.Mock(side_effect = _bad_install)
- monkeypatch.setattr(worker, "_ensure_flash_linear_attention_unconditional", fake_fla_install)
+ monkeypatch.setattr(
+ worker, "_ensure_flash_linear_attention_unconditional", fake_fla_install
+ )
monkeypatch.setattr(
worker, "_ensure_tilelang_backend_unconditional", mock.Mock(return_value = True)
)
- monkeypatch.setattr(worker, "_install_package_wheel_first", mock.Mock(return_value = True))
+ monkeypatch.setattr(
+ worker, "_install_package_wheel_first", mock.Mock(return_value = True)
+ )
monkeypatch.delenv(worker._FAST_PATH_HOOKS_SKIP_ENV, raising = False)
- worker._install_fast_path_hooks(event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B")
+ worker._install_fast_path_hooks(
+ event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B"
+ )
from transformers.utils import import_utils as _iu
@@ -1071,10 +1139,14 @@ def test_hook_skips_tilelang_when_fla_install_is_skipped(monkeypatch):
monkeypatch.setenv(worker._FLA_SKIP_ENV, "1")
tile_install = mock.Mock(return_value = True)
monkeypatch.setattr(worker, "_ensure_tilelang_backend_unconditional", tile_install)
- monkeypatch.setattr(worker, "_install_package_wheel_first", mock.Mock(return_value = True))
+ monkeypatch.setattr(
+ worker, "_install_package_wheel_first", mock.Mock(return_value = True)
+ )
monkeypatch.delenv(worker._FAST_PATH_HOOKS_SKIP_ENV, raising = False)
- worker._install_fast_path_hooks(event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B")
+ worker._install_fast_path_hooks(
+ event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B"
+ )
from transformers.utils import import_utils as _iu
@@ -1094,15 +1166,21 @@ def test_hook_runs_tilelang_repair_when_fla_already_true(monkeypatch):
fla_install = mock.Mock(return_value = True)
tile_install = mock.Mock(return_value = True)
- monkeypatch.setattr(worker, "_ensure_flash_linear_attention_unconditional", fla_install)
+ monkeypatch.setattr(
+ worker, "_ensure_flash_linear_attention_unconditional", fla_install
+ )
monkeypatch.setattr(worker, "_ensure_tilelang_backend_unconditional", tile_install)
- monkeypatch.setattr(worker, "_install_package_wheel_first", mock.Mock(return_value = True))
+ monkeypatch.setattr(
+ worker, "_install_package_wheel_first", mock.Mock(return_value = True)
+ )
# tilelang missing AND tvm-ffi on broken list — both trigger repair.
monkeypatch.setattr(worker, "_tilelang_importable", lambda: False)
monkeypatch.setattr(worker, "_installed_tvm_ffi_version", lambda: "0.1.11")
monkeypatch.delenv(worker._FAST_PATH_HOOKS_SKIP_ENV, raising = False)
- worker._install_fast_path_hooks(event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B")
+ worker._install_fast_path_hooks(
+ event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B"
+ )
from transformers.utils import import_utils as _iu
@@ -1199,11 +1277,17 @@ def test_install_fast_path_hooks_sets_fla_tilelang_zero_on_hip(monkeypatch):
monkeypatch.delenv("FLA_TILELANG", raising = False)
monkeypatch.delenv(worker._FAST_PATH_HOOKS_SKIP_ENV, raising = False)
monkeypatch.setattr(worker, "_torch_has_hip", lambda: True)
- monkeypatch.setattr(worker, "_ensure_flash_linear_attention_unconditional", lambda eq: True)
- monkeypatch.setattr(worker, "_ensure_tilelang_backend_unconditional", lambda eq: True)
+ monkeypatch.setattr(
+ worker, "_ensure_flash_linear_attention_unconditional", lambda eq: True
+ )
+ monkeypatch.setattr(
+ worker, "_ensure_tilelang_backend_unconditional", lambda eq: True
+ )
monkeypatch.setattr(worker, "_install_package_wheel_first", lambda **kw: True)
- worker._install_fast_path_hooks(event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B")
+ worker._install_fast_path_hooks(
+ event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B"
+ )
assert _os.environ.get("FLA_TILELANG") == "0"
@@ -1217,11 +1301,17 @@ def test_install_fast_path_hooks_respects_user_fla_tilelang_override(monkeypatch
monkeypatch.setenv("FLA_TILELANG", "1")
monkeypatch.delenv(worker._FAST_PATH_HOOKS_SKIP_ENV, raising = False)
monkeypatch.setattr(worker, "_torch_has_hip", lambda: True)
- monkeypatch.setattr(worker, "_ensure_flash_linear_attention_unconditional", lambda eq: True)
- monkeypatch.setattr(worker, "_ensure_tilelang_backend_unconditional", lambda eq: True)
+ monkeypatch.setattr(
+ worker, "_ensure_flash_linear_attention_unconditional", lambda eq: True
+ )
+ monkeypatch.setattr(
+ worker, "_ensure_tilelang_backend_unconditional", lambda eq: True
+ )
monkeypatch.setattr(worker, "_install_package_wheel_first", lambda **kw: True)
- worker._install_fast_path_hooks(event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B")
+ worker._install_fast_path_hooks(
+ event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B"
+ )
assert _os.environ["FLA_TILELANG"] == "1"
@@ -1233,11 +1323,17 @@ def test_install_fast_path_hooks_does_not_set_fla_tilelang_on_cuda(monkeypatch):
monkeypatch.delenv("FLA_TILELANG", raising = False)
monkeypatch.delenv(worker._FAST_PATH_HOOKS_SKIP_ENV, raising = False)
monkeypatch.setattr(worker, "_torch_has_hip", lambda: False)
- monkeypatch.setattr(worker, "_ensure_flash_linear_attention_unconditional", lambda eq: True)
- monkeypatch.setattr(worker, "_ensure_tilelang_backend_unconditional", lambda eq: True)
+ monkeypatch.setattr(
+ worker, "_ensure_flash_linear_attention_unconditional", lambda eq: True
+ )
+ monkeypatch.setattr(
+ worker, "_ensure_tilelang_backend_unconditional", lambda eq: True
+ )
monkeypatch.setattr(worker, "_install_package_wheel_first", lambda **kw: True)
- worker._install_fast_path_hooks(event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B")
+ worker._install_fast_path_hooks(
+ event_queue = _FakeQueue(), model_name = "unsloth/Qwen3.5-2B"
+ )
assert _os.environ.get("FLA_TILELANG") is None
@@ -1247,7 +1343,9 @@ def test_install_fast_path_hooks_does_not_set_fla_tilelang_on_cuda(monkeypatch):
# ───────────────────────────────────────────────────────────────────
-def _make_fake_transformers_tree(tmp_path, fla_types: list[str], non_fla_types: list[str]):
+def _make_fake_transformers_tree(
+ tmp_path, fla_types: list[str], non_fla_types: list[str]
+):
"""Lay out tmp dir as `transformers/models/{type}/modeling_{type}.py`."""
pkg = tmp_path / "transformers"
models = pkg / "models"
@@ -1290,7 +1388,9 @@ def test_discover_fla_model_types_returns_only_fla_users(tmp_path, monkeypatch):
def test_discover_fla_model_types_caches_across_calls(tmp_path, monkeypatch):
- pkg = _make_fake_transformers_tree(tmp_path, fla_types = ["qwen3_5"], non_fla_types = [])
+ pkg = _make_fake_transformers_tree(
+ tmp_path, fla_types = ["qwen3_5"], non_fla_types = []
+ )
fake = mock.MagicMock(__file__ = str(pkg / "__init__.py"))
monkeypatch.setitem(sys.modules, "transformers", fake)
_reset_fla_cache(monkeypatch)
@@ -1336,7 +1436,9 @@ def test_discover_fla_model_types_handles_missing_transformers(monkeypatch):
def test_discover_fla_model_types_handles_unreadable_file(tmp_path, monkeypatch):
- pkg = _make_fake_transformers_tree(tmp_path, fla_types = ["qwen3_5"], non_fla_types = [])
+ pkg = _make_fake_transformers_tree(
+ tmp_path, fla_types = ["qwen3_5"], non_fla_types = []
+ )
fake = mock.MagicMock(__file__ = str(pkg / "__init__.py"))
monkeypatch.setitem(sys.modules, "transformers", fake)
_reset_fla_cache(monkeypatch)
@@ -1382,7 +1484,9 @@ def test_model_wants_tilelang_empty_when_transformers_has_no_fla(monkeypatch):
def test_model_wants_tilelang_normalizes_separators(monkeypatch):
- monkeypatch.setattr(worker, "_discover_fla_model_types", lambda: frozenset({"qwen3_next"}))
+ monkeypatch.setattr(
+ worker, "_discover_fla_model_types", lambda: frozenset({"qwen3_next"})
+ )
for variant in (
"qwen3-next",
"Qwen3.Next",
diff --git a/studio/backend/tests/test_transformers_version.py b/studio/backend/tests/test_transformers_version.py
index 7c497ba1b1..4089ea98d8 100644
--- a/studio/backend/tests/test_transformers_version.py
+++ b/studio/backend/tests/test_transformers_version.py
@@ -480,7 +480,9 @@ class TestGetTransformersTier:
return_value = False,
),
):
- assert get_transformers_tier("mistralai/Ministral-3-8B-Instruct-2512") == "530"
+ assert (
+ get_transformers_tier("mistralai/Ministral-3-8B-Instruct-2512") == "530"
+ )
def test_llama_returns_default(self):
with (
diff --git a/studio/backend/tests/test_utils.py b/studio/backend/tests/test_utils.py
index c66d56528a..49c7660226 100644
--- a/studio/backend/tests/test_utils.py
+++ b/studio/backend/tests/test_utils.py
@@ -187,7 +187,9 @@ class TestGetGpuMemoryInfo:
# --- When a GPU IS available ---
- @pytest.mark.skipif(_actual_device() == "cpu", reason = "No GPU available on this machine")
+ @pytest.mark.skipif(
+ _actual_device() == "cpu", reason = "No GPU available on this machine"
+ )
def test_gpu_available_fields(self):
result = get_gpu_memory_info()
assert result["available"] is True
@@ -285,7 +287,9 @@ class TestLogGpuMemory:
"free_gb": 14.0,
}
- with patch("utils.hardware.hardware.get_gpu_memory_info", return_value = fake_info):
+ with patch(
+ "utils.hardware.hardware.get_gpu_memory_info", return_value = fake_info
+ ):
log_gpu_memory("unit-test")
captured = capfd.readouterr()
@@ -296,7 +300,9 @@ class TestLogGpuMemory:
def test_logs_cpu_fallback_when_no_gpu(self, capfd):
fake_info = {"available": False, "backend": "cpu"}
- with patch("utils.hardware.hardware.get_gpu_memory_info", return_value = fake_info):
+ with patch(
+ "utils.hardware.hardware.get_gpu_memory_info", return_value = fake_info
+ ):
log_gpu_memory("cpu-test")
captured = capfd.readouterr()
diff --git a/studio/backend/tests/test_vision_cache.py b/studio/backend/tests/test_vision_cache.py
index d1bdec8449..d16ab29bcb 100644
--- a/studio/backend/tests/test_vision_cache.py
+++ b/studio/backend/tests/test_vision_cache.py
@@ -131,7 +131,9 @@ class TestLocalGgufVisionDetection:
"utils.models.model_config._is_vision_model_subprocess",
side_effect = AssertionError("GGUF must not use Transformers vision detection"),
)
- def test_qwen36_gguf_with_mmproj_skips_transformers(self, mock_subprocess, tmp_path):
+ def test_qwen36_gguf_with_mmproj_skips_transformers(
+ self, mock_subprocess, tmp_path
+ ):
model = tmp_path / "Qwen3.6-27B-UD-Q4_K_XL-MTP.gguf"
model.write_bytes(b"")
(tmp_path / "mmproj-F32.gguf").write_bytes(b"")
@@ -143,7 +145,9 @@ class TestLocalGgufVisionDetection:
"utils.models.model_config._is_vision_model_subprocess",
side_effect = AssertionError("GGUF must not use Transformers vision detection"),
)
- def test_direct_gguf_in_variant_subdir_finds_snapshot_mmproj(self, mock_subprocess, tmp_path):
+ def test_direct_gguf_in_variant_subdir_finds_snapshot_mmproj(
+ self, mock_subprocess, tmp_path
+ ):
variant_dir = tmp_path / "BF16"
variant_dir.mkdir()
model = variant_dir / "Qwen3.6-27B-UD-Q4_K_XL-MTP.gguf"
@@ -157,7 +161,9 @@ class TestLocalGgufVisionDetection:
"utils.models.model_config._is_vision_model_subprocess",
side_effect = AssertionError("GGUF must not use Transformers vision detection"),
)
- def test_qwen36_gguf_without_mmproj_skips_transformers(self, mock_subprocess, tmp_path):
+ def test_qwen36_gguf_without_mmproj_skips_transformers(
+ self, mock_subprocess, tmp_path
+ ):
model = tmp_path / "Qwen3.6-27B-UD-Q4_K_XL-MTP.gguf"
model.write_bytes(b"")
@@ -289,7 +295,9 @@ class TestVisionCacheDirectPath:
@patch("utils.transformers_version.needs_transformers_5", return_value = False)
@patch("utils.models.model_config.load_model_config")
- def test_vision_config_attr_detected_and_cached(self, mock_load_config, mock_needs_t5):
+ def test_vision_config_attr_detected_and_cached(
+ self, mock_load_config, mock_needs_t5
+ ):
"""Models with vision_config (LLaVA, Qwen2-VL, etc.) should be cached as True."""
cfg = MagicMock(spec = []) # strict: only explicitly set attrs exist
cfg.model_type = "qwen2_vl"
@@ -303,7 +311,9 @@ class TestVisionCacheDirectPath:
@patch("utils.transformers_version.needs_transformers_5", return_value = False)
@patch("utils.models.model_config.load_model_config")
- def test_gemma4_model_type_detected_and_cached(self, mock_load_config, mock_needs_t5):
+ def test_gemma4_model_type_detected_and_cached(
+ self, mock_load_config, mock_needs_t5
+ ):
cfg = MagicMock(spec = [])
cfg.model_type = "gemma4"
cfg.architectures = ["Gemma4ForConditionalGeneration"]
@@ -315,7 +325,9 @@ class TestVisionCacheDirectPath:
@patch("utils.transformers_version.needs_transformers_5", return_value = False)
@patch("utils.models.model_config.load_model_config")
- def test_gemma4_audio_subconfig_not_detected_as_vision(self, mock_load_config, mock_needs_t5):
+ def test_gemma4_audio_subconfig_not_detected_as_vision(
+ self, mock_load_config, mock_needs_t5
+ ):
cfg = MagicMock(spec = [])
cfg.model_type = "gemma4_audio"
cfg.architectures = ["Gemma4AudioModel"]
@@ -327,7 +339,9 @@ class TestVisionCacheDirectPath:
@patch("utils.transformers_version.needs_transformers_5", return_value = False)
@patch("utils.models.model_config.load_model_config")
- def test_gemma4_text_subconfig_not_detected_as_vision(self, mock_load_config, mock_needs_t5):
+ def test_gemma4_text_subconfig_not_detected_as_vision(
+ self, mock_load_config, mock_needs_t5
+ ):
cfg = MagicMock(spec = [])
cfg.model_type = "gemma4_text"
cfg.architectures = ["Gemma4ForCausalLM"]
@@ -472,10 +486,15 @@ class TestSubprocessScript:
is True
)
assert (
- inline_is_vlm(_C(model_type = "gemma4_text", architectures = ["Gemma4ForCausalLM"]))
+ inline_is_vlm(
+ _C(model_type = "gemma4_text", architectures = ["Gemma4ForCausalLM"])
+ )
+ is False
+ )
+ assert (
+ inline_is_vlm(_C(model_type = "llama", architectures = ["LlamaForCausalLM"]))
is False
)
- assert inline_is_vlm(_C(model_type = "llama", architectures = ["LlamaForCausalLM"])) is False
# ---------------------------------------------------------------------------
diff --git a/studio/backend/tests/test_vram_estimation.py b/studio/backend/tests/test_vram_estimation.py
index 2def8738e2..d0e2b03623 100644
--- a/studio/backend/tests/test_vram_estimation.py
+++ b/studio/backend/tests/test_vram_estimation.py
@@ -316,8 +316,12 @@ class TestLoraParams(unittest.TestCase):
self.assertLess(qv_only, all_mods)
def test_moe_mlp_modules_scale_with_experts(self):
- dense_lora = compute_lora_params(LLAMA_8B, 16, ["gate_proj", "up_proj", "down_proj"])
- moe_lora = compute_lora_params(MOE_CONFIG, 16, ["gate_proj", "up_proj", "down_proj"])
+ dense_lora = compute_lora_params(
+ LLAMA_8B, 16, ["gate_proj", "up_proj", "down_proj"]
+ )
+ moe_lora = compute_lora_params(
+ MOE_CONFIG, 16, ["gate_proj", "up_proj", "down_proj"]
+ )
ratio = moe_lora / dense_lora
self.assertAlmostEqual(ratio, 8.0, delta = 0.5)
@@ -334,8 +338,12 @@ class TestLoraParams(unittest.TestCase):
self.assertGreater(moe_lora, dense_lora * 20)
def test_attention_modules_same_for_moe(self):
- dense_attn = compute_lora_params(LLAMA_8B, 16, ["q_proj", "k_proj", "v_proj", "o_proj"])
- moe_attn = compute_lora_params(MOE_CONFIG, 16, ["q_proj", "k_proj", "v_proj", "o_proj"])
+ dense_attn = compute_lora_params(
+ LLAMA_8B, 16, ["q_proj", "k_proj", "v_proj", "o_proj"]
+ )
+ moe_attn = compute_lora_params(
+ MOE_CONFIG, 16, ["q_proj", "k_proj", "v_proj", "o_proj"]
+ )
self.assertEqual(dense_attn, moe_attn)
def test_all_linear_uses_default_text_modules(self):
@@ -458,7 +466,9 @@ class TestActivationBytes(unittest.TestCase):
def test_non_flash_attention_uses_quadratic_path(self):
seq_len = 4096
- expected_quadratic = 1 * STRUCTURED_MIXED.num_attention_heads * seq_len * seq_len * 2 * 12.0
+ expected_quadratic = (
+ 1 * STRUCTURED_MIXED.num_attention_heads * seq_len * seq_len * 2 * 12.0
+ )
for attention_implementation in ("eager", "unknown_impl", None):
with self.subTest(attention_implementation = attention_implementation):
non_flash = compute_activation_bytes(
@@ -473,7 +483,9 @@ class TestActivationBytes(unittest.TestCase):
def test_non_flash_attention_without_gc_scales_quadratic_path_by_layers(self):
seq_len = 4096
- one_layer = 1 * STRUCTURED_MIXED.num_attention_heads * seq_len * seq_len * 2 * 12.0
+ one_layer = (
+ 1 * STRUCTURED_MIXED.num_attention_heads * seq_len * seq_len * 2 * 12.0
+ )
non_flash = compute_activation_bytes(
STRUCTURED_MIXED,
1,
@@ -705,7 +717,9 @@ class TestEstimateTrainingVram(unittest.TestCase):
)
v8 = estimate_training_vram(LLAMA_8B, opt8)
v32 = estimate_training_vram(LLAMA_8B, opt32)
- self.assertAlmostEqual(v32.optimizer_states / v8.optimizer_states, 1.5, delta = 0.1)
+ self.assertAlmostEqual(
+ v32.optimizer_states / v8.optimizer_states, 1.5, delta = 0.1
+ )
def test_min_gpu_vram_treats_activations_as_per_gpu_fixed(self):
config = TrainingVramConfig(training_method = "qlora", load_in_4bit = True)
@@ -755,7 +769,9 @@ class TestEstimateTrainingVram(unittest.TestCase):
optimizer = "adamw_8bit",
load_in_4bit = False,
)
- expected_floor = int(compute_model_weights_bytes(LLAMA_8B, "full", False) * 0.15)
+ expected_floor = int(
+ compute_model_weights_bytes(LLAMA_8B, "full", False) * 0.15
+ )
with patch(
"utils.hardware.vram_estimation.compute_gradient_bytes",
return_value = 1,
@@ -1275,7 +1291,9 @@ class TestSharedExperts(unittest.TestCase):
delta_per_layer = 4096 * 1407 * 3 * 2
expected_delta = delta_per_layer * 32 * 2
actual_delta = w_yes - w_no
- self.assertAlmostEqual(actual_delta, expected_delta, delta = expected_delta * 0.01)
+ self.assertAlmostEqual(
+ actual_delta, expected_delta, delta = expected_delta * 0.01
+ )
def test_deepseek_v3_params_in_range(self):
total = compute_total_params(DEEPSEEK_V3)
@@ -1391,7 +1409,9 @@ class TestDenseMoEMix(unittest.TestCase):
moe_intermediate_size = 1024,
num_dense_layers = 5,
)
- lora_all = compute_lora_params(all_moe, 16, ["gate_proj", "up_proj", "down_proj"])
+ lora_all = compute_lora_params(
+ all_moe, 16, ["gate_proj", "up_proj", "down_proj"]
+ )
lora_mix = compute_lora_params(mixed, 16, ["gate_proj", "up_proj", "down_proj"])
self.assertNotEqual(lora_all, lora_mix)
@@ -1475,7 +1495,9 @@ class TestPerLayerInputSkipAlias(unittest.TestCase):
delta = _compute_skipped_quantizable_elements(arch)
self.assertEqual(
delta,
- arch.hidden_size * arch.num_hidden_layers * arch.hidden_size_per_layer_input,
+ arch.hidden_size
+ * arch.num_hidden_layers
+ * arch.hidden_size_per_layer_input,
)
def test_layer_aggregate_skip_includes_per_layer_input_modules(self):
@@ -1554,7 +1576,9 @@ class TestSharedExpertVariants(unittest.TestCase):
def test_shared_expert_size_separate_from_routed_changes_weight_count(self):
from utils.hardware.vram_estimation import _compute_moe_mlp_elements
- arch_separate = extract_arch_config(self._hf(shared_expert_intermediate_size = 64))
+ arch_separate = extract_arch_config(
+ self._hf(shared_expert_intermediate_size = 64)
+ )
arch_implicit = extract_arch_config(self._hf(n_shared_experts = 1))
# Different shared sizes (64 vs default moe_intermediate_size=128) must
# give different MoE element counts.
@@ -1598,7 +1622,9 @@ class TestSharedExpertActivation(unittest.TestCase):
moe_intermediate_size = 64,
**fields,
)
- return extract_arch_config(SimpleNamespace(text_config = text_config, quantization_config = {}))
+ return extract_arch_config(
+ SimpleNamespace(text_config = text_config, quantization_config = {})
+ )
def test_shared_expert_increases_activation_bytes(self):
with_shared = self._make(shared_expert_intermediate_size = 64)
@@ -1650,7 +1676,9 @@ class TestPerLayerInputActivation(unittest.TestCase):
tie_word_embeddings = False,
**fields,
)
- return extract_arch_config(SimpleNamespace(text_config = text_config, quantization_config = {}))
+ return extract_arch_config(
+ SimpleNamespace(text_config = text_config, quantization_config = {})
+ )
def test_ple_increases_activation_bytes(self):
with_ple = self._make(
@@ -1714,7 +1742,9 @@ class TestKvSharedActivation(unittest.TestCase):
num_kv_shared_layers = kv_shared,
layer_types = ["full_attention"] * 4,
)
- return extract_arch_config(SimpleNamespace(text_config = text_config, quantization_config = {}))
+ return extract_arch_config(
+ SimpleNamespace(text_config = text_config, quantization_config = {})
+ )
def test_kv_shared_layers_keep_activation_bytes(self):
shared = self._make(kv_shared = 2)
@@ -1760,7 +1790,9 @@ class TestSparseMoeSkipAliases(unittest.TestCase):
def test_gemma4_layers_experts_alias_pulls_routed(self):
from utils.hardware.vram_estimation import _compute_skipped_quantizable_elements
- arch = extract_arch_config(self._hf(["model.layers.0.experts"], enable_moe_block = True))
+ arch = extract_arch_config(
+ self._hf(["model.layers.0.experts"], enable_moe_block = True)
+ )
self.assertGreater(_compute_skipped_quantizable_elements(arch), 0)
def test_qwen_shared_expert_skip_pulls_only_shared(self):
@@ -1811,7 +1843,9 @@ class TestAllLinearMoELoraExclusion(unittest.TestCase):
moe_intermediate_size = 64,
**fields,
)
- return extract_arch_config(SimpleNamespace(text_config = text_config, quantization_config = {}))
+ return extract_arch_config(
+ SimpleNamespace(text_config = text_config, quantization_config = {})
+ )
def test_all_linear_drops_routed_moe_expert_lora(self):
arch = self._arch()
@@ -1829,7 +1863,9 @@ class TestAllLinearMoELoraExclusion(unittest.TestCase):
def test_all_linear_includes_attention_lora(self):
arch = self._arch()
all_linear = compute_lora_params(arch, 8, "all-linear")
- attn_only = compute_lora_params(arch, 8, ["q_proj", "k_proj", "v_proj", "o_proj"])
+ attn_only = compute_lora_params(
+ arch, 8, ["q_proj", "k_proj", "v_proj", "o_proj"]
+ )
# all-linear still attaches to attention nn.Linear modules.
self.assertGreaterEqual(all_linear, attn_only)
@@ -1847,7 +1883,9 @@ class TestExplicitPerLayerInputLora(unittest.TestCase):
hidden_size_per_layer_input = 32,
vocab_size_per_layer_input = 128,
)
- return extract_arch_config(SimpleNamespace(text_config = text_config, quantization_config = {}))
+ return extract_arch_config(
+ SimpleNamespace(text_config = text_config, quantization_config = {})
+ )
def test_explicit_per_layer_input_gate_returns_nonzero(self):
arch = self._arch()
@@ -1886,7 +1924,9 @@ class TestTopKExpertActivation(unittest.TestCase):
moe_intermediate_size = 64,
**fields,
)
- return extract_arch_config(SimpleNamespace(text_config = text_config, quantization_config = {}))
+ return extract_arch_config(
+ SimpleNamespace(text_config = text_config, quantization_config = {})
+ )
def test_num_experts_per_tok_extracted(self):
arch = self._make(num_experts_per_tok = 4)
diff --git a/studio/backend/tests/test_windows_gpu_detection_mock.py b/studio/backend/tests/test_windows_gpu_detection_mock.py
index 88a1a28d14..bc06b83b30 100644
--- a/studio/backend/tests/test_windows_gpu_detection_mock.py
+++ b/studio/backend/tests/test_windows_gpu_detection_mock.py
@@ -167,7 +167,9 @@ def _build_path_dirs_like_start_llama_server(
cuda_path: str = "",
) -> list[str]:
"""Wrapper around the real _build_windows_path_dirs staticmethod."""
- return LlamaCppBackend._build_windows_path_dirs(str(binary_dir), str(prefix), cuda_path)
+ return LlamaCppBackend._build_windows_path_dirs(
+ str(binary_dir), str(prefix), cuda_path
+ )
def _mock_nvidia_smi_run(fake_output: str, returncode: int = 0) -> "mock._patch":
@@ -201,7 +203,9 @@ class TestWindowsGpuDetectionAfter5106Fix:
fake_csv = "0, 22805\n"
with _mock_nvidia_smi_run(fake_csv):
gpus = LlamaCppBackend._get_gpu_free_memory()
- assert gpus == [(0, 22805)], f"GPU probe failed to parse mocked nvidia-smi output: {gpus}"
+ assert gpus == [
+ (0, 22805)
+ ], f"GPU probe failed to parse mocked nvidia-smi output: {gpus}"
def test_nvidia_smi_probe_respects_cuda_visible_devices(self, monkeypatch):
"""CUDA_VISIBLE_DEVICES=1 -> only GPU 1 visible."""
@@ -236,7 +240,9 @@ class TestWindowsGpuDetectionAfter5106Fix:
site / "nvidia" / "cu13" / "bin" / "x86_64",
site / "torch" / "lib",
):
- assert str(expected) in out, f"resolver missed {expected.relative_to(prefix)}: {out}"
+ assert (
+ str(expected) in out
+ ), f"resolver missed {expected.relative_to(prefix)}: {out}"
def test_path_assembly_makes_cudart_reachable_without_toolkit(self, tmp_path):
"""The #5106 scenario: GPU detected, pip nvidia wheels present,
@@ -247,7 +253,9 @@ class TestWindowsGpuDetectionAfter5106Fix:
_populate_studio_venv(prefix)
_populate_studio_install(install, runtime = "13.1")
binary_dir = install / "build" / "bin" / "Release"
- path_dirs = _build_path_dirs_like_start_llama_server(binary_dir, prefix, cuda_path = "")
+ path_dirs = _build_path_dirs_like_start_llama_server(
+ binary_dir, prefix, cuda_path = ""
+ )
# binary_dir first -- Windows DLL search step 1.
assert path_dirs[0] == str(
binary_dir
@@ -263,7 +271,9 @@ class TestWindowsGpuDetectionAfter5106Fix:
)
# Defence in depth: both fix paths contribute cudart.
sources = {Path(e).relative_to(tmp_path).parts[0] for e, _ in cudart_locations}
- assert "studio_install" in sources, f"#5322's cudart drop not reachable: {cudart_locations}"
+ assert (
+ "studio_install" in sources
+ ), f"#5322's cudart drop not reachable: {cudart_locations}"
assert (
"studio_venv" in sources
), f"#5324's pip nvidia dir not contributing cudart: {cudart_locations}"
@@ -280,7 +290,8 @@ class TestWindowsGpuDetectionAfter5106Fix:
for required in REAL_UPSTREAM_CUDART_BUNDLE["13.1"]:
reachable = any((Path(d) / required).exists() for d in path_dirs)
assert reachable, (
- f"{required} unreachable from PATH; #5106 not fixed.\n" f"PATH entries: {path_dirs}"
+ f"{required} unreachable from PATH; #5106 not fixed.\n"
+ f"PATH entries: {path_dirs}"
)
def test_no_pip_nvidia_wheels_still_works_via_install_dir(self, tmp_path):
@@ -292,7 +303,9 @@ class TestWindowsGpuDetectionAfter5106Fix:
_populate_studio_install(install, runtime = "13.1")
binary_dir = install / "build" / "bin" / "Release"
path_dirs = _build_path_dirs_like_start_llama_server(binary_dir, prefix)
- assert path_dirs == [str(binary_dir)], f"bare venv produced unexpected PATH: {path_dirs}"
+ assert path_dirs == [
+ str(binary_dir)
+ ], f"bare venv produced unexpected PATH: {path_dirs}"
for required in REAL_UPSTREAM_CUDART_BUNDLE["13.1"]:
assert (
binary_dir / required
@@ -316,7 +329,8 @@ class TestWindowsGpuDetectionAfter5106Fix:
(rel / fn).write_bytes(b"PE-stub")
path_dirs = _build_path_dirs_like_start_llama_server(rel, prefix)
cudart_reachable = any(
- (Path(d) / "cudart64_12.dll").exists() or (Path(d) / "cudart64_13.dll").exists()
+ (Path(d) / "cudart64_12.dll").exists()
+ or (Path(d) / "cudart64_13.dll").exists()
for d in path_dirs
)
assert cudart_reachable, (
@@ -324,7 +338,8 @@ class TestWindowsGpuDetectionAfter5106Fix:
f"on cudart-less install. PATH entries: {path_dirs}"
)
cublas_reachable = any(
- (Path(d) / "cublas64_12.dll").exists() or (Path(d) / "cublas64_13.dll").exists()
+ (Path(d) / "cublas64_12.dll").exists()
+ or (Path(d) / "cublas64_13.dll").exists()
for d in path_dirs
)
assert cublas_reachable, "cublas unreachable on cudart-less install"
@@ -343,7 +358,8 @@ class TestWindowsGpuDetectionAfter5106Fix:
# Pre-PR PATH: binary_dir only, no pip nvidia dirs, no toolkit.
pre_pr_path_dirs = [str(rel)]
cudart_reachable_pre = any(
- (Path(d) / "cudart64_12.dll").exists() or (Path(d) / "cudart64_13.dll").exists()
+ (Path(d) / "cudart64_12.dll").exists()
+ or (Path(d) / "cudart64_13.dll").exists()
for d in pre_pr_path_dirs
)
assert not cudart_reachable_pre, (
@@ -364,5 +380,7 @@ class TestWindowsSysPlatformMocked:
out = LlamaCppBackend._windows_pip_nvidia_dll_dirs(str(prefix))
assert out, f"resolver returned empty under sys.platform=win32: {out}"
# cu13 arch dir must be in the output.
- cu13_arch = prefix / "Lib" / "site-packages" / "nvidia" / "cu13" / "bin" / "x86_64"
+ cu13_arch = (
+ prefix / "Lib" / "site-packages" / "nvidia" / "cu13" / "bin" / "x86_64"
+ )
assert str(cu13_arch) in out
diff --git a/studio/backend/utils/api_errors.py b/studio/backend/utils/api_errors.py
index b1c55b61b9..0ad10a7ae1 100644
--- a/studio/backend/utils/api_errors.py
+++ b/studio/backend/utils/api_errors.py
@@ -142,7 +142,9 @@ def error_body_for_path(
"""
if is_anthropic_path(path):
return anthropic_error_body(message, status = status, err_type = err_type)
- return openai_error_body(message, status = status, err_type = err_type, code = code, param = param)
+ return openai_error_body(
+ message, status = status, err_type = err_type, code = code, param = param
+ )
def _summarize_validation_errors(errors) -> tuple:
@@ -175,7 +177,11 @@ def _summarize_validation_errors(errors) -> tuple:
param = part
break
- label = ".".join(str(p) for p in loc_parts) if loc_parts else ".".join(str(p) for p in loc)
+ label = (
+ ".".join(str(p) for p in loc_parts)
+ if loc_parts
+ else ".".join(str(p) for p in loc)
+ )
summary = f"{label}: {msg}" if label else str(msg)
return summary, param
@@ -214,7 +220,9 @@ def install_api_error_handlers(app) -> None:
if path.startswith("/v1/"):
detail = exc.detail
# Already a fully-formed envelope: pass through untouched.
- if isinstance(detail, dict) and ("error" in detail or detail.get("type") == "error"):
+ if isinstance(detail, dict) and (
+ "error" in detail or detail.get("type") == "error"
+ ):
return JSONResponse(
status_code = exc.status_code,
content = detail,
diff --git a/studio/backend/utils/cache_cleanup.py b/studio/backend/utils/cache_cleanup.py
index 210735973d..ede6047fcc 100644
--- a/studio/backend/utils/cache_cleanup.py
+++ b/studio/backend/utils/cache_cleanup.py
@@ -72,7 +72,8 @@ def clear_unsloth_compiled_cache(preserve_patterns: Optional[List[str]] = None)
if preserve_patterns:
logger.info(
- f"Cleaning unsloth compiled cache (preserving {preserve_patterns}): " f"{cache_dir}"
+ f"Cleaning unsloth compiled cache (preserving {preserve_patterns}): "
+ f"{cache_dir}"
)
for item in cache_dir.iterdir():
diff --git a/studio/backend/utils/datasets/data_collators.py b/studio/backend/utils/datasets/data_collators.py
index 9bfb60ba17..73d66a2ee4 100644
--- a/studio/backend/utils/datasets/data_collators.py
+++ b/studio/backend/utils/datasets/data_collators.py
@@ -23,13 +23,19 @@ class DataCollatorSpeechSeq2SeqWithPadding:
processor: Any
def __call__(self, features: List[dict]) -> dict:
- input_features = [{"input_features": feature["input_features"]} for feature in features]
- batch = self.processor.feature_extractor.pad(input_features, return_tensors = "pt")
+ input_features = [
+ {"input_features": feature["input_features"]} for feature in features
+ ]
+ batch = self.processor.feature_extractor.pad(
+ input_features, return_tensors = "pt"
+ )
label_features = [{"input_ids": feature["labels"]} for feature in features]
labels_batch = self.processor.tokenizer.pad(label_features, return_tensors = "pt")
- labels = labels_batch["input_ids"].masked_fill(labels_batch.attention_mask.ne(1), -100)
+ labels = labels_batch["input_ids"].masked_fill(
+ labels_batch.attention_mask.ne(1), -100
+ )
if (labels[:, 0] == self.processor.tokenizer.bos_token_id).all().cpu().item():
labels = labels[:, 1:]
@@ -136,7 +142,9 @@ class VLMDataCollator:
all_images.append(img)
texts = [
- self.processor.apply_chat_template(msgs, tokenize = False, add_generation_prompt = False)
+ self.processor.apply_chat_template(
+ msgs, tokenize = False, add_generation_prompt = False
+ )
for msgs in all_messages
]
diff --git a/studio/backend/utils/datasets/dataset_none_detect.py b/studio/backend/utils/datasets/dataset_none_detect.py
index a2fd8ef667..7fb39c6526 100644
--- a/studio/backend/utils/datasets/dataset_none_detect.py
+++ b/studio/backend/utils/datasets/dataset_none_detect.py
@@ -71,7 +71,9 @@ def _probe_conversation(dataset: Dataset, candidates = None):
# No usable dict turn in 100 rows. Record an all_corrupt fallback,
# plausible only with turn-shaped data (None cell or list of dict/None
# turns); a later plausible candidate upgrades a non-plausible one.
- if all_corrupt_fallback is None or not all_corrupt_fallback.get("has_plausible_turns"):
+ if all_corrupt_fallback is None or not all_corrupt_fallback.get(
+ "has_plausible_turns"
+ ):
has_plausible_turns = False
for i in range(min(len(dataset), 100)):
cell = dataset[i][col]
@@ -116,7 +118,9 @@ def _probe_conversation(dataset: Dataset, candidates = None):
_CONV_KEYS = {"role", "from", "content", "value"}
if not any(keys <= turn_keys for keys in _CHAT_KEY_SETS):
schema_less_plausible = bool(turn_keys & _CONV_KEYS)
- if all_corrupt_fallback is None or not all_corrupt_fallback.get("has_plausible_turns"):
+ if all_corrupt_fallback is None or not all_corrupt_fallback.get(
+ "has_plausible_turns"
+ ):
all_corrupt_fallback = {
"column": col,
"turn_keys": turn_keys,
@@ -159,11 +163,14 @@ def is_none_or_empty(value) -> bool:
non_text_blocks = [item for item in dict_blocks if item.get("type") != "text"]
if non_text_blocks:
return False
- text_values = [item.get("text") for item in dict_blocks if item.get("type") == "text"]
+ text_values = [
+ item.get("text") for item in dict_blocks if item.get("type") == "text"
+ ]
if text_values and all(
t is None
or (
- isinstance(t, str) and not t.strip().strip("\ufeff\u200b\u200c\u200d\u2060").strip()
+ isinstance(t, str)
+ and not t.strip().strip("\ufeff\u200b\u200c\u200d\u2060").strip()
)
for t in text_values
):
@@ -274,7 +281,9 @@ def find_none_chatml(dataset: Dataset, col: str = None) -> dict:
stats["rows_with_none_turns"] += 1
stats["total_none_turns"] += 1
stats["rows_all_none"] += 1
- stats["none_by_role"]["unknown"] = stats["none_by_role"].get("unknown", 0) + 1
+ stats["none_by_role"]["unknown"] = (
+ stats["none_by_role"].get("unknown", 0) + 1
+ )
stats["none_by_type"][vtype] = stats["none_by_type"].get(vtype, 0) + 1
stats["findings"].append(
{
@@ -293,7 +302,9 @@ def find_none_chatml(dataset: Dataset, col: str = None) -> dict:
stats["rows_with_none_turns"] += 1
stats["total_none_turns"] += 1
stats["rows_all_none"] += 1
- stats["none_by_role"]["unknown"] = stats["none_by_role"].get("unknown", 0) + 1
+ stats["none_by_role"]["unknown"] = (
+ stats["none_by_role"].get("unknown", 0) + 1
+ )
stats["none_by_type"]["empty_conversation"] = (
stats["none_by_type"].get("empty_conversation", 0) + 1
)
@@ -321,7 +332,9 @@ def find_none_chatml(dataset: Dataset, col: str = None) -> dict:
"raw_value": repr(turn),
}
)
- stats["none_by_role"]["unknown"] = stats["none_by_role"].get("unknown", 0) + 1
+ stats["none_by_role"]["unknown"] = (
+ stats["none_by_role"].get("unknown", 0) + 1
+ )
vtype = "None" if turn is None else "invalid_type"
stats["none_by_type"][vtype] = stats["none_by_type"].get(vtype, 0) + 1
continue
@@ -342,14 +355,20 @@ def find_none_chatml(dataset: Dataset, col: str = None) -> dict:
if "from" in turn and "value" in turn:
content = turn.get("value")
elif "role" in turn:
- content = turn.get("content") if "content" in turn else turn.get("value")
+ content = (
+ turn.get("content") if "content" in turn else turn.get("value")
+ )
elif "from" in turn:
content = turn.get("value")
else:
- content = turn.get("content") if "content" in turn else turn.get("value")
+ content = (
+ turn.get("content") if "content" in turn else turn.get("value")
+ )
# Assistant tool-call turns carry empty content + tool_calls and are
# valid; the exemption is assistant-only.
- if is_none_or_empty(content) and not (role == "assistant" and turn.get("tool_calls")):
+ if is_none_or_empty(content) and not (
+ role == "assistant" and turn.get("tool_calls")
+ ):
vtype = _classify_empty(content)
row_findings.append(
{
@@ -446,7 +465,9 @@ FORMAT_REGISTRY = [
},
{
"name": "sharegpt",
- "match": lambda ds, conv: (conv is not None and {"from", "value"} <= conv["turn_keys"]),
+ "match": lambda ds, conv: (
+ conv is not None and {"from", "value"} <= conv["turn_keys"]
+ ),
"scan": find_none_sharegpt,
},
{
@@ -731,7 +752,9 @@ def show_row(
# Mirror scanner: tool_calls exemption is assistant-only;
# other roles with empty content + tool_calls are still bad.
r = t.get("role") if t.get("role") is not None else t.get("from")
- if is_none_or_empty(c) and not (str(r) == "assistant" and t.get("tool_calls")):
+ if is_none_or_empty(c) and not (
+ str(r) == "assistant" and t.get("tool_calls")
+ ):
return True
return False
@@ -752,11 +775,19 @@ def show_row(
if "from" in turn and "value" in turn:
content = turn.get("value")
elif "role" in turn:
- content = turn.get("content") if "content" in turn else turn.get("value")
+ content = (
+ turn.get("content")
+ if "content" in turn
+ else turn.get("value")
+ )
elif "from" in turn:
content = turn.get("value")
else:
- content = turn.get("content") if "content" in turn else turn.get("value")
+ content = (
+ turn.get("content")
+ if "content" in turn
+ else turn.get("value")
+ )
if is_none_or_empty(content) and not (
role == "assistant" and turn.get("tool_calls")
):
@@ -798,8 +829,12 @@ examples:
python dataset_none_detect.py org/my-dataset --token hf_...
""",
)
- parser.add_argument("dataset", help = "HuggingFace dataset repo id (e.g. org/my-dataset)")
- parser.add_argument("--split", default = "train", help = "Dataset split to load (default: train)")
+ parser.add_argument(
+ "dataset", help = "HuggingFace dataset repo id (e.g. org/my-dataset)"
+ )
+ parser.add_argument(
+ "--split", default = "train", help = "Dataset split to load (default: train)"
+ )
parser.add_argument(
"--format",
default = "auto",
diff --git a/studio/backend/utils/datasets/dataset_utils.py b/studio/backend/utils/datasets/dataset_utils.py
index faa3deac70..cb943d7402 100644
--- a/studio/backend/utils/datasets/dataset_utils.py
+++ b/studio/backend/utils/datasets/dataset_utils.py
@@ -341,7 +341,9 @@ def _apply_template_mapping(
user_parts = []
for col in role_groups["user"]:
if col in examples:
- user_parts.append(_extract_column_value(examples[col][i], col, label_mapping))
+ user_parts.append(
+ _extract_column_value(examples[col][i], col, label_mapping)
+ )
if user_parts:
convo.append({"role": "user", "content": "\n".join(user_parts)})
@@ -349,7 +351,9 @@ def _apply_template_mapping(
asst_parts = []
for col in role_groups["assistant"]:
if col in examples:
- asst_parts.append(_extract_column_value(examples[col][i], col, label_mapping))
+ asst_parts.append(
+ _extract_column_value(examples[col][i], col, label_mapping)
+ )
if asst_parts:
convo.append({"role": "assistant", "content": "\n".join(asst_parts)})
@@ -399,7 +403,11 @@ def _apply_user_mapping_alpaca(
("output", outputs),
):
col = col_for[field]
- val = str(examples[col][i]) if col and col in examples and examples[col][i] else ""
+ val = (
+ str(examples[col][i])
+ if col and col in examples and examples[col][i]
+ else ""
+ )
dest.append(val)
return {"instruction": instructions, "input": inputs, "output": outputs}
@@ -477,7 +485,9 @@ def format_dataset(
else:
# auto / chatml / sharegpt / conversational all produce chatml
# conversations (sharegpt standardized to role/content internally)
- mapped_dataset = _apply_user_mapping(dataset, custom_format_mapping, batch_size)
+ mapped_dataset = _apply_user_mapping(
+ dataset, custom_format_mapping, batch_size
+ )
final_format = "chatml_conversations"
chat_column = "conversations"
@@ -574,7 +584,9 @@ def format_dataset(
elif detected["format"] == "chatml" and detected.get("chat_column"):
return {
"dataset": dataset,
- "detected_format": _chatml_detected_format_label(detected["chat_column"]),
+ "detected_format": _chatml_detected_format_label(
+ detected["chat_column"]
+ ),
"final_format": _chatml_final_format(detected["chat_column"]),
"chat_column": detected["chat_column"],
"is_standardized": True,
@@ -586,7 +598,9 @@ def format_dataset(
# Unknown - try standardization, pass as-is on failure
else:
- warnings.append(f"Unknown format detected. Keys found: {detected['sample_keys']}")
+ warnings.append(
+ f"Unknown format detected. Keys found: {detected['sample_keys']}"
+ )
# Try heuristic detection
if auto_detect_custom:
@@ -612,7 +626,9 @@ def format_dataset(
if role == target_role and col_name in examples:
content = examples[col_name][i]
if content and str(content).strip():
- convo.append({"role": role, "content": str(content)})
+ convo.append(
+ {"role": role, "content": str(content)}
+ )
conversations.append(convo)
return {"conversations": conversations, **preserved_columns}
@@ -661,7 +677,9 @@ def format_dataset(
"warnings": warnings,
}
except Exception as e:
- warnings.append(f"Could not standardize: {e}. Passing dataset as-is.")
+ warnings.append(
+ f"Could not standardize: {e}. Passing dataset as-is."
+ )
# Return as-is with warnings
return {
@@ -691,7 +709,9 @@ def format_dataset(
"warnings": [],
}
- elif detected["format"] in ["sharegpt", "chatml"] and detected.get("chat_column"):
+ elif detected["format"] in ["sharegpt", "chatml"] and detected.get(
+ "chat_column"
+ ):
try:
# First standardize if ShareGPT
if detected["format"] == "sharegpt":
@@ -808,7 +828,9 @@ def format_dataset(
elif detected["format"] == "chatml" and detected.get("chat_column"):
return {
"dataset": dataset,
- "detected_format": _chatml_detected_format_label(detected["chat_column"]),
+ "detected_format": _chatml_detected_format_label(
+ detected["chat_column"]
+ ),
"final_format": _chatml_final_format(detected["chat_column"]),
"chat_column": detected["chat_column"],
"is_standardized": True,
@@ -966,7 +988,9 @@ def format_and_template_dataset(
f"text='{user_vlm_text_column}') failed: {e} — "
f"falling back to auto-detection"
)
- logger.info(f"⚠️ User VLM mapping failed, falling back to auto-detection...")
+ logger.info(
+ f"⚠️ User VLM mapping failed, falling back to auto-detection..."
+ )
custom_format_mapping = None # so auto-detection runs below
else:
errors.append(
@@ -1020,7 +1044,9 @@ def format_and_template_dataset(
dataset_name = dataset_name,
progress_callback = progress_callback,
)
- warnings.append("Converted from ShareGPT+image format to standard VLM format")
+ warnings.append(
+ "Converted from ShareGPT+image format to standard VLM format"
+ )
except Exception as e:
errors.append(f"Failed to convert ShareGPT+image format: {e}")
import traceback
@@ -1088,9 +1114,13 @@ def format_and_template_dataset(
)
if vlm_instruction:
- warnings.append(f"Using user-provided instruction: '{vlm_instruction}'")
+ warnings.append(
+ f"Using user-provided instruction: '{vlm_instruction}'"
+ )
else:
- warnings.append("Auto-generated instruction based on dataset analysis")
+ warnings.append(
+ "Auto-generated instruction based on dataset analysis"
+ )
except Exception as e:
errors.append(f"Failed to convert to VLM format: {e}")
@@ -1195,7 +1225,9 @@ def format_and_template_dataset(
summary = get_dataset_info_summary(dataset_info)
# Combine results
- all_warnings = dataset_info.get("warnings", []) + template_result.get("warnings", [])
+ all_warnings = dataset_info.get("warnings", []) + template_result.get(
+ "warnings", []
+ )
all_errors = template_result.get("errors", [])
# If apply_chat_template rescued an "unknown" format, update final_format.
diff --git a/studio/backend/utils/datasets/format_conversion.py b/studio/backend/utils/datasets/format_conversion.py
index cb24bd96ba..096a33c18f 100644
--- a/studio/backend/utils/datasets/format_conversion.py
+++ b/studio/backend/utils/datasets/format_conversion.py
@@ -100,7 +100,9 @@ def standardize_chat_format(
role_key = keys[1]
content_key = keys[0]
else:
- raise ValueError(f"Could not infer role/content keys for chat column '{chat_column}'")
+ raise ValueError(
+ f"Could not infer role/content keys for chat column '{chat_column}'"
+ )
# Mapping for aliases
aliases_mapping = {}
@@ -131,7 +133,9 @@ def standardize_chat_format(
if original_role is None:
original_role = message.get("role") or message.get("from") or ""
if original_content is None:
- original_content = message.get("content") or message.get("value") or ""
+ original_content = (
+ message.get("content") or message.get("value") or ""
+ )
standard_role = aliases_mapping.get(original_role, original_role)
@@ -188,11 +192,15 @@ def convert_chatml_to_alpaca(
chatml_data = examples.get(chat_column) if chat_column else None
if chatml_data is None:
chatml_data = (
- examples.get("messages") or examples.get("conversations") or examples.get("texts")
+ examples.get("messages")
+ or examples.get("conversations")
+ or examples.get("texts")
)
if chatml_data is None:
- raise ValueError("No 'messages' or 'conversations' or 'texts' column found.")
+ raise ValueError(
+ "No 'messages' or 'conversations' or 'texts' column found."
+ )
instructions = []
outputs = []
@@ -352,12 +360,16 @@ def convert_to_vlm_format(
instruction_column = instruction_info.get("instruction_column")
uses_dynamic = instruction_info["uses_dynamic_instruction"]
- logger.info(f"📝 Auto-detected instruction type: {instruction_info['instruction_type']}")
+ logger.info(
+ f"📝 Auto-detected instruction type: {instruction_info['instruction_type']}"
+ )
logger.info(f"📝 Confidence: {instruction_info['confidence']:.2f}")
if not uses_dynamic:
logger.info(f"📝 Using instruction: '{instruction}'")
else:
- logger.info(f"📝 Using dynamic instructions from column: '{instruction_column}'")
+ logger.info(
+ f"📝 Using dynamic instructions from column: '{instruction_column}'"
+ )
else:
instruction_column = None
uses_dynamic = False
@@ -412,7 +424,9 @@ def convert_to_vlm_format(
total = len(dataset)
first_image = next(iter(dataset))[image_column]
- has_urls = isinstance(first_image, str) and first_image.startswith(("http://", "https://"))
+ has_urls = isinstance(first_image, str) and first_image.startswith(
+ ("http://", "https://")
+ )
# ── Bare-filename detection: build a basename→repo_path lookup so
# filename-only images resolve via hf_hub_download during conversion.
@@ -461,7 +475,9 @@ def convert_to_vlm_format(
num_workers = safe_thread_num_proc()
_notify(f"Probing {PROBE_SIZE} image URLs with {num_workers} workers...")
- logger.info(f"🔍 Probing {PROBE_SIZE}/{total} image URLs with {num_workers} workers...")
+ logger.info(
+ f"🔍 Probing {PROBE_SIZE}/{total} image URLs with {num_workers} workers..."
+ )
probe_samples = [dataset[i] for i in range(PROBE_SIZE)]
probe_ok = 0
@@ -469,7 +485,9 @@ def convert_to_vlm_format(
probe_start = time.time()
with ThreadPoolExecutor(max_workers = num_workers) as executor:
- futures = {executor.submit(_convert_single_sample, s): s for s in probe_samples}
+ futures = {
+ executor.submit(_convert_single_sample, s): s for s in probe_samples
+ }
for future in as_completed(futures):
try:
future.result()
@@ -561,7 +579,9 @@ def convert_to_vlm_format(
except Exception as e:
failed_count += 1
if failed_count == 1:
- logger.info(f"First VLM conversion failure: {type(e).__name__}: {e}")
+ logger.info(
+ f"First VLM conversion failure: {type(e).__name__}: {e}"
+ )
converted_list.extend(r for r in batch_results if r is not None)
@@ -586,7 +606,9 @@ def convert_to_vlm_format(
failed_count += 1
if failed_count == 1:
# Log the first failure to aid debugging
- logger.info(f"First VLM conversion failure: {type(e).__name__}: {e}")
+ logger.info(
+ f"First VLM conversion failure: {type(e).__name__}: {e}"
+ )
pbar.set_postfix(ok = len(converted_list), failed = failed_count, refresh = False)
pbar.close()
@@ -752,7 +774,9 @@ def convert_sharegpt_with_images_to_vlm_format(
return Image.open(local_path).convert("RGB")
else:
return Image.open(image_data).convert("RGB")
- if isinstance(image_data, dict) and ("bytes" in image_data or "path" in image_data):
+ if isinstance(image_data, dict) and (
+ "bytes" in image_data or "path" in image_data
+ ):
if image_data.get("bytes"):
from io import BytesIO
return Image.open(BytesIO(image_data["bytes"])).convert("RGB")
@@ -808,7 +832,9 @@ def convert_sharegpt_with_images_to_vlm_format(
pbar.close()
if failed_count > 0:
- logger.info(f"⚠️ Skipped {failed_count}/{total} ({failed_count*100//total}%) samples")
+ logger.info(
+ f"⚠️ Skipped {failed_count}/{total} ({failed_count*100//total}%) samples"
+ )
if len(converted_list) == 0:
raise ValueError(
@@ -834,7 +860,9 @@ def convert_llava_to_vlm_format(dataset):
"""
from PIL import Image
- logger.info(f"🔄 Converting {len(dataset)} samples from Llava format to standard VLM format...")
+ logger.info(
+ f"🔄 Converting {len(dataset)} samples from Llava format to standard VLM format..."
+ )
def _convert_single_sample(sample):
"""Convert one llava sample to standard VLM format."""
diff --git a/studio/backend/utils/datasets/format_detection.py b/studio/backend/utils/datasets/format_detection.py
index f5ea5ca138..cdc816235f 100644
--- a/studio/backend/utils/datasets/format_detection.py
+++ b/studio/backend/utils/datasets/format_detection.py
@@ -8,7 +8,10 @@ import re
def _keyword_in_column(keyword: str, col_name: str) -> bool:
"""Word-boundary keyword match to avoid false positives like 'pic' in 'topic'."""
- return re.search(r"\b" + re.escape(keyword) + r"\b", col_name, re.IGNORECASE) is not None
+ return (
+ re.search(r"\b" + re.escape(keyword) + r"\b", col_name, re.IGNORECASE)
+ is not None
+ )
CONVERSATION_COLUMNS = ("messages", "conversations", "texts")
@@ -89,7 +92,9 @@ def _inspect_conversation_column(rows: list[dict], column_name: str) -> dict | N
return None
-def _detect_conversation_column(rows: list[dict], column_names: list[str]) -> dict | None:
+def _detect_conversation_column(
+ rows: list[dict], column_names: list[str]
+) -> dict | None:
column_name_set = set(column_names)
unknown_exact = None
for column_name in CONVERSATION_COLUMNS:
@@ -281,7 +286,10 @@ def detect_custom_format_heuristic(dataset):
return True
for pattern in metadata_prefix_patterns:
- if col_lower.startswith(pattern.split("_")[0] + "_") and col_lower != pattern:
+ if (
+ col_lower.startswith(pattern.split("_")[0] + "_")
+ and col_lower != pattern
+ ):
if "_" in col_lower:
prefix = col_lower.split("_")[0]
if prefix in ["generation", "pass", "inference"]:
@@ -324,7 +332,9 @@ def detect_custom_format_heuristic(dataset):
# Penalize ambiguous "task" so other user columns win.
if role_type == "user":
col_lower = col_name.lower()
- if "task" in col_lower and not any(kw in col_lower for kw in user_words_high_priority):
+ if "task" in col_lower and not any(
+ kw in col_lower for kw in user_words_high_priority
+ ):
score -= 15
priority_bonus = get_priority_score(col_name)
@@ -354,13 +364,17 @@ def detect_custom_format_heuristic(dataset):
content_columns = [col for col in all_columns if not is_metadata(col)]
- assistant_potential = [col for col in content_columns if has_keyword(col, assistant_words)]
+ assistant_potential = [
+ col for col in content_columns if has_keyword(col, assistant_words)
+ ]
user_potential = [col for col in content_columns if has_keyword(col, user_words)]
# STEP 1: best ASSISTANT column
assistant_candidates = []
for col in assistant_potential:
- score = score_column(col, assistant_words, "assistant", len(assistant_potential))
+ score = score_column(
+ col, assistant_words, "assistant", len(assistant_potential)
+ )
if score > 0:
assistant_candidates.append((col, score))
@@ -664,7 +678,9 @@ def detect_vlm_dataset_structure(dataset):
if isinstance(content[0], dict) and "type" in content[0]:
# Llava format?
has_index = any(
- "index" in item for item in content if isinstance(item, dict)
+ "index" in item
+ for item in content
+ if isinstance(item, dict)
)
has_images_column = "images" in column_names
@@ -679,7 +695,9 @@ def detect_vlm_dataset_structure(dataset):
# Standard VLM format
has_image = any(
- "image" in item for item in content if isinstance(item, dict)
+ "image" in item
+ for item in content
+ if isinstance(item, dict)
)
if has_image:
return {
@@ -782,7 +800,9 @@ def detect_vlm_dataset_structure(dataset):
if any(col_lower.endswith(suffix) for suffix in metadata_patterns["suffixes"]):
return True
- if any(col_lower.startswith(prefix) for prefix in metadata_patterns["prefixes"]):
+ if any(
+ col_lower.startswith(prefix) for prefix in metadata_patterns["prefixes"]
+ ):
return True
return False
@@ -794,7 +814,9 @@ def detect_vlm_dataset_structure(dataset):
return 100
# HF Image feature dict.
- if isinstance(sample_value, dict) and ("bytes" in sample_value or "path" in sample_value):
+ if isinstance(sample_value, dict) and (
+ "bytes" in sample_value or "path" in sample_value
+ ):
return 75
if isinstance(sample_value, str):
@@ -816,7 +838,9 @@ def detect_vlm_dataset_structure(dataset):
# Local file — check it exists.
if not sample_value.startswith(("http://", "https://")):
- return os.path.exists(sample_value) # bare filenames return False, that's OK
+ return os.path.exists(
+ sample_value
+ ) # bare filenames return False, that's OK
# URL — quick HEAD with short timeout.
try:
diff --git a/studio/backend/utils/datasets/llm_assist.py b/studio/backend/utils/datasets/llm_assist.py
index f7b35e2869..85a67724b2 100644
--- a/studio/backend/utils/datasets/llm_assist.py
+++ b/studio/backend/utils/datasets/llm_assist.py
@@ -53,7 +53,9 @@ def precache_helper_gguf():
return
repo = os.environ.get("UNSLOTH_HELPER_MODEL_REPO", DEFAULT_HELPER_MODEL_REPO)
- variant = os.environ.get("UNSLOTH_HELPER_MODEL_VARIANT", DEFAULT_HELPER_MODEL_VARIANT)
+ variant = os.environ.get(
+ "UNSLOTH_HELPER_MODEL_VARIANT", DEFAULT_HELPER_MODEL_VARIANT
+ )
try:
from huggingface_hub import HfApi, hf_hub_download
@@ -68,7 +70,9 @@ def precache_helper_gguf():
# GGUF files matching the variant (may be split into shards).
variant_lower = variant.lower().replace("-", "_")
- matching = sorted(f for f in gguf_files if variant_lower in f.lower().replace("-", "_"))
+ matching = sorted(
+ f for f in gguf_files if variant_lower in f.lower().replace("-", "_")
+ )
if matching:
logger.info(
@@ -95,7 +99,9 @@ def _run_with_helper(prompt: str, max_tokens: int = 256) -> Optional[str]:
return None
repo = os.environ.get("UNSLOTH_HELPER_MODEL_REPO", DEFAULT_HELPER_MODEL_REPO)
- variant = os.environ.get("UNSLOTH_HELPER_MODEL_VARIANT", DEFAULT_HELPER_MODEL_VARIANT)
+ variant = os.environ.get(
+ "UNSLOTH_HELPER_MODEL_VARIANT", DEFAULT_HELPER_MODEL_VARIANT
+ )
backend = None
try:
@@ -117,7 +123,9 @@ def _run_with_helper(prompt: str, max_tokens: int = 256) -> Optional[str]:
return None
messages = [{"role": "user", "content": prompt}]
- logger.info("Helper model request: enable_thinking=False (per-request override)")
+ logger.info(
+ "Helper model request: enable_thinking=False (per-request override)"
+ )
cumulative = ""
for chunk in backend.generate_chat_completion(
messages = messages,
@@ -200,7 +208,9 @@ def llm_generate_vlm_instruction(
}
-def llm_classify_columns(column_names: list[str], samples: list[dict]) -> Optional[dict[str, str]]:
+def llm_classify_columns(
+ column_names: list[str], samples: list[dict]
+) -> Optional[dict[str, str]]:
"""Ask a helper LLM to classify columns into roles (when heuristic detection fails).
Returns {column_name: role} for roles user|assistant|system|metadata, or None.
@@ -258,7 +268,11 @@ def llm_classify_columns(column_names: list[str], samples: list[dict]) -> Option
valid_roles = {"user", "assistant", "system", "metadata"}
cleaned = {}
for col, role in mapping.items():
- if col in column_names and isinstance(role, str) and role.lower() in valid_roles:
+ if (
+ col in column_names
+ and isinstance(role, str)
+ and role.lower() in valid_roles
+ ):
cleaned[col] = role.lower()
if not cleaned:
@@ -409,7 +423,9 @@ def fetch_hf_dataset_card(
if val is not None:
metadata[key] = val
- logger.info(f"Fetched dataset card: {len(readme)} chars, {len(metadata)} metadata fields")
+ logger.info(
+ f"Fetched dataset card: {len(readme)} chars, {len(metadata)} metadata fields"
+ )
return readme, metadata
except Exception as e:
@@ -435,7 +451,9 @@ def _run_multi_pass_advisor(
return None
repo = os.environ.get("UNSLOTH_HELPER_MODEL_REPO", DEFAULT_HELPER_MODEL_REPO)
- variant = os.environ.get("UNSLOTH_HELPER_MODEL_VARIANT", DEFAULT_HELPER_MODEL_VARIANT)
+ variant = os.environ.get(
+ "UNSLOTH_HELPER_MODEL_VARIANT", DEFAULT_HELPER_MODEL_VARIANT
+ )
backend = None
try:
@@ -465,7 +483,9 @@ def _run_multi_pass_advisor(
samples_text += f"Row {i}:\n" + "\n".join(parts) + "\n"
metadata_str = (
- json.dumps(dataset_metadata, indent = 2, default = str)[:500] if dataset_metadata else "N/A"
+ json.dumps(dataset_metadata, indent = 2, default = str)[:500]
+ if dataset_metadata
+ else "N/A"
)
card_excerpt = (dataset_card or "")[:1200] or "N/A"
@@ -667,7 +687,9 @@ def _run_multi_pass_advisor(
# Must have at least one user AND one assistant
roles_present = set(column_roles.values())
if "user" not in roles_present or "assistant" not in roles_present:
- logger.warning(f"Pass 2 sanity fail: missing user or assistant role: {column_roles}")
+ logger.warning(
+ f"Pass 2 sanity fail: missing user or assistant role: {column_roles}"
+ )
return None # falls back to simple classification
# ── Pass 3: System prompt (non-conversational datasets only) ──
diff --git a/studio/backend/utils/datasets/raw_text.py b/studio/backend/utils/datasets/raw_text.py
index 03315fb287..82e5683eb5 100644
--- a/studio/backend/utils/datasets/raw_text.py
+++ b/studio/backend/utils/datasets/raw_text.py
@@ -100,7 +100,8 @@ def prepare_raw_text_dataset(
notices.append(
RawTextNotice(
message = (
- f"{mode_title}: renaming column '{renamed_col}' -> 'text' " f"for {split_scope}"
+ f"{mode_title}: renaming column '{renamed_col}' -> 'text' "
+ f"for {split_scope}"
),
level = "info",
)
diff --git a/studio/backend/utils/datasets/vlm_processing.py b/studio/backend/utils/datasets/vlm_processing.py
index f018913fa8..5c336ee2a3 100644
--- a/studio/backend/utils/datasets/vlm_processing.py
+++ b/studio/backend/utils/datasets/vlm_processing.py
@@ -65,7 +65,9 @@ def generate_smart_vlm_instruction(
# OCR / Transcription
"ocr": {
"keywords": ["ocr", "transcribe", "transcript"],
- "content_hints": [r"[A-Za-z\u0600-\u06FF]{10,}"], # Long Latin/Arabic passages
+ "content_hints": [
+ r"[A-Za-z\u0600-\u06FF]{10,}"
+ ], # Long Latin/Arabic passages
"instruction": "Transcribe all the text shown in this image.",
"confidence": 0.9,
},
diff --git a/studio/backend/utils/downsample.py b/studio/backend/utils/downsample.py
index 2d340ca248..bccf6a23b7 100644
--- a/studio/backend/utils/downsample.py
+++ b/studio/backend/utils/downsample.py
@@ -12,5 +12,7 @@ def downsample(values: list[float], target_count: int) -> list[float]:
return []
if target_count == 1:
return [values[-1]]
- indices = [round(i * (len(values) - 1) / (target_count - 1)) for i in range(target_count)]
+ indices = [
+ round(i * (len(values) - 1) / (target_count - 1)) for i in range(target_count)
+ ]
return [values[i] for i in indices]
diff --git a/studio/backend/utils/hardware/amd.py b/studio/backend/utils/hardware/amd.py
index 27dfe187cc..1764f16c4a 100644
--- a/studio/backend/utils/hardware/amd.py
+++ b/studio/backend/utils/hardware/amd.py
@@ -215,7 +215,9 @@ def _extract_gpu_metrics(gpu_data: dict) -> dict[str, Any]:
# Output structure varies by version; try common paths
usage = gpu_data.get("usage", gpu_data.get("gpu_activity", {}))
if isinstance(usage, dict):
- gpu_util = _parse_numeric(usage.get("gfx_activity", usage.get("gpu_use_percent")))
+ gpu_util = _parse_numeric(
+ usage.get("gfx_activity", usage.get("gpu_use_percent"))
+ )
else:
gpu_util = _parse_numeric(usage)
@@ -240,7 +242,9 @@ def _extract_gpu_metrics(gpu_data: dict) -> dict[str, Any]:
power_data.get("average_socket_power", power_data.get("socket_power")),
)
)
- power_limit = _parse_numeric(power_data.get("power_cap", power_data.get("max_power_limit")))
+ power_limit = _parse_numeric(
+ power_data.get("power_cap", power_data.get("max_power_limit"))
+ )
else:
power_draw = None
power_limit = None
@@ -254,10 +258,14 @@ def _extract_gpu_metrics(gpu_data: dict) -> dict[str, Any]:
)
if isinstance(vram_data, dict):
vram_used_mb = _parse_memory_mb(
- vram_data.get("used_vram", vram_data.get("vram_used", vram_data.get("used")))
+ vram_data.get(
+ "used_vram", vram_data.get("vram_used", vram_data.get("used"))
+ )
)
vram_total_mb = _parse_memory_mb(
- vram_data.get("total_vram", vram_data.get("vram_total", vram_data.get("total")))
+ vram_data.get(
+ "total_vram", vram_data.get("vram_total", vram_data.get("total"))
+ )
)
else:
vram_used_mb = None
@@ -265,7 +273,9 @@ def _extract_gpu_metrics(gpu_data: dict) -> dict[str, Any]:
# Build the standardized dict (same shape as nvidia._build_gpu_metrics)
vram_used_gb = round(vram_used_mb / 1024, 2) if vram_used_mb is not None else None
- vram_total_gb = round(vram_total_mb / 1024, 2) if vram_total_mb is not None else None
+ vram_total_gb = (
+ round(vram_total_mb / 1024, 2) if vram_total_mb is not None else None
+ )
vram_util = (
round((vram_used_mb / vram_total_mb) * 100, 1)
if vram_used_mb is not None and vram_total_mb is not None and vram_total_mb > 0
@@ -375,7 +385,8 @@ def get_primary_gpu_utilization() -> dict[str, Any]:
def get_visible_gpu_utilization(
- parent_visible_ids: Optional[list[int]], parent_cuda_visible_devices: Optional[str] = None
+ parent_visible_ids: Optional[list[int]],
+ parent_cuda_visible_devices: Optional[str] = None,
) -> dict[str, Any]:
"""Return utilization metrics for visible AMD GPUs."""
if parent_visible_ids is None:
@@ -415,7 +426,9 @@ def get_visible_gpu_utilization(
continue
# Use the AMD-reported GPU ID, else the enumeration index. _parse_numeric
# handles bare ints/floats/strings and the {"value", "unit"} dict shape.
- raw_id = gpu_data.get("gpu", gpu_data.get("gpu_id", gpu_data.get("id", fallback_idx)))
+ raw_id = gpu_data.get(
+ "gpu", gpu_data.get("gpu_id", gpu_data.get("id", fallback_idx))
+ )
parsed_id = _parse_numeric(raw_id)
if parsed_id is None:
logger.warning(
diff --git a/studio/backend/utils/hardware/apple.py b/studio/backend/utils/hardware/apple.py
index 62dbd10b8d..3252dd3fa6 100644
--- a/studio/backend/utils/hardware/apple.py
+++ b/studio/backend/utils/hardware/apple.py
@@ -154,7 +154,11 @@ def _load_iokit() -> ctypes.CDLL:
def _load_cf() -> ctypes.CDLL:
cf = ctypes.CDLL(_CF_PATH)
cf.CFStringCreateWithCString.restype = ctypes.c_void_p
- cf.CFStringCreateWithCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint32]
+ cf.CFStringCreateWithCString.argtypes = [
+ ctypes.c_void_p,
+ ctypes.c_char_p,
+ ctypes.c_uint32,
+ ]
cf.CFStringGetCString.restype = ctypes.c_bool
cf.CFStringGetCString.argtypes = [
ctypes.c_void_p,
@@ -191,9 +195,17 @@ def _load_ioreport() -> ctypes.CDLL:
ctypes.c_void_p,
]
ior.IOReportCreateSamples.restype = ctypes.c_void_p
- ior.IOReportCreateSamples.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
+ ior.IOReportCreateSamples.argtypes = [
+ ctypes.c_void_p,
+ ctypes.c_void_p,
+ ctypes.c_void_p,
+ ]
ior.IOReportCreateSamplesDelta.restype = ctypes.c_void_p
- ior.IOReportCreateSamplesDelta.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
+ ior.IOReportCreateSamplesDelta.argtypes = [
+ ctypes.c_void_p,
+ ctypes.c_void_p,
+ ctypes.c_void_p,
+ ]
ior.IOReportChannelGetChannelName.restype = ctypes.c_void_p
ior.IOReportChannelGetChannelName.argtypes = [ctypes.c_void_p]
ior.IOReportChannelGetUnitLabel.restype = ctypes.c_void_p
@@ -204,7 +216,9 @@ def _load_ioreport() -> ctypes.CDLL:
def _cfstr(cf: ctypes.CDLL, text: str) -> int:
- return cf.CFStringCreateWithCString(None, text.encode("utf-8"), _CF_STRING_ENCODING_UTF8)
+ return cf.CFStringCreateWithCString(
+ None, text.encode("utf-8"), _CF_STRING_ENCODING_UTF8
+ )
def _from_cfstr(cf: ctypes.CDLL, ref: Optional[int]) -> str:
@@ -235,7 +249,12 @@ class _SMCConnection:
def _open(self) -> int:
iterator = ctypes.c_uint32(0)
matching = self._iokit.IOServiceMatching(b"AppleSMC")
- if self._iokit.IOServiceGetMatchingServices(0, matching, ctypes.byref(iterator)) != 0:
+ if (
+ self._iokit.IOServiceGetMatchingServices(
+ 0, matching, ctypes.byref(iterator)
+ )
+ != 0
+ ):
raise OSError("AppleSMC service not found")
try:
conn = self._open_keys_endpoint(iterator.value)
@@ -290,7 +309,9 @@ class _SMCConnection:
try:
key_id = _fourcc(key)
info = self._read_key_info(key_id)
- oval = self._call(_SMCKeyData(key = key_id, data8 = _SMC_CMD_READ_BYTES, key_info = info))
+ oval = self._call(
+ _SMCKeyData(key = key_id, data8 = _SMC_CMD_READ_BYTES, key_info = info)
+ )
return bytes(oval.bytes[: info.data_size])
except OSError:
return None
@@ -386,7 +407,9 @@ class _IOReportEnergy:
watts = _watts(energy, unit, elapsed_s)
if watts is not None:
total = (total or 0.0) + watts
- if total is None or total < 0: # negative = counter reset; show -- not a bogus draw
+ if (
+ total is None or total < 0
+ ): # negative = counter reset; show -- not a bogus draw
return None
return round(total, 1)
diff --git a/studio/backend/utils/hardware/hardware.py b/studio/backend/utils/hardware/hardware.py
index 86dfa8a93f..a391a90edc 100644
--- a/studio/backend/utils/hardware/hardware.py
+++ b/studio/backend/utils/hardware/hardware.py
@@ -51,7 +51,9 @@ class DeviceType(str, Enum):
DEVICE: Optional[DeviceType] = None
CHAT_ONLY: bool = True # No CUDA GPU -> GGUF chat only (Mac, CPU-only, etc.)
-IS_ROCM: bool = False # True when running on AMD ROCm (HIP) -- routes GPU monitoring to amd.py
+IS_ROCM: bool = (
+ False # True when running on AMD ROCm (HIP) -- routes GPU monitoring to amd.py
+)
def _backend_label(device: DeviceType) -> str:
@@ -622,7 +624,9 @@ def get_gpu_utilization() -> Dict[str, Any]:
result["backend"] = _backend_label(device)
if IS_ROCM:
# Fix unified-memory VRAM on AMD iGPUs (Strix Halo etc.).
- _reconcile_primary_rocm_unified_memory(result, _get_parent_visible_gpu_spec())
+ _reconcile_primary_rocm_unified_memory(
+ result, _get_parent_visible_gpu_spec()
+ )
return result
# SMI unavailable. On Windows, use Performance Counters (Task Manager
# source) for system-wide VRAM, covering cross-process usage torch can't see.
@@ -681,7 +685,9 @@ def get_gpu_utilization() -> Dict[str, Any]:
"temperature_c": None,
"vram_used_gb": _used,
"vram_total_gb": _total,
- "vram_utilization_pct": round((_used / _total) * 100, 1) if _total > 0 else None,
+ "vram_utilization_pct": round((_used / _total) * 100, 1)
+ if _total > 0
+ else None,
"power_draw_w": None,
"power_limit_w": None,
"power_utilization_pct": None,
@@ -765,7 +771,9 @@ def _apply_unified_memory_correction(
device_metrics["vram_total_gb"] = torch_total_gb
device_metrics["vram_used_gb"] = torch_used_gb
device_metrics["vram_utilization_pct"] = (
- round((torch_used_gb / torch_total_gb) * 100, 1) if torch_total_gb > 0 else None
+ round((torch_used_gb / torch_total_gb) * 100, 1)
+ if torch_total_gb > 0
+ else None
)
logger.debug(
"ROCm unified memory: replaced amd-smi VRAM (%.2f GB) with "
@@ -776,7 +784,9 @@ def _apply_unified_memory_correction(
)
-def _reconcile_rocm_unified_memory(utilization: Dict[str, Any], device_indices: list[int]) -> None:
+def _reconcile_rocm_unified_memory(
+ utilization: Dict[str, Any], device_indices: list[int]
+) -> None:
"""Fix amd-smi VRAM for ROCm unified-memory GPUs (e.g. Strix Halo).
amd-smi reports only the dedicated slice; torch sees the full GTT pool. When
@@ -928,7 +938,9 @@ def _get_parent_visible_gpu_spec() -> Dict[str, Any]:
# stale HIP_VISIBLE_DEVICES on NVIDIA can't override CUDA_VISIBLE_DEVICES.
_is_rocm_spec = IS_ROCM or (
"CUDA_VISIBLE_DEVICES" not in os.environ
- and ("HIP_VISIBLE_DEVICES" in os.environ or "ROCR_VISIBLE_DEVICES" in os.environ)
+ and (
+ "HIP_VISIBLE_DEVICES" in os.environ or "ROCR_VISIBLE_DEVICES" in os.environ
+ )
)
if _is_rocm_spec:
hip_vis = os.environ.get("HIP_VISIBLE_DEVICES")
@@ -1018,7 +1030,9 @@ def resolve_requested_gpu_ids(gpu_ids: Optional[list[int]]) -> list[int]:
max_parent_id = max(parent_visible_ids)
if physical_gpu_count > max_parent_id:
# Count is plausibly physical, so enforce it.
- out_of_range = [gpu_id for gpu_id in requested_ids if gpu_id >= physical_gpu_count]
+ out_of_range = [
+ gpu_id for gpu_id in requested_ids if gpu_id >= physical_gpu_count
+ ]
if out_of_range:
raise ValueError(
f"Invalid gpu_ids {requested_ids}: IDs must be physical GPU IDs "
@@ -1026,7 +1040,9 @@ def resolve_requested_gpu_ids(gpu_ids: Optional[list[int]]) -> list[int]:
f"Rejected IDs: {out_of_range}. Parent-visible GPUs: {parent_visible_ids}"
)
- disallowed_ids = [gpu_id for gpu_id in requested_ids if gpu_id not in parent_visible_ids]
+ disallowed_ids = [
+ gpu_id for gpu_id in requested_ids if gpu_id not in parent_visible_ids
+ ]
if disallowed_ids:
raise ValueError(
f"Invalid gpu_ids {requested_ids}: requested GPUs {disallowed_ids} are "
@@ -1047,7 +1063,9 @@ def _resolve_model_identifier_for_gpu_estimate(
return config.base_model
return config.identifier if config else model_name
except Exception as e:
- logger.debug("Could not resolve base model for GPU estimate '%s': %s", model_name, e)
+ logger.debug(
+ "Could not resolve base model for GPU estimate '%s': %s", model_name, e
+ )
return model_name
@@ -1192,15 +1210,17 @@ def _estimate_fp16_model_size_bytes_from_vllm_utils(config) -> Optional[int]:
synthetic_total_bytes,
synthetic_total_bytes,
)
- _, _, _, memory_left_for_kv_cache_gb = _vllm_utils.approximate_vllm_memory_usage(
- config,
- load_in_4bit = False,
- load_in_8bit = False,
- max_seq_length = 1,
- gpu_memory_utilization = 1.0,
- enable_lora = False,
- account_for_gradients = False,
- cuda_graph_overhead = False,
+ _, _, _, memory_left_for_kv_cache_gb = (
+ _vllm_utils.approximate_vllm_memory_usage(
+ config,
+ load_in_4bit = False,
+ load_in_8bit = False,
+ max_seq_length = 1,
+ gpu_memory_utilization = 1.0,
+ enable_lora = False,
+ account_for_gradients = False,
+ cuda_graph_overhead = False,
+ )
)
finally:
_vllm_utils.get_mem_info = original_get_mem_info
@@ -1222,11 +1242,15 @@ def _estimate_fp16_model_size_bytes_from_vllm_utils(config) -> Optional[int]:
def estimate_fp16_model_size_bytes(
model_name: str, hf_token: Optional[str] = None
) -> tuple[Optional[int], str]:
- estimate_model = _resolve_model_identifier_for_gpu_estimate(model_name, hf_token = hf_token)
+ estimate_model = _resolve_model_identifier_for_gpu_estimate(
+ model_name, hf_token = hf_token
+ )
total_params = None
if "/" in estimate_model and not Path(estimate_model).exists():
- total_params = _get_hf_safetensors_total_params(estimate_model, hf_token = hf_token)
+ total_params = _get_hf_safetensors_total_params(
+ estimate_model, hf_token = hf_token
+ )
if total_params:
return int(total_params * 2), "safetensors"
@@ -1281,7 +1305,9 @@ def estimate_required_model_memory_gb(
DEFAULT_TARGET_MODULES,
)
- model_size_bytes, source = estimate_fp16_model_size_bytes(model_name, hf_token = hf_token)
+ model_size_bytes, source = estimate_fp16_model_size_bytes(
+ model_name, hf_token = hf_token
+ )
metadata: Dict[str, Any] = {
"mode": "inference" if training_type is None else "training",
"model_size_source": source,
@@ -1304,7 +1330,9 @@ def estimate_required_model_memory_gb(
return required_gb, metadata
training_method = (
- "full" if training_type == "Full Finetuning" else ("qlora" if load_in_4bit else "lora")
+ "full"
+ if training_type == "Full Finetuning"
+ else ("qlora" if load_in_4bit else "lora")
)
vram_config = TrainingVramConfig(
training_method = training_method,
@@ -1317,12 +1345,14 @@ def estimate_required_model_memory_gb(
load_in_4bit = load_in_4bit,
)
- estimate_model = _resolve_model_identifier_for_gpu_estimate(model_name, hf_token = hf_token)
+ estimate_model = _resolve_model_identifier_for_gpu_estimate(
+ model_name, hf_token = hf_token
+ )
config = _load_config_for_gpu_estimate(estimate_model, hf_token = hf_token)
if config is not None:
try:
- vram_config.attention_implementation = _determine_attention_impl_for_gpu_estimate(
- config
+ vram_config.attention_implementation = (
+ _determine_attention_impl_for_gpu_estimate(config)
)
except Exception as e:
# Debug-level: fires every estimate on Windows ROCm (stub lacks Store);
@@ -1508,7 +1538,9 @@ def auto_select_gpu_ids(
return selected, metadata
# Use only GPUs with verified VRAM data.
- fallback_all = [c["index"] for c in gpu_candidates] if gpu_candidates else parent_ids
+ fallback_all = (
+ [c["index"] for c in gpu_candidates] if gpu_candidates else parent_ids
+ )
metadata["selection_mode"] = "fallback_all"
if ranked:
fallback_usable = ranked[0]["free_gb"] + sum(
@@ -1857,7 +1889,10 @@ def get_device_map(gpu_ids: Optional[list[int]] = None) -> str:
# UUID/MIG masks can't be split into numeric IDs; >1 visible GPU
# means multi-GPU sharding is intended.
parent_visible_spec = _get_parent_visible_gpu_spec()
- if parent_visible_spec["numeric_ids"] is None and get_visible_gpu_count() > 1:
+ if (
+ parent_visible_spec["numeric_ids"] is None
+ and get_visible_gpu_count() > 1
+ ):
multi_gpu = True
if multi_gpu:
@@ -1886,7 +1921,9 @@ def raise_if_offloaded(
offloaded = get_offloaded_device_map_entries(model)
if not offloaded:
return
- example = ", ".join(f"{name}={placement}" for name, placement in list(offloaded.items())[:5])
+ example = ", ".join(
+ f"{name}={placement}" for name, placement in list(offloaded.items())[:5]
+ )
raise ValueError(
f"{context} does not support models loaded with CPU or disk offload. "
f"device_map='{device_map}' produced offloaded modules: {example}"
diff --git a/studio/backend/utils/hardware/nvidia.py b/studio/backend/utils/hardware/nvidia.py
index f98ca4343e..eaabd9d55b 100644
--- a/studio/backend/utils/hardware/nvidia.py
+++ b/studio/backend/utils/hardware/nvidia.py
@@ -29,8 +29,12 @@ def _build_gpu_metrics(
) -> dict[str, Any]:
return {
**extra,
- "vram_used_gb": round(vram_used_mb / 1024, 2) if vram_used_mb is not None else None,
- "vram_total_gb": round(vram_total_mb / 1024, 2) if vram_total_mb is not None else None,
+ "vram_used_gb": round(vram_used_mb / 1024, 2)
+ if vram_used_mb is not None
+ else None,
+ "vram_total_gb": round(vram_total_mb / 1024, 2)
+ if vram_total_mb is not None
+ else None,
"vram_utilization_pct": round((vram_used_mb / vram_total_mb) * 100, 1)
if vram_used_mb is not None and vram_total_mb and vram_total_mb > 0
else None,
@@ -42,7 +46,9 @@ def _build_gpu_metrics(
}
-def _visible_ordinal_map(parent_visible_ids: Optional[list[int]]) -> Optional[dict[int, int]]:
+def _visible_ordinal_map(
+ parent_visible_ids: Optional[list[int]],
+) -> Optional[dict[int, int]]:
if parent_visible_ids is None:
return None
return {gpu_id: ordinal for ordinal, gpu_id in enumerate(parent_visible_ids)}
@@ -108,7 +114,8 @@ def get_primary_gpu_utilization() -> dict[str, Any]:
def get_visible_gpu_utilization(
- parent_visible_ids: Optional[list[int]], parent_cuda_visible_devices: Optional[str] = None
+ parent_visible_ids: Optional[list[int]],
+ parent_cuda_visible_devices: Optional[str] = None,
) -> dict[str, Any]:
# parent_visible_ids None (UUID/MIG mask): can't map nvidia-smi rows to
# visible devices, so return empty rather than exposing all physical GPUs.
@@ -176,7 +183,9 @@ def get_visible_gpu_utilization(
index = idx,
index_kind = "physical",
visible_ordinal = (
- visible_ordinals[idx] if visible_ordinals is not None else len(devices)
+ visible_ordinals[idx]
+ if visible_ordinals is not None
+ else len(devices)
),
gpu_utilization_pct = _parse_smi_value(parts[1]),
temperature_c = _parse_smi_value(parts[2]),
@@ -259,7 +268,9 @@ def get_backend_visible_gpu_info(
"index": idx,
"index_kind": "physical",
"visible_ordinal": (
- visible_ordinals[idx] if visible_ordinals is not None else len(devices)
+ visible_ordinals[idx]
+ if visible_ordinals is not None
+ else len(devices)
),
"name": name,
"memory_total_gb": round(mem_total_mb / 1024, 2),
diff --git a/studio/backend/utils/hardware/vram_estimation.py b/studio/backend/utils/hardware/vram_estimation.py
index 86069ead3d..8dd99fe55e 100644
--- a/studio/backend/utils/hardware/vram_estimation.py
+++ b/studio/backend/utils/hardware/vram_estimation.py
@@ -16,7 +16,9 @@ from dataclasses import dataclass, field
from typing import Dict, Optional
QUANT_4BIT_FACTOR = 16 / 5
-DOUBLE_QUANT_4BIT_FACTOR = 3.6 # bnb_4bit_use_double_quant; see VRAM_ESTIMATION.md section 1
+DOUBLE_QUANT_4BIT_FACTOR = (
+ 3.6 # bnb_4bit_use_double_quant; see VRAM_ESTIMATION.md section 1
+)
CUDA_OVERHEAD_BYTES = int(1.4 * 1024**3) # calibrated on RTX 5070 Ti
NON_FLASH_ATTENTION_FACTOR = (
12.0 # eager attention score+workspace overhead; see VRAM_ESTIMATION.md section 5
@@ -144,7 +146,12 @@ class VramBreakdown:
Weights/LoRA/optimizer/gradients shard across GPUs; activations do
NOT (the GPU running a layer holds them).
"""
- shardable = self.model_weights + self.lora_adapters + self.optimizer_states + self.gradients
+ shardable = (
+ self.model_weights
+ + self.lora_adapters
+ + self.optimizer_states
+ + self.gradients
+ )
per_gpu_fixed = self.activations + self.cuda_overhead
return shardable // max(n_gpus, 1) + per_gpu_fixed
@@ -184,7 +191,9 @@ def _compute_dense_layer_indices(text_config, total_layers: int) -> tuple:
layer_types = getattr(text_config, "mlp_layer_types", None)
if layer_types:
return tuple(
- i for i, t in enumerate(layer_types[:total_layers]) if str(t).lower() == "dense"
+ i
+ for i, t in enumerate(layer_types[:total_layers])
+ if str(t).lower() == "dense"
)
# Llama4TextConfig.__init__ auto-populates self.moe_layers from
@@ -221,7 +230,9 @@ def _compute_dense_layer_indices(text_config, total_layers: int) -> tuple:
if sparse_step is not None and sparse_step > 0:
mlp_only_set = {int(i) for i in mlp_only}
return tuple(
- i for i in range(total_layers) if i in mlp_only_set or (i + 1) % sparse_step != 0
+ i
+ for i in range(total_layers)
+ if i in mlp_only_set or (i + 1) % sparse_step != 0
)
return ()
@@ -249,7 +260,8 @@ def extract_arch_config(hf_config) -> Optional[ModelArchConfig]:
intermediate_size = hidden_size * 4
if not all(
- v is not None for v in (hidden_size, num_layers, num_heads, intermediate_size, vocab_size)
+ v is not None
+ for v in (hidden_size, num_layers, num_heads, intermediate_size, vocab_size)
):
return None
if num_heads <= 0:
@@ -312,7 +324,9 @@ def extract_arch_config(hf_config) -> Optional[ModelArchConfig]:
# intermediate_size. One shared_expert per MoE layer (modeling_llama4.py).
intermediate_size_mlp_raw = _first_scalar(_moe_attr("intermediate_size_mlp"))
dense_intermediate_size = (
- int(intermediate_size_mlp_raw) if intermediate_size_mlp_raw is not None else None
+ int(intermediate_size_mlp_raw)
+ if intermediate_size_mlp_raw is not None
+ else None
)
if (
intermediate_size_mlp_raw is not None
@@ -371,7 +385,9 @@ def extract_arch_config(hf_config) -> Optional[ModelArchConfig]:
None,
)
or 0,
- quantization_skip_modules = list(quantization_config.get("llm_int8_skip_modules", []) or []),
+ quantization_skip_modules = list(
+ quantization_config.get("llm_int8_skip_modules", []) or []
+ ),
quant_4bit_factor = quant_4bit_factor,
moe_has_dense_mlp = bool(getattr(text_config, "enable_moe_block", False)),
dense_layer_indices = dense_layer_indices,
@@ -458,7 +474,11 @@ def _per_layer_input_lora_params(arch: ModelArchConfig, r: int, target_modules)
pli = arch.hidden_size_per_layer_input
if pli <= 0:
return 0
- targets = {target_modules} if isinstance(target_modules, str) else set(target_modules or [])
+ targets = (
+ {target_modules}
+ if isinstance(target_modules, str)
+ else set(target_modules or [])
+ )
n_layers = arch.num_hidden_layers
hd = arch.hidden_size
total = 0
@@ -475,7 +495,11 @@ def _layer_attention_dims(arch: ModelArchConfig, layer_idx: int) -> tuple:
layer_types = _layer_types(arch)
layer_type = layer_types[layer_idx]
is_sliding = layer_type == "sliding_attention"
- head_dim = arch.global_head_dim if not is_sliding and arch.global_head_dim else _head_dim(arch)
+ head_dim = (
+ arch.global_head_dim
+ if not is_sliding and arch.global_head_dim
+ else _head_dim(arch)
+ )
use_alt_attention = arch.attention_k_eq_v and not is_sliding
num_kv_heads = (
arch.num_global_key_value_heads
@@ -495,7 +519,9 @@ def _layer_mlp_size(arch: ModelArchConfig, layer_idx: int) -> int:
return _dense_mlp_size(arch)
-def _text_linear_dims(arch: ModelArchConfig, layer_idx: int) -> Dict[str, tuple[int, int]]:
+def _text_linear_dims(
+ arch: ModelArchConfig, layer_idx: int
+) -> Dict[str, tuple[int, int]]:
hd = arch.hidden_size
if _uses_structured_layer_shapes(arch):
q_size, kv_size, has_k, has_v = _layer_attention_dims(arch, layer_idx)
@@ -561,7 +587,9 @@ def _add_module_aliases(aliases: Dict[str, str], canonical: str, suffix: str) ->
aliases[alias] = canonical
-def _build_text_module_elements(arch: ModelArchConfig) -> tuple[Dict[str, int], Dict[str, str]]:
+def _build_text_module_elements(
+ arch: ModelArchConfig,
+) -> tuple[Dict[str, int], Dict[str, str]]:
elements: Dict[str, int] = {}
aliases: Dict[str, str] = {}
@@ -572,8 +600,12 @@ def _build_text_module_elements(arch: ModelArchConfig) -> tuple[Dict[str, int],
for layer_idx in range(arch.num_hidden_layers):
layer_modules: Dict[str, int] = {}
dims = _text_linear_dims(arch, layer_idx)
- attn_dims = {name: dim for name, dim in dims.items() if name in ATTENTION_TARGET_MODULES}
- mlp_dims = {name: dim for name, dim in dims.items() if name in MLP_TARGET_MODULES}
+ attn_dims = {
+ name: dim for name, dim in dims.items() if name in ATTENTION_TARGET_MODULES
+ }
+ mlp_dims = {
+ name: dim for name, dim in dims.items() if name in MLP_TARGET_MODULES
+ }
if is_mla:
# MLA splits q/o into q_a/q_b/kv_a/kv_b; emit a single self_attn
@@ -620,7 +652,10 @@ def _build_text_module_elements(arch: ModelArchConfig) -> tuple[Dict[str, int],
)
else:
layer_modules.update(
- {f"mlp.{name}": in_dim * out_dim for name, (in_dim, out_dim) in mlp_dims.items()}
+ {
+ f"mlp.{name}": in_dim * out_dim
+ for name, (in_dim, out_dim) in mlp_dims.items()
+ }
)
if pli > 0:
@@ -643,7 +678,10 @@ def _build_text_module_elements(arch: ModelArchConfig) -> tuple[Dict[str, int],
for name, value in layer_modules.items()
if (
name == "mlp"
- or (name.startswith("mlp.") and not (is_sibling_experts and name == "mlp.experts"))
+ or (
+ name.startswith("mlp.")
+ and not (is_sibling_experts and name == "mlp.experts")
+ )
)
)
experts_total = layer_modules.get("mlp.experts", 0) if is_sibling_experts else 0
@@ -698,7 +736,10 @@ def _compute_skipped_quantizable_elements(arch: ModelArchConfig) -> int:
pruned = {
canonical
for canonical in matched
- if not any(canonical != parent and canonical.startswith(f"{parent}.") for parent in matched)
+ if not any(
+ canonical != parent and canonical.startswith(f"{parent}.")
+ for parent in matched
+ )
}
return sum(module_elements[canonical] for canonical in pruned)
@@ -829,7 +870,9 @@ def _compute_layer_elements(arch: ModelArchConfig):
mlp_total = _compute_dense_mlp_elements(arch) * n_layers
layernorms = 2 * hd
- per_layer_embed = arch.vocab_size_per_layer_input * arch.hidden_size_per_layer_input * n_layers
+ per_layer_embed = (
+ arch.vocab_size_per_layer_input * arch.hidden_size_per_layer_input * n_layers
+ )
ple_text_linear = _per_layer_input_quantizable(arch)
ple_norms = _per_layer_input_norm_elements(arch)
embed_tokens = arch.vocab_size * hd + per_layer_embed + ple_norms
@@ -851,7 +894,9 @@ def compute_model_weights_bytes(
)
quantized = total_quantizable - skipped_quantizable
return int(
- quantized * 2 / arch.quant_4bit_factor + skipped_quantizable * 2 + non_quantizable * 2
+ quantized * 2 / arch.quant_4bit_factor
+ + skipped_quantizable * 2
+ + non_quantizable * 2
)
return int((total_quantizable + non_quantizable) * 2)
@@ -907,7 +952,9 @@ def _lora_mlp_elements(
return total
-def compute_lora_params(arch: ModelArchConfig, lora_rank: int, target_modules: list) -> int:
+def compute_lora_params(
+ arch: ModelArchConfig, lora_rank: int, target_modules: list
+) -> int:
all_linear = _targets_all_linear(target_modules)
selected_modules = list(DEFAULT_TARGET_MODULES) if all_linear else target_modules
hd = arch.hidden_size
@@ -972,7 +1019,11 @@ def compute_lora_params(arch: ModelArchConfig, lora_rank: int, target_modules: l
mlp_total = moe_mlp * n_moe + dense_only
else:
mlp_total = structured_dense_mlp
- return attn_total + mlp_total + _per_layer_input_lora_params(arch, r, target_modules)
+ return (
+ attn_total
+ + mlp_total
+ + _per_layer_input_lora_params(arch, r, target_modules)
+ )
elif n_experts > 1:
attn_total = _lora_attn_elements(arch, r, selected_modules) * n_layers
n_dense = arch.num_dense_layers
@@ -1024,7 +1075,9 @@ def compute_lora_params(arch: ModelArchConfig, lora_rank: int, target_modules: l
* n_layers
)
- return attn_total + mlp_total + _per_layer_input_lora_params(arch, r, target_modules)
+ return (
+ attn_total + mlp_total + _per_layer_input_lora_params(arch, r, target_modules)
+ )
def compute_lora_adapter_bytes(lora_params: int) -> int:
@@ -1100,7 +1153,9 @@ def _per_layer_activation_bytes(
# layer when hidden_size_per_layer_input is set (gemma4 modular:1141-1145).
pli = arch.hidden_size_per_layer_input
activation_ple = seq_len * batch_size * (arch.hidden_size + pli) if pli > 0 else 0
- return int((activation_qkv + residual_memory + activation_mlp + activation_ple) * 2 * 1.25)
+ return int(
+ (activation_qkv + residual_memory + activation_mlp + activation_ple) * 2 * 1.25
+ )
def compute_activation_bytes(
@@ -1121,12 +1176,14 @@ def compute_activation_bytes(
if gc_multiplier is None:
effective_layers = n_layers
linear_bytes = sum(
- _per_layer_activation_bytes(arch, i, batch_size, seq_len) for i in range(n_layers)
+ _per_layer_activation_bytes(arch, i, batch_size, seq_len)
+ for i in range(n_layers)
)
else:
effective_layers = gc_multiplier
max_layer_bytes = max(
- _per_layer_activation_bytes(arch, i, batch_size, seq_len) for i in range(n_layers)
+ _per_layer_activation_bytes(arch, i, batch_size, seq_len)
+ for i in range(n_layers)
)
linear_bytes = int(max_layer_bytes * effective_layers)
@@ -1149,7 +1206,9 @@ def compute_activation_bytes(
)
-def estimate_training_vram(arch: ModelArchConfig, config: TrainingVramConfig) -> VramBreakdown:
+def estimate_training_vram(
+ arch: ModelArchConfig, config: TrainingVramConfig
+) -> VramBreakdown:
method = config.training_method.lower()
is_lora = method in ("qlora", "lora")
load_in_4bit = config.load_in_4bit or method == "qlora"
diff --git a/studio/backend/utils/inference/inference_config.py b/studio/backend/utils/inference/inference_config.py
index 05eb08067c..bbc65c35fa 100644
--- a/studio/backend/utils/inference/inference_config.py
+++ b/studio/backend/utils/inference/inference_config.py
@@ -28,7 +28,10 @@ def _load_family_defaults():
return
json_path = (
- Path(__file__).parent.parent.parent / "assets" / "configs" / "inference_defaults.json"
+ Path(__file__).parent.parent.parent
+ / "assets"
+ / "configs"
+ / "inference_defaults.json"
)
try:
with open(json_path, "r", encoding = "utf-8") as f:
diff --git a/studio/backend/utils/llama_cpp_freshness.py b/studio/backend/utils/llama_cpp_freshness.py
index 87d0d2ec01..905200a348 100644
--- a/studio/backend/utils/llama_cpp_freshness.py
+++ b/studio/backend/utils/llama_cpp_freshness.py
@@ -153,7 +153,9 @@ def _fetch_latest_release_tag(repo: str, timeout: float = 5.0) -> Optional[str]:
return newest["tag_name"]
-def latest_published_release(repo: str, *, force_refresh: bool = False) -> Optional[str]:
+def latest_published_release(
+ repo: str, *, force_refresh: bool = False
+) -> Optional[str]:
"""Latest release tag for `repo`. Memo + disk-cached (24h TTL).
None when offline and never previously cached."""
if not repo:
diff --git a/studio/backend/utils/llama_cpp_update.py b/studio/backend/utils/llama_cpp_update.py
index 8b90d36bc5..fc7af35ff9 100644
--- a/studio/backend/utils/llama_cpp_update.py
+++ b/studio/backend/utils/llama_cpp_update.py
@@ -110,7 +110,10 @@ def _installer_script() -> Optional[Path]:
return Path(env)
here = Path(__file__).resolve()
for up in here.parents:
- for cand in (up / "install_llama_prebuilt.py", up / "studio" / "install_llama_prebuilt.py"):
+ for cand in (
+ up / "install_llama_prebuilt.py",
+ up / "studio" / "install_llama_prebuilt.py",
+ ):
if cand.is_file():
return cand
return None
@@ -169,7 +172,9 @@ def _installed_build_number(binary: Optional[str]) -> Optional[int]:
if not binary:
return None
try:
- proc = subprocess.run([binary, "--version"], capture_output = True, text = True, timeout = 20)
+ proc = subprocess.run(
+ [binary, "--version"], capture_output = True, text = True, timeout = 20
+ )
except Exception: # pragma: no cover - defensive
return None
m = re.search(r"version:\s*(\d+)", (proc.stderr or "") + (proc.stdout or ""))
@@ -235,7 +240,9 @@ def _source_build_status(binary: str, *, force_refresh: bool) -> Optional[dict]:
# Suppress only when the source build is reliably newer/equal; unknown
# version (the involuntary source-build case) is treated as behind.
update_available = (
- installed_build is None or latest_build is None or installed_build < latest_build
+ installed_build is None
+ or latest_build is None
+ or installed_build < latest_build
)
with _job_lock:
job = dict(_job)
@@ -325,7 +332,9 @@ def _rocm_install_args(asset: Optional[str]) -> list[str]:
return ["--has-rocm"]
-def _run_update(install_dir: Path, repo: str, asset: Optional[str], script: Path) -> None:
+def _run_update(
+ install_dir: Path, repo: str, asset: Optional[str], script: Path
+) -> None:
"""Worker: put the backend into a maintenance state, run the installer for
the latest prebuilt, then refresh caches so the next load uses the new build."""
backend = None
@@ -338,7 +347,8 @@ def _run_update(install_dir: Path, repo: str, asset: Optional[str], script: Path
backend = get_llama_cpp_backend()
except Exception as exc:
logger.debug(
- "llama update: backend unavailable, skipping load coordination", error = str(exc)
+ "llama update: backend unavailable, skipping load coordination",
+ error = str(exc),
)
backend = None
@@ -395,7 +405,9 @@ def _run_update(install_dir: Path, repo: str, asset: Optional[str], script: Path
m = _PROGRESS_LINE_RE.search(line)
if m is None:
continue
- fraction = min(float(m.group(1)) / 100.0, 1.0) * _DOWNLOAD_PROGRESS_CEILING
+ fraction = (
+ min(float(m.group(1)) / 100.0, 1.0) * _DOWNLOAD_PROGRESS_CEILING
+ )
with _job_lock:
_job["progress"] = max(_job.get("progress") or 0.0, fraction)
returncode = proc.wait()
@@ -417,7 +429,9 @@ def _run_update(install_dir: Path, repo: str, asset: Optional[str], script: Path
try:
latest_published_release(repo, force_refresh = True)
except Exception as exc: # pragma: no cover - network defensive
- logger.debug("llama update: post-install freshness refresh failed", error = str(exc))
+ logger.debug(
+ "llama update: post-install freshness refresh failed", error = str(exc)
+ )
new_marker = read_install_marker(_find_binary())
new_tag = (new_marker or {}).get("tag") or (new_marker or {}).get("release_tag")
diff --git a/studio/backend/utils/models/checkpoints.py b/studio/backend/utils/models/checkpoints.py
index 5a992926ec..3ccfceac86 100644
--- a/studio/backend/utils/models/checkpoints.py
+++ b/studio/backend/utils/models/checkpoints.py
@@ -92,7 +92,9 @@ def scan_checkpoints(
name_part = parts[0]
idx = name_part.find("_")
if idx > 0:
- metadata["base_model"] = name_part[:idx] + "/" + name_part[idx + 1 :]
+ metadata["base_model"] = (
+ name_part[:idx] + "/" + name_part[idx + 1 :]
+ )
else:
metadata["base_model"] = name_part
@@ -122,7 +124,9 @@ def scan_checkpoints(
)
models.append((item.name, checkpoints, metadata))
- logger.debug(f"Found model: {item.name} with {len(checkpoints)} checkpoint(s)")
+ logger.debug(
+ f"Found model: {item.name} with {len(checkpoints)} checkpoint(s)"
+ )
# Sort by modification time (newest first)
models.sort(key = lambda x: Path(x[1][0][1]).stat().st_mtime, reverse = True)
diff --git a/studio/backend/utils/models/model_config.py b/studio/backend/utils/models/model_config.py
index 7245343eeb..ba6cc613f4 100644
--- a/studio/backend/utils/models/model_config.py
+++ b/studio/backend/utils/models/model_config.py
@@ -55,13 +55,19 @@ def _env_offline() -> bool:
# ── Model size extraction ────────────────────────────────────
import re as _re
-_MODEL_SIZE_RE = _re.compile(r"(?:^|[-_/])(\d+\.?\d*)\s*([bm])(?:$|[-_/])", _re.IGNORECASE)
+_MODEL_SIZE_RE = _re.compile(
+ r"(?:^|[-_/])(\d+\.?\d*)\s*([bm])(?:$|[-_/])", _re.IGNORECASE
+)
# MoE active-parameter pattern: "A3B", "A3.5B", etc.
-_ACTIVE_SIZE_RE = _re.compile(r"(?:^|[-_/])a(\d+\.?\d*)\s*([bm])(?:$|[-_/])", _re.IGNORECASE)
+_ACTIVE_SIZE_RE = _re.compile(
+ r"(?:^|[-_/])a(\d+\.?\d*)\s*([bm])(?:$|[-_/])", _re.IGNORECASE
+)
# Gemma 3n/4 effective-parameter pattern: "E2B", "E4B" -- the runtime
# footprint (MatFormer + per-layer embeddings), which is the size that
# matters for size-gated policies like sub-3B speculative-decoding fallback.
-_EFFECTIVE_SIZE_RE = _re.compile(r"(?:^|[-_/])e(\d+\.?\d*)\s*([bm])(?:$|[-_/])", _re.IGNORECASE)
+_EFFECTIVE_SIZE_RE = _re.compile(
+ r"(?:^|[-_/])e(\d+\.?\d*)\s*([bm])(?:$|[-_/])", _re.IGNORECASE
+)
def extract_model_size_b(model_id: str) -> float | None:
@@ -555,7 +561,10 @@ def _raw_config_has_vision_config(
if model_type in _AUDIO_ONLY_MODEL_TYPES:
return False
return (
- any(isinstance(x, str) and x.endswith(_VLM_ARCH_SUFFIXES) for x in architectures)
+ any(
+ isinstance(x, str) and x.endswith(_VLM_ARCH_SUFFIXES)
+ for x in architectures
+ )
or "vision_config" in config
or "img_processor" in config
or "image_token_index" in config
@@ -627,7 +636,9 @@ except Exception as exc:
)
-def _is_vision_model_subprocess(model_name: str, hf_token: Optional[str] = None) -> Optional[bool]:
+def _is_vision_model_subprocess(
+ model_name: str, hf_token: Optional[str] = None
+) -> Optional[bool]:
"""Run is_vision_model in a subprocess with transformers 5.x.
Spawns a clean subprocess with .venv_t5/ on sys.path so AutoConfig
@@ -773,7 +784,9 @@ def is_vision_model(model_name: str, hf_token: Optional[str] = None) -> bool:
return False
-def _is_vision_model_uncached(model_name: str, hf_token: Optional[str] = None) -> Optional[bool]:
+def _is_vision_model_uncached(
+ model_name: str, hf_token: Optional[str] = None
+) -> Optional[bool]:
"""Uncached vision detection; use is_vision_model() instead.
Returns True/False for definitive results, or None on transient errors
@@ -855,7 +868,9 @@ _AUDIO_TOKEN_PATTERNS = {
and "<|text_start|>" in tokens
and "<|text_end|>" in tokens
),
- "snac": lambda tokens: (sum(1 for t in tokens if t.startswith(" 10000),
+ "snac": lambda tokens: (
+ sum(1 for t in tokens if t.startswith(" 10000
+ ),
}
@@ -877,7 +892,9 @@ def detect_audio_type(model_name: str, hf_token: Optional[str] = None) -> Option
return result
-def _detect_audio_from_tokenizer(model_name: str, hf_token: Optional[str] = None) -> Optional[str]:
+def _detect_audio_from_tokenizer(
+ model_name: str, hf_token: Optional[str] = None
+) -> Optional[str]:
"""Detect audio type from tokenizer special tokens.
Checks local HF cache first, then fetches tokenizer_config.json from HF;
@@ -938,7 +955,9 @@ def _detect_audio_from_tokenizer(model_name: str, hf_token: Optional[str] = None
return None
except Exception as e:
- logger.debug(f"Could not detect audio type from tokenizer for {model_name}: {e}")
+ logger.debug(
+ f"Could not detect audio type from tokenizer for {model_name}: {e}"
+ )
return None
@@ -1209,7 +1228,9 @@ def detect_mtp_file(path: str, search_root: Optional[str] = None) -> Optional[st
if not (name.startswith("mtp-") and name.endswith(".gguf")):
continue
stem = name[len("mtp-") : -len(".gguf")]
- if not stem or (weight_name is not None and not weight_name.startswith(stem)):
+ if not stem or (
+ weight_name is not None and not weight_name.startswith(stem)
+ ):
continue
try:
if f.is_file():
@@ -1253,7 +1274,8 @@ def detect_gguf_model(path: str) -> Optional[str]:
(
f
for f in _iter_gguf_files(p)
- if not _is_mmproj(f.name) and not _is_mtp_drafter(f"{f.parent.name}/{f.name}")
+ if not _is_mmproj(f.name)
+ and not _is_mtp_drafter(f"{f.parent.name}/{f.name}")
),
key = lambda f: f.stat().st_size,
reverse = True,
@@ -1450,7 +1472,9 @@ def _iter_hf_cache_snapshots(repo_id: str):
yield from snap_dirs
-def _list_gguf_variants_from_hf_cache(repo_id: str) -> Optional[tuple[list[GgufVariantInfo], bool]]:
+def _list_gguf_variants_from_hf_cache(
+ repo_id: str,
+) -> Optional[tuple[list[GgufVariantInfo], bool]]:
"""Variants from the local HF cache snapshot, or None if not cached."""
for snap in _iter_hf_cache_snapshots(repo_id):
variants, has_vision = list_local_gguf_variants(str(snap))
@@ -1652,14 +1676,17 @@ def _detect_gguf_from_hf_cache(repo_id: str) -> Optional[str]:
rel_files = [
rel
for f in _iter_gguf_files(snap, recursive = True)
- if not _is_mtp_drafter(rel := f.relative_to(snap).as_posix()) and not _is_mmproj(f.name)
+ if not _is_mtp_drafter(rel := f.relative_to(snap).as_posix())
+ and not _is_mmproj(f.name)
]
if rel_files:
return _pick_best_gguf(rel_files)
return None
-def detect_gguf_model_remote(repo_id: str, hf_token: Optional[str] = None) -> Optional[str]:
+def detect_gguf_model_remote(
+ repo_id: str, hf_token: Optional[str] = None
+) -> Optional[str]:
"""Return the best GGUF filename in a HF repo, or None.
Retries (3 attempts, 1s/2s/4s backoff) on transient HF Hub failures: a
@@ -1705,7 +1732,9 @@ def detect_gguf_model_remote(repo_id: str, hf_token: Optional[str] = None) -> Op
)
return cached
- logger.warning(f"Could not check GGUF files for '{repo_id}' after 3 attempts: {last_err}")
+ logger.warning(
+ f"Could not check GGUF files for '{repo_id}' after 3 attempts: {last_err}"
+ )
return None
@@ -1828,7 +1857,9 @@ def _looks_like_lora_adapter(model_dir: Path) -> bool:
)
-def scan_trained_models(outputs_dir: str = str(outputs_root())) -> List[Tuple[str, str, str]]:
+def scan_trained_models(
+ outputs_dir: str = str(outputs_root()),
+) -> List[Tuple[str, str, str]]:
"""Scan outputs folder for trained Studio models.
Returns:
@@ -1894,7 +1925,9 @@ def scan_exported_models(
# Flat GGUF export (e.g. exports/gemma-3-4b-it-finetune-gguf/).
# Skip mmproj (vision projection) files — not loadable as main models.
- gguf_files = [f for f in _iter_gguf_files(run_dir) if not _is_mmproj(f.name)]
+ gguf_files = [
+ f for f in _iter_gguf_files(run_dir) if not _is_mmproj(f.name)
+ ]
if gguf_files:
base_model = None
export_meta = run_dir / "export_metadata.json"
@@ -1968,7 +2001,9 @@ def scan_exported_models(
# Fallback: base model from ./outputs/{run_name}/adapter_config.json
if not base_model:
- outputs_adapter_cfg = resolve_output_dir(run_dir.name) / "adapter_config.json"
+ outputs_adapter_cfg = (
+ resolve_output_dir(run_dir.name) / "adapter_config.json"
+ )
try:
if outputs_adapter_cfg.exists():
cfg = json.loads(outputs_adapter_cfg.read_text())
@@ -2001,7 +2036,9 @@ def get_base_model_from_checkpoint(checkpoint_path: str) -> Optional[str]:
config = json.load(f)
base_model = config.get("base_model_name_or_path")
if base_model:
- logger.info("Detected base model from adapter_config.json: %s", base_model)
+ logger.info(
+ "Detected base model from adapter_config.json: %s", base_model
+ )
return base_model
config_path = checkpoint_path_obj / "config.json"
@@ -2066,7 +2103,9 @@ def get_base_model_from_lora(lora_path: str) -> Optional[str]:
config = json.load(f)
base_model = config.get("base_model_name_or_path")
if base_model:
- logger.info(f"Detected base model from adapter_config.json: {base_model}")
+ logger.info(
+ f"Detected base model from adapter_config.json: {base_model}"
+ )
return base_model
# Fallback: try training_args.bin (requires torch)
@@ -2126,7 +2165,9 @@ def load_model_defaults(model_name: str) -> Dict[str, Any]:
if config_path.is_file():
with open(config_path, "r", encoding = "utf-8") as f:
config = yaml.safe_load(f) or {}
- logger.info(f"Loaded model defaults from {config_path} (via mapping)")
+ logger.info(
+ f"Loaded model defaults from {config_path} (via mapping)"
+ )
return config
# For local paths (e.g. /home/.../Spark-TTS-0.5B/LLM from
@@ -2195,11 +2236,17 @@ class ModelConfig:
is_lora: bool # LoRA adapter?
is_gguf: bool = False # GGUF model?
is_audio: bool = False # TTS audio model?
- audio_type: Optional[str] = None # Audio codec type: 'snac', 'csm', 'bicodec', 'dac'
+ audio_type: Optional[str] = (
+ None # Audio codec type: 'snac', 'csm', 'bicodec', 'dac'
+ )
has_audio_input: bool = False # Accepts audio input (ASR/speech understanding)
gguf_file: Optional[str] = None # Full path to the .gguf file (local mode)
- gguf_mmproj_file: Optional[str] = None # Full path to the mmproj .gguf file (vision projection)
- gguf_mtp_file: Optional[str] = None # Full path to the separate MTP drafter (local mode)
+ gguf_mmproj_file: Optional[str] = (
+ None # Full path to the mmproj .gguf file (vision projection)
+ )
+ gguf_mtp_file: Optional[str] = (
+ None # Full path to the separate MTP drafter (local mode)
+ )
gguf_hf_repo: Optional[str] = (
None # HF repo ID for -hf mode (e.g. "unsloth/gemma-3-4b-it-GGUF")
)
@@ -2337,7 +2384,9 @@ class ModelConfig:
gguf_is_vision = True
logger.info(f"Detected mmproj for vision: {mmproj_file}")
elif base_is_vision:
- logger.warning(f"Base model is vision but no mmproj file found in {gguf_dir}")
+ logger.warning(
+ f"Base model is vision but no mmproj file found in {gguf_dir}"
+ )
# Separate MTP drafter sibling (Gemma 4), mirroring mmproj.
mtp_file = detect_mtp_file(gguf_file, search_root = companion_root)
@@ -2406,11 +2455,15 @@ class ModelConfig:
# Auto-detect LoRA for local paths (adapter_config.json on disk)
if not is_lora and is_local:
detected_base = (
- get_base_model_from_lora(path) if _looks_like_lora_adapter(Path(path)) else None
+ get_base_model_from_lora(path)
+ if _looks_like_lora_adapter(Path(path))
+ else None
)
if detected_base:
is_lora = True
- logger.info(f"Auto-detected local LoRA adapter at '{path}' (base: {detected_base})")
+ logger.info(
+ f"Auto-detected local LoRA adapter at '{path}' (base: {detected_base})"
+ )
# Auto-detect LoRA for remote HF models. When offline, huggingface_hub
# raises OfflineModeIsEnabled in ~0ms; we fall through to the cache.
@@ -2424,14 +2477,18 @@ class ModelConfig:
is_lora = True
logger.info(f"Auto-detected remote LoRA adapter: '{identifier}'")
except Exception as e:
- logger.debug(f"Could not check remote LoRA status for '{identifier}': {e}")
+ logger.debug(
+ f"Could not check remote LoRA status for '{identifier}': {e}"
+ )
# API may have failed; adapter_config.json could still be cached.
if not is_lora:
for snap in _iter_hf_cache_snapshots(identifier):
if (snap / "adapter_config.json").is_file():
is_lora = True
- logger.info(f"Auto-detected cached LoRA adapter: '{identifier}'")
+ logger.info(
+ f"Auto-detected cached LoRA adapter: '{identifier}'"
+ )
break
# Handle LoRA adapters
@@ -2445,7 +2502,9 @@ class ModelConfig:
try:
from huggingface_hub import hf_hub_download
- config_path = hf_hub_download(identifier, "adapter_config.json", token = hf_token)
+ config_path = hf_hub_download(
+ identifier, "adapter_config.json", token = hf_token
+ )
with open(config_path, "r") as f:
adapter_config = json.load(f)
base_model = adapter_config.get("base_model_name_or_path")
@@ -2506,7 +2565,9 @@ class ModelConfig:
# Resolve display names via the 'local_models' parameter
if " (Active)" in selected or " (Ready)" in selected:
- clean_display_name = selected.replace(" (Active)", "").replace(" (Ready)", "")
+ clean_display_name = selected.replace(" (Active)", "").replace(
+ " (Ready)", ""
+ )
if local_models:
for local_display, local_path in local_models:
if local_display == clean_display_name:
diff --git a/studio/backend/utils/native_path_leases.py b/studio/backend/utils/native_path_leases.py
index 7d8514abc8..58b4a3c653 100644
--- a/studio/backend/utils/native_path_leases.py
+++ b/studio/backend/utils/native_path_leases.py
@@ -68,7 +68,9 @@ def native_path_leases_supported() -> bool:
return True
-def child_env_without_native_path_secret(env: Mapping[str, str] | None = None) -> dict[str, str]:
+def child_env_without_native_path_secret(
+ env: Mapping[str, str] | None = None,
+) -> dict[str, str]:
"""Return a child-process env with the native path lease secret removed."""
if env is None:
@@ -80,7 +82,9 @@ def child_env_without_native_path_secret(env: Mapping[str, str] | None = None) -
return cleaned
-def run_without_native_path_secret(target: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
+def run_without_native_path_secret(
+ target: Callable[..., Any], *args: Any, **kwargs: Any
+) -> Any:
"""Run a multiprocessing child target without the native path lease secret."""
global _CACHED_LEASE_SECRET, _SCRUB_SAVED_SECRET
@@ -147,7 +151,9 @@ def verify_native_path_lease(
raise NativePathLeaseError("Native path is no longer accessible.") from exc
_reject_network_or_device_path(resolved)
if not _same_native_path(resolved, path):
- raise NativePathLeaseError("Native path grant no longer resolves to the selected path.")
+ raise NativePathLeaseError(
+ "Native path grant no longer resolves to the selected path."
+ )
grant = NativePathGrant(
operation = str(payload["operation"]),
@@ -211,7 +217,9 @@ def _decode_secret() -> bytes:
if encoded is None and _SCRUB_SAVED_SECRET is not None:
encoded = _SCRUB_SAVED_SECRET
if not encoded:
- raise NativePathLeaseError("Native path grants require the managed desktop backend.")
+ raise NativePathLeaseError(
+ "Native path grants require the managed desktop backend."
+ )
try:
secret = _b64decode(encoded)
except Exception as exc:
@@ -262,7 +270,9 @@ def _validate_payload(
)
missing = [key for key in required if key not in payload]
if missing:
- raise NativePathLeaseError("Native path grant payload is missing required fields.")
+ raise NativePathLeaseError(
+ "Native path grant payload is missing required fields."
+ )
if _required_int(payload, "version") != 1:
raise NativePathLeaseError("Native path grant version is unsupported.")
if payload["operation"] != operation:
@@ -341,13 +351,19 @@ def _reject_network_or_device_path(path: Path) -> None:
rest = normalized[4:]
is_local_drive = len(rest) >= 3 and rest[0].isalpha() and rest[1:3] == ":\\"
if not is_local_drive:
- raise NativePathLeaseError("Network paths are not supported for native grants.")
+ raise NativePathLeaseError(
+ "Network paths are not supported for native grants."
+ )
elif normalized.startswith("\\\\"):
- raise NativePathLeaseError("Network paths are not supported for native grants.")
+ raise NativePathLeaseError(
+ "Network paths are not supported for native grants."
+ )
if os.name != "nt":
for root in ("/dev", "/proc", "/sys"):
if path.is_relative_to(root):
- raise NativePathLeaseError("Device and virtual filesystem paths are not supported.")
+ raise NativePathLeaseError(
+ "Device and virtual filesystem paths are not supported."
+ )
if "\x00" in text:
raise NativePathLeaseError("Native path contains invalid characters.")
@@ -379,7 +395,9 @@ def _optional_int(value: Any) -> int | None:
def _required_int(payload: dict[str, Any], key: str) -> int:
raw = payload.get(key)
if raw is None:
- raise NativePathLeaseError("Native path grant payload is missing required fields.")
+ raise NativePathLeaseError(
+ "Native path grant payload is missing required fields."
+ )
try:
return int(raw)
except (TypeError, ValueError) as exc:
diff --git a/studio/backend/utils/paths/storage_roots.py b/studio/backend/utils/paths/storage_roots.py
index d336bc2e71..cd7f9e3313 100644
--- a/studio/backend/utils/paths/storage_roots.py
+++ b/studio/backend/utils/paths/storage_roots.py
@@ -276,7 +276,9 @@ def _setup_cache_env() -> None:
user hasn't, so explicit overrides are honored.
"""
root = cache_root()
- xdg_cache = Path(os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache")).expanduser()
+ xdg_cache = Path(
+ os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache")
+ ).expanduser()
hf_default = xdg_cache / "huggingface"
defaults: dict[str, str] = {
"HF_HOME": str(hf_default),
@@ -309,7 +311,9 @@ def ensure_studio_directories() -> None:
_setup_cache_env()
-def _clean_relative_path(path_value: str, *, strip_prefixes: tuple[str, ...] = ()) -> Path:
+def _clean_relative_path(
+ path_value: str, *, strip_prefixes: tuple[str, ...] = ()
+) -> Path:
path = Path(path_value).expanduser()
parts = [part for part in path.parts if part not in ("", ".")]
while parts and parts[0] in strip_prefixes:
@@ -348,7 +352,8 @@ def _assert_contained(resolved: Path, root: Path) -> None:
resolved_real.relative_to(root_real)
except ValueError as exc:
raise ValueError(
- f"path escapes root: {resolved!s} -> {resolved_real!s} " f"is not under {root_real!s}"
+ f"path escapes root: {resolved!s} -> {resolved_real!s} "
+ f"is not under {root_real!s}"
) from exc
@@ -452,7 +457,9 @@ def resolve_dataset_path(path_value: str) -> Path:
return path
except ValueError:
continue
- raise ValueError(f"dataset path must be relative or under a dataset root: {raw!r}")
+ raise ValueError(
+ f"dataset path must be relative or under a dataset root: {raw!r}"
+ )
parts = [part for part in Path(path_value).parts if part not in ("", ".")]
if parts[:2] == ["assets", "datasets"]:
diff --git a/studio/backend/utils/studio_version.py b/studio/backend/utils/studio_version.py
index 9c18070fbb..946873a2a1 100644
--- a/studio/backend/utils/studio_version.py
+++ b/studio/backend/utils/studio_version.py
@@ -40,7 +40,9 @@ def _path_is_in_site_packages(path: Path) -> bool:
def _is_source_checkout(repo_root: Path) -> bool:
- return (repo_root / ".git").exists() and not _path_is_in_site_packages(Path(__file__).resolve())
+ return (repo_root / ".git").exists() and not _path_is_in_site_packages(
+ Path(__file__).resolve()
+ )
def _exact_git_studio_tag(repo_root: Path) -> str | None:
diff --git a/studio/backend/utils/transformers_version.py b/studio/backend/utils/transformers_version.py
index 6e1571d5ce..cd3af40148 100644
--- a/studio/backend/utils/transformers_version.py
+++ b/studio/backend/utils/transformers_version.py
@@ -313,7 +313,9 @@ def _check_tokenizer_config_needs_v5(model_name: str) -> bool:
_tokenizer_class_cache[model_name] = result
return result
except Exception as exc:
- logger.debug("Could not fetch tokenizer_config.json for '%s': %s", model_name, exc)
+ logger.debug(
+ "Could not fetch tokenizer_config.json for '%s': %s", model_name, exc
+ )
_tokenizer_class_cache[model_name] = False
return False
@@ -354,7 +356,9 @@ def _load_config_json(model_name: str) -> dict | None:
return None
-def _config_matches_tier(cfg: dict, architectures: set[str], model_types: set[str]) -> bool:
+def _config_matches_tier(
+ cfg: dict, architectures: set[str], model_types: set[str]
+) -> bool:
archs = cfg.get("architectures", [])
if any(a in architectures for a in archs):
return True
@@ -574,7 +578,8 @@ def _venv_dir_is_valid(venv_dir: str, packages: tuple[str, ...]) -> bool:
pkg_name_norm = pkg_name.replace("-", "_")
# Directory must exist.
if not any(
- (Path(venv_dir) / d).is_dir() for d in (pkg_name_norm, pkg_name_norm.replace("_", "-"))
+ (Path(venv_dir) / d).is_dir()
+ for d in (pkg_name_norm, pkg_name_norm.replace("_", "-"))
):
return False
# Unpinned packages: existence is enough.
@@ -669,7 +674,9 @@ def _ensure_venv_dir(venv_dir: str, packages: tuple[str, ...], label: str) -> bo
if _venv_dir_is_valid(venv_dir, packages):
return True
- logger.warning("%s not found or incomplete at %s -- installing at runtime", label, venv_dir)
+ logger.warning(
+ "%s not found or incomplete at %s -- installing at runtime", label, venv_dir
+ )
shutil.rmtree(venv_dir, ignore_errors = True)
os.makedirs(venv_dir, exist_ok = True)
for pkg in packages:
@@ -681,7 +688,9 @@ def _ensure_venv_dir(venv_dir: str, packages: tuple[str, ...], label: str) -> bo
def _ensure_venv_t5_530_exists() -> bool:
"""Ensure .venv_t5_530/ exists with transformers 5.3.0."""
- return _ensure_venv_dir(_VENV_T5_530_DIR, _VENV_T5_530_PACKAGES, "transformers 5.3.0")
+ return _ensure_venv_dir(
+ _VENV_T5_530_DIR, _VENV_T5_530_PACKAGES, "transformers 5.3.0"
+ )
def _ensure_venv_t5_550_exists() -> bool:
@@ -812,12 +821,15 @@ def ensure_transformers_version(model_name: str) -> None:
_deactivate_5x()
if not ensure_fn():
raise RuntimeError(
- f"Cannot activate transformers {target_version}: " f"venv missing at {venv_dir}"
+ f"Cannot activate transformers {target_version}: "
+ f"venv missing at {venv_dir}"
)
logger.info("Activating transformers %s…", target_version)
_activate_venv(venv_dir, f"transformers {target_version}")
else:
- logger.info("Reverting to default transformers %s…", TRANSFORMERS_DEFAULT_VERSION)
+ logger.info(
+ "Reverting to default transformers %s…", TRANSFORMERS_DEFAULT_VERSION
+ )
_deactivate_5x()
final = _get_in_memory_version()
diff --git a/studio/backend/utils/update_status.py b/studio/backend/utils/update_status.py
index ad9dabcf36..bf9afcd23a 100644
--- a/studio/backend/utils/update_status.py
+++ b/studio/backend/utils/update_status.py
@@ -72,7 +72,11 @@ def detect_install_source() -> str:
try:
dist = distribution(PACKAGE_NAME)
except PackageNotFoundError:
- return "local_repo" if _path_has_git_parent(_repo_root_from_this_file()) else "unknown"
+ return (
+ "local_repo"
+ if _path_has_git_parent(_repo_root_from_this_file())
+ else "unknown"
+ )
try:
direct_url = dist.read_text("direct_url.json")
@@ -141,7 +145,9 @@ def get_studio_update_status(current_version: str) -> dict[str, Any]:
current_version = current_version,
latest_version = None,
install_source = install_source,
- reason = "invalid_current_version" if current_version != "dev" else "dev_build",
+ reason = "invalid_current_version"
+ if current_version != "dev"
+ else "dev_build",
)
latest_result = get_latest_pypi_version()
if latest_result.latest_version is None:
@@ -209,7 +215,9 @@ def get_latest_pypi_version() -> LatestVersionResult:
error = "Could not check PyPI update metadata.",
)
- ttl = PYPI_SUCCESS_TTL_SECONDS if result.latest_version else PYPI_FAILURE_TTL_SECONDS
+ ttl = (
+ PYPI_SUCCESS_TTL_SECONDS if result.latest_version else PYPI_FAILURE_TTL_SECONDS
+ )
with _cache_condition:
_latest_version_cache = _LatestVersionCacheEntry(
result = result,
@@ -253,7 +261,9 @@ def _fetch_latest_pypi_version() -> LatestVersionResult:
error = "Could not reach PyPI for update metadata.",
)
- latest = payload.get("info", {}).get("version") if isinstance(payload, dict) else None
+ latest = (
+ payload.get("info", {}).get("version") if isinstance(payload, dict) else None
+ )
if not isinstance(latest, str) or not latest.strip():
return LatestVersionResult(
latest_version = None,
@@ -355,4 +365,9 @@ def _parse_current_version(current_version: str) -> Version | None:
def _utc_now_iso() -> str:
- return datetime.now(timezone.utc).replace(microsecond = 0).isoformat().replace("+00:00", "Z")
+ return (
+ datetime.now(timezone.utc)
+ .replace(microsecond = 0)
+ .isoformat()
+ .replace("+00:00", "Z")
+ )
diff --git a/studio/backend/utils/utils.py b/studio/backend/utils/utils.py
index 3818253ac9..1d94fc39aa 100644
--- a/studio/backend/utils/utils.py
+++ b/studio/backend/utils/utils.py
@@ -19,7 +19,9 @@ logger = get_logger(__name__)
# Never return raw exception text to clients; log server-side, return generic.
-def safe_error_detail(error: Exception, fallback: str = "An internal error occurred") -> str:
+def safe_error_detail(
+ error: Exception, fallback: str = "An internal error occurred"
+) -> str:
"""Map an exception to a generic, client-safe message (never raw
``str(error)``, which can leak paths). Log the real exception server-side.
"""
@@ -36,7 +38,9 @@ def safe_error_detail(error: Exception, fallback: str = "An internal error occur
return fallback
-def safe_curated_detail(error: Exception, fallback: str = "An internal error occurred") -> str:
+def safe_curated_detail(
+ error: Exception, fallback: str = "An internal error occurred"
+) -> str:
"""Client-safe text for curated domain/validation exceptions.
Keeps the message (paths stripped) instead of a generic fallback; for known
diff --git a/studio/backend/utils/wheel_utils.py b/studio/backend/utils/wheel_utils.py
index 98697df83c..4e005f8b50 100644
--- a/studio/backend/utils/wheel_utils.py
+++ b/studio/backend/utils/wheel_utils.py
@@ -19,7 +19,9 @@ from utils.subprocess_compat import windows_hidden_subprocess_kwargs
_logger = logging.getLogger(__name__)
-FLASH_ATTN_RELEASE_BASE_URL = "https://github.com/Dao-AILab/flash-attention/releases/download"
+FLASH_ATTN_RELEASE_BASE_URL = (
+ "https://github.com/Dao-AILab/flash-attention/releases/download"
+)
@functools.lru_cache(maxsize = 1)
diff --git a/studio/install_llama_prebuilt.py b/studio/install_llama_prebuilt.py
index 46aa14b041..baf0f9b22e 100644
--- a/studio/install_llama_prebuilt.py
+++ b/studio/install_llama_prebuilt.py
@@ -140,7 +140,9 @@ UPSTREAM_REPO = "ggml-org/llama.cpp"
UPSTREAM_RELEASES_API = f"https://api.github.com/repos/{UPSTREAM_REPO}/releases/latest"
-TEST_MODEL_URL = "https://huggingface.co/ggml-org/models/resolve/main/tinyllamas/stories260K.gguf"
+TEST_MODEL_URL = (
+ "https://huggingface.co/ggml-org/models/resolve/main/tinyllamas/stories260K.gguf"
+)
TEST_MODEL_SHA256 = "270cba1bd5109f42d03350f60406024560464db173c0e387d91f0426d3bd256d"
VALIDATION_MODEL_CACHE_DIRNAME = ".cache"
VALIDATION_MODEL_CACHE_FILENAME = "stories260K.gguf"
@@ -211,8 +213,12 @@ _BLACKWELL_MIN_SM = 120
# windows-cuda build at or above this already covers Blackwell and makes the
# older pinned 13.1 fallback unnecessary (cuda-12.4 is below it).
_BLACKWELL_MIN_TOOLKIT = (12, 8)
-_PINNED_BLACKWELL_LLAMA_SHA256 = "31ddb8b42d7ab4a47cab8c48c397519f580ca502df7e73f3ab396eacc16c8e8d"
-_PINNED_BLACKWELL_CUDART_SHA256 = "f96935e7e385e3b2d0189239077c10fe8fd7e95690fea4afec455b1b6c7e3f18"
+_PINNED_BLACKWELL_LLAMA_SHA256 = (
+ "31ddb8b42d7ab4a47cab8c48c397519f580ca502df7e73f3ab396eacc16c8e8d"
+)
+_PINNED_BLACKWELL_CUDART_SHA256 = (
+ "f96935e7e385e3b2d0189239077c10fe8fd7e95690fea4afec455b1b6c7e3f18"
+)
def _cuda_runtime_lines_for_major(major: int) -> list[str]:
@@ -443,7 +449,9 @@ _LOG_TO_STDOUT = False
def log(message: str) -> None:
- print(f"[llama-prebuilt] {message}", file = sys.stdout if _LOG_TO_STDOUT else sys.stderr)
+ print(
+ f"[llama-prebuilt] {message}", file = sys.stdout if _LOG_TO_STDOUT else sys.stderr
+ )
def log_lines(lines: Iterable[str]) -> None:
@@ -498,7 +506,9 @@ class _CrossHostAuthStrippingRedirectHandler(urllib.request.HTTPRedirectHandler)
def redirect_request(self, req, fp, code, msg, headers, newurl):
new_request = super().redirect_request(req, fp, code, msg, headers, newurl)
- if new_request is not None and parsed_hostname(newurl) != parsed_hostname(req.full_url):
+ if new_request is not None and parsed_hostname(newurl) != parsed_hostname(
+ req.full_url
+ ):
new_request.headers.pop("Authorization", None)
new_request.unredirected_hdrs.pop("Authorization", None)
return new_request
@@ -750,9 +760,9 @@ def refs_match(candidate_ref: str | None, requested_ref: str | None) -> bool:
candidate_commit = normalize_source_commit(candidate_ref)
requested_commit = normalize_source_commit(requested_ref)
if candidate_commit and requested_commit:
- return candidate_commit.startswith(requested_commit) or requested_commit.startswith(
- candidate_commit
- )
+ return candidate_commit.startswith(
+ requested_commit
+ ) or requested_commit.startswith(candidate_commit)
return False
@@ -856,7 +866,11 @@ class DownloadProgress:
self.last_emit = 0.0
term_ok = os.environ.get("TERM", "").lower() != "dumb"
self.stream = (
- sys.stderr if sys.stderr.isatty() else sys.stdout if sys.stdout.isatty() else sys.stderr
+ sys.stderr
+ if sys.stderr.isatty()
+ else sys.stdout
+ if sys.stdout.isatty()
+ else sys.stderr
)
self.is_tty = term_ok and self.stream.isatty()
self.completed = False
@@ -890,7 +904,10 @@ class DownloadProgress:
if self.is_tty:
elapsed = now - self.start_time
if not self.has_rendered_tty_progress:
- if self.total_bytes is not None and downloaded_bytes >= self.total_bytes:
+ if (
+ self.total_bytes is not None
+ and downloaded_bytes >= self.total_bytes
+ ):
return
if elapsed < TTY_PROGRESS_START_DELAY_SECONDS:
return
@@ -913,7 +930,10 @@ class DownloadProgress:
percent = int((downloaded_bytes * 100) / max(self.total_bytes, 1))
step = self.milestone_step
milestone_percent = min((percent // step) * step, 100)
- if milestone_percent > self.last_milestone_percent and milestone_percent < 100:
+ if (
+ milestone_percent > self.last_milestone_percent
+ and milestone_percent < 100
+ ):
self.last_milestone_percent = milestone_percent
should_emit = True
else:
@@ -966,7 +986,11 @@ def download_bytes(
content_length = response.headers.get("Content-Length")
if content_length and content_length.isdigit():
total_bytes = int(content_length)
- progress = DownloadProgress(progress_label, total_bytes) if progress_label else None
+ progress = (
+ DownloadProgress(progress_label, total_bytes)
+ if progress_label
+ else None
+ )
data = bytearray()
while True:
chunk = response.read(1024 * 1024)
@@ -996,13 +1020,17 @@ def fetch_json(url: str) -> Any:
data = download_bytes(
url,
timeout = 30,
- headers = github_api_headers(url) if is_github_api_url(url) else auth_headers(url),
+ headers = github_api_headers(url)
+ if is_github_api_url(url)
+ else auth_headers(url),
)
except urllib.error.HTTPError as exc:
if exc.code == 403 and is_github_api_url(url):
hint = ""
if not (os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN")):
- hint = "; set GH_TOKEN or GITHUB_TOKEN to avoid GitHub API rate limits"
+ hint = (
+ "; set GH_TOKEN or GITHUB_TOKEN to avoid GitHub API rate limits"
+ )
raise RuntimeError(f"GitHub API returned 403 for {url}{hint}") from exc
raise
if not data:
@@ -1011,7 +1039,9 @@ def fetch_json(url: str) -> Any:
try:
payload = json.loads(data.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
- last_decode_exc = RuntimeError(f"downloaded invalid JSON from {url}: {exc}")
+ last_decode_exc = RuntimeError(
+ f"downloaded invalid JSON from {url}: {exc}"
+ )
else:
if not isinstance(payload, dict) and not isinstance(payload, list):
raise RuntimeError(
@@ -1045,7 +1075,9 @@ def download_file(url: str, destination: Path) -> None:
content_length = response.headers.get("Content-Length")
if content_length and content_length.isdigit():
total_bytes = int(content_length)
- progress = DownloadProgress(f"Downloading {destination.name}", total_bytes)
+ progress = DownloadProgress(
+ f"Downloading {destination.name}", total_bytes
+ )
downloaded_bytes = 0
while True:
chunk = response.read(1024 * 1024)
@@ -1070,7 +1102,9 @@ def download_file(url: str, destination: Path) -> None:
pass
if attempt >= HTTP_FETCH_ATTEMPTS or not is_retryable_url_error(exc):
raise
- log(f"download failed ({attempt}/{HTTP_FETCH_ATTEMPTS}) for {url}: {exc}; retrying")
+ log(
+ f"download failed ({attempt}/{HTTP_FETCH_ATTEMPTS}) for {url}: {exc}; retrying"
+ )
sleep_backoff(attempt, exc = exc)
assert last_exc is not None
raise last_exc
@@ -1082,7 +1116,9 @@ def download_file_verified(
normalized_expected = normalize_sha256_digest(expected_sha256)
if not normalized_expected:
download_file(url, destination)
- log(f"downloaded {label} without a published sha256; relying on install validation")
+ log(
+ f"downloaded {label} without a published sha256; relying on install validation"
+ )
return
for attempt in range(1, 3):
@@ -1180,7 +1216,9 @@ def latest_upstream_release_tag() -> str:
payload = fetch_json(UPSTREAM_RELEASES_API)
tag = payload.get("tag_name")
if not isinstance(tag, str) or not tag:
- raise RuntimeError(f"latest release tag was missing from {UPSTREAM_RELEASES_API}")
+ raise RuntimeError(
+ f"latest release tag was missing from {UPSTREAM_RELEASES_API}"
+ )
return tag
@@ -1215,13 +1253,19 @@ def iter_release_payloads_by_time(
yield github_release(repo, published_release_tag)
return
- if requested_tag and requested_tag != "latest" and is_release_tag_like(requested_tag):
+ if (
+ requested_tag
+ and requested_tag != "latest"
+ and is_release_tag_like(requested_tag)
+ ):
try:
yield github_release(repo, requested_tag)
return
except urllib.error.HTTPError as exc:
if exc.code == 404:
- log(f"release tag {requested_tag} not found in {repo}; scanning recent releases")
+ log(
+ f"release tag {requested_tag} not found in {repo}; scanning recent releases"
+ )
else:
raise
except Exception:
@@ -1229,15 +1273,21 @@ def iter_release_payloads_by_time(
releases = [
release
- for release in github_releases(repo, max_pages = DEFAULT_GITHUB_RELEASE_SCAN_MAX_PAGES)
- if isinstance(release, dict) and not release.get("draft") and not release.get("prerelease")
+ for release in github_releases(
+ repo, max_pages = DEFAULT_GITHUB_RELEASE_SCAN_MAX_PAGES
+ )
+ if isinstance(release, dict)
+ and not release.get("draft")
+ and not release.get("prerelease")
]
releases.sort(key = release_time_sort_key, reverse = True)
for release in releases:
yield release
-def direct_release_matches_request(*, release_tag: str, llama_tag: str, requested_tag: str) -> bool:
+def direct_release_matches_request(
+ *, release_tag: str, llama_tag: str, requested_tag: str
+) -> bool:
if requested_tag == "latest":
return True
for candidate in (release_tag, llama_tag):
@@ -1519,7 +1569,12 @@ def direct_upstream_release_plan(
install_kind = "macos-x64",
)
)
- elif host.is_linux and host.is_x86_64 and not host.has_usable_nvidia and not host.has_rocm:
+ elif (
+ host.is_linux
+ and host.is_x86_64
+ and not host.has_usable_nvidia
+ and not host.has_rocm
+ ):
# ROCm hosts are excluded: this ggml-org path ships no per-gfx ROCm
# asset, so they fall through to the empty-attempts raise (HIP source
# build) rather than silently getting a CPU binary on a GPU host.
@@ -1609,7 +1664,9 @@ def resolve_simple_install_release_plans(
max_release_fallbacks = max_release_fallbacks,
)
requested_tag = normalized_requested_llama_tag(llama_tag)
- allow_older_release_fallback = requested_tag == "latest" and not published_release_tag
+ allow_older_release_fallback = (
+ requested_tag == "latest" and not published_release_tag
+ )
# macOS: pin the last upstream build that loads on a pre-26 host instead of
# fetching the latest (macOS 26 only) build and walking back release by
# release. No-op on macOS 26+, unknown version, non-macOS, and the fork.
@@ -1623,7 +1680,9 @@ def resolve_simple_install_release_plans(
last_error: PrebuiltFallback | None = None
try:
- releases = iter_release_payloads_by_time(repo, published_release_tag, requested_tag)
+ releases = iter_release_payloads_by_time(
+ repo, published_release_tag, requested_tag
+ )
for release in releases:
try:
plan = direct_upstream_release_plan(release, host, repo, requested_tag)
@@ -1646,13 +1705,17 @@ def resolve_simple_install_release_plans(
except PrebuiltFallback:
raise
except Exception as exc:
- raise PrebuiltFallback(f"failed to inspect published releases in {repo}: {exc}") from exc
+ raise PrebuiltFallback(
+ f"failed to inspect published releases in {repo}: {exc}"
+ ) from exc
if plans:
return requested_tag, plans
if last_error is not None:
raise last_error
- raise PrebuiltFallback(f"no installable published llama.cpp releases were found in {repo}")
+ raise PrebuiltFallback(
+ f"no installable published llama.cpp releases were found in {repo}"
+ )
def normalized_requested_llama_tag(requested_tag: str | None) -> str:
@@ -1704,7 +1767,9 @@ def parse_cuda_visible_devices(value: str | None) -> list[str] | None:
return [token.strip() for token in raw.split(",") if token.strip()]
-def supports_explicit_visible_device_matching(visible_devices: list[str] | None) -> bool:
+def supports_explicit_visible_device_matching(
+ visible_devices: list[str] | None,
+) -> bool:
if not visible_devices:
return False
for token in visible_devices:
@@ -1754,7 +1819,9 @@ def dir_provides_exact_library(directory: str | Path, library: str) -> bool:
return candidate.exists() and (candidate.is_file() or candidate.is_symlink())
-def linux_runtime_dirs_for_required_libraries(required_libraries: Iterable[str]) -> list[str]:
+def linux_runtime_dirs_for_required_libraries(
+ required_libraries: Iterable[str],
+) -> list[str]:
required = [library for library in required_libraries if library]
candidates: list[str | Path] = []
@@ -1770,7 +1837,9 @@ def linux_runtime_dirs_for_required_libraries(required_libraries: Iterable[str])
value = os.environ.get(name)
if value:
cuda_roots.append(Path(value))
- cuda_roots.extend(Path(path) for path in glob_paths("/usr/local/cuda", "/usr/local/cuda-*"))
+ cuda_roots.extend(
+ Path(path) for path in glob_paths("/usr/local/cuda", "/usr/local/cuda-*")
+ )
for root in cuda_roots:
candidates.extend(
@@ -1795,7 +1864,8 @@ def linux_runtime_dirs_for_required_libraries(required_libraries: Iterable[str])
)
)
candidates.extend(
- Path(path) for path in glob_paths("/usr/local/lib/ollama/cuda_v*", "/usr/lib/wsl/lib")
+ Path(path)
+ for path in glob_paths("/usr/local/lib/ollama/cuda_v*", "/usr/lib/wsl/lib")
)
candidates.extend(Path(path) for path in python_runtime_dirs())
candidates.extend(Path(path) for path in ldconfig_runtime_dirs(required))
@@ -1807,7 +1877,9 @@ def linux_runtime_dirs_for_required_libraries(required_libraries: Iterable[str])
matched: list[tuple[int, str]] = []
for directory in resolved:
base = Path(directory)
- provided = sum(1 for library in required if dir_provides_exact_library(directory, library))
+ provided = sum(
+ 1 for library in required if dir_provides_exact_library(directory, library)
+ )
if provided:
matched.append((provided, directory))
@@ -1828,7 +1900,9 @@ def detected_linux_runtime_lines() -> tuple[list[str], dict[str, list[str]]]:
matching_dirs: list[str] = []
for library in required:
matched_dirs = [
- directory for directory in dirs if any(Path(directory).glob(f"{library}*"))
+ directory
+ for directory in dirs
+ if any(Path(directory).glob(f"{library}*"))
]
if not matched_dirs:
library_matches = {}
@@ -1865,13 +1939,17 @@ def parse_published_artifact(raw: Any) -> PublishedLlamaArtifact | None:
if not isinstance(asset_name, str) or not asset_name:
raise ValueError("artifact.asset_name was missing or not a string")
if not isinstance(install_kind, str) or not install_kind:
- raise ValueError(f"artifact {asset_name} install_kind was missing or not a string")
+ raise ValueError(
+ f"artifact {asset_name} install_kind was missing or not a string"
+ )
supported_sms_raw = raw.get("supported_sms", [])
if not isinstance(supported_sms_raw, (list, tuple)):
raise ValueError(f"artifact {asset_name} supported_sms must be a list or tuple")
if any(not isinstance(value, (int, str)) for value in supported_sms_raw):
- raise ValueError(f"artifact {asset_name} supported_sms entries must be ints or strings")
+ raise ValueError(
+ f"artifact {asset_name} supported_sms entries must be ints or strings"
+ )
supported_sms = normalize_compute_caps(supported_sms_raw)
min_sm_raw = raw.get("min_sm")
@@ -1880,7 +1958,9 @@ def parse_published_artifact(raw: Any) -> PublishedLlamaArtifact | None:
min_sm = int(min_sm_raw) if min_sm_raw is not None else None
max_sm = int(max_sm_raw) if max_sm_raw is not None else None
except (TypeError, ValueError) as exc:
- raise ValueError(f"artifact {asset_name} min_sm/max_sm were not integers") from exc
+ raise ValueError(
+ f"artifact {asset_name} min_sm/max_sm were not integers"
+ ) from exc
runtime_line = raw.get("runtime_line")
coverage_class = raw.get("coverage_class")
bundle_profile = raw.get("bundle_profile")
@@ -1903,14 +1983,20 @@ def parse_published_artifact(raw: Any) -> PublishedLlamaArtifact | None:
)
mapped_raw = raw.get("mapped_targets", [])
mapped_targets = (
- [value.strip() for value in mapped_raw if isinstance(value, str) and value.strip()]
+ [
+ value.strip()
+ for value in mapped_raw
+ if isinstance(value, str) and value.strip()
+ ]
if isinstance(mapped_raw, (list, tuple))
else []
)
return PublishedLlamaArtifact(
asset_name = asset_name,
install_kind = install_kind,
- runtime_line = runtime_line if isinstance(runtime_line, str) and runtime_line else None,
+ runtime_line = runtime_line
+ if isinstance(runtime_line, str) and runtime_line
+ else None,
coverage_class = coverage_class
if isinstance(coverage_class, str) and coverage_class
else None,
@@ -1987,7 +2073,9 @@ def parse_published_release_bundle(
try:
artifact = parse_published_artifact(raw_artifact)
except ValueError as exc:
- log(f"published artifact ignored for {repo}@{release_tag} artifact[{index}]: {exc}")
+ log(
+ f"published artifact ignored for {repo}@{release_tag} artifact[{index}]: {exc}"
+ )
continue
if artifact is not None:
artifacts.append(artifact)
@@ -2006,7 +2094,9 @@ def parse_published_release_bundle(
release_tag = release_tag,
upstream_tag = upstream_tag,
manifest_sha256 = manifest_sha256,
- source_repo = source_repo if isinstance(source_repo, str) and source_repo else None,
+ source_repo = source_repo
+ if isinstance(source_repo, str) and source_repo
+ else None,
source_repo_url = source_repo_url
if isinstance(source_repo_url, str) and source_repo_url
else None,
@@ -2067,12 +2157,18 @@ def parse_approved_release_checksums(
artifacts: dict[str, ApprovedArtifactHash] = {}
for asset_name, raw_entry in artifacts_payload.items():
if not isinstance(asset_name, str) or not asset_name:
- raise RuntimeError("published checksum asset used a non-string artifact key")
+ raise RuntimeError(
+ "published checksum asset used a non-string artifact key"
+ )
if not isinstance(raw_entry, dict):
- raise RuntimeError(f"published checksum entry for {asset_name} was not an object")
+ raise RuntimeError(
+ f"published checksum entry for {asset_name} was not an object"
+ )
digest = normalize_sha256_digest(raw_entry.get("sha256"))
if not digest:
- raise RuntimeError(f"published checksum entry for {asset_name} omitted a valid sha256")
+ raise RuntimeError(
+ f"published checksum entry for {asset_name} omitted a valid sha256"
+ )
repo_value = raw_entry.get("repo")
kind_value = raw_entry.get("kind")
artifacts[asset_name] = ApprovedArtifactHash(
@@ -2093,7 +2189,9 @@ def parse_approved_release_checksums(
repo = repo,
release_tag = release_tag,
upstream_tag = upstream_tag,
- source_repo = source_repo if isinstance(source_repo, str) and source_repo else None,
+ source_repo = source_repo
+ if isinstance(source_repo, str) and source_repo
+ else None,
source_repo_url = source_repo_url
if isinstance(source_repo_url, str) and source_repo_url
else None,
@@ -2112,7 +2210,9 @@ def parse_approved_release_checksums(
)
-def load_approved_release_checksums(repo: str, release_tag: str) -> ApprovedReleaseChecksums:
+def load_approved_release_checksums(
+ repo: str, release_tag: str
+) -> ApprovedReleaseChecksums:
try:
release = github_release(repo, release_tag)
except Exception as exc:
@@ -2146,7 +2246,9 @@ def iter_published_release_bundles(
else github_releases(repo, max_pages = DEFAULT_GITHUB_RELEASE_SCAN_MAX_PAGES)
)
for release in releases:
- if not published_release_tag and (release.get("draft") or release.get("prerelease")):
+ if not published_release_tag and (
+ release.get("draft") or release.get("prerelease")
+ ):
continue
try:
bundle = parse_published_release_bundle(repo, release)
@@ -2159,13 +2261,18 @@ def iter_published_release_bundles(
yield bundle
-def _artifact_covers_sms(artifact: PublishedLlamaArtifact, host_sms: Iterable[str]) -> bool:
+def _artifact_covers_sms(
+ artifact: PublishedLlamaArtifact, host_sms: Iterable[str]
+) -> bool:
"""True when every host SM is listed in the artifact's supported_sms and
falls within its [min_sm, max_sm] range."""
if not artifact.supported_sms or artifact.min_sm is None or artifact.max_sm is None:
return False
supported = {str(value) for value in artifact.supported_sms}
- return all(sm in supported and artifact.min_sm <= int(sm) <= artifact.max_sm for sm in host_sms)
+ return all(
+ sm in supported and artifact.min_sm <= int(sm) <= artifact.max_sm
+ for sm in host_sms
+ )
def _sm_range(artifact: PublishedLlamaArtifact) -> int:
@@ -2222,9 +2329,13 @@ def linux_cuda_choice_from_release(
# below is arch-agnostic and applies to both.
cuda_install_kind = "linux-arm64-cuda" if host.is_arm64 else "linux-cuda"
published_artifacts = [
- artifact for artifact in release.artifacts if artifact.install_kind == cuda_install_kind
+ artifact
+ for artifact in release.artifacts
+ if artifact.install_kind == cuda_install_kind
]
- published_asset_names = sorted(artifact.asset_name for artifact in published_artifacts)
+ published_asset_names = sorted(
+ artifact.asset_name for artifact in published_artifacts
+ )
selection_log.append(
"linux_cuda_selection: published_assets="
+ (",".join(published_asset_names) if published_asset_names else "none")
@@ -2260,7 +2371,9 @@ def linux_cuda_choice_from_release(
attempts: list[AssetChoice] = []
seen_attempts: set[str] = set()
- def add_attempt(artifact: PublishedLlamaArtifact, asset_url: str, reason: str) -> None:
+ def add_attempt(
+ artifact: PublishedLlamaArtifact, asset_url: str, reason: str
+ ) -> None:
asset_name = artifact.asset_name
if asset_name in seen_attempts:
return
@@ -2297,7 +2410,9 @@ def linux_cuda_choice_from_release(
asset_name = artifact.asset_name
asset_url = release.assets.get(asset_name)
if not asset_url:
- selection_log.append(f"linux_cuda_selection: reject {asset_name} missing asset")
+ selection_log.append(
+ f"linux_cuda_selection: reject {asset_name} missing asset"
+ )
continue
if not host_sms and artifact.coverage_class != "portable":
selection_log.append(
@@ -2325,7 +2440,9 @@ def linux_cuda_choice_from_release(
supported_sms = {str(value) for value in artifact.supported_sms}
missing_sms = [sm for sm in host_sms if sm not in supported_sms]
out_of_range_sms = [
- sm for sm in host_sms if not (artifact.min_sm <= int(sm) <= artifact.max_sm)
+ sm
+ for sm in host_sms
+ if not (artifact.min_sm <= int(sm) <= artifact.max_sm)
]
reasons: list[str] = []
if missing_sms:
@@ -2369,7 +2486,8 @@ def linux_cuda_choice_from_release(
return None
selection_log.append(
- "linux_cuda_selection: attempt_order=" + ",".join(choice.name for choice in attempts)
+ "linux_cuda_selection: attempt_order="
+ + ",".join(choice.name for choice in attempts)
)
for attempt in attempts:
attempt.selection_log = list(selection_log) + [
@@ -2387,7 +2505,9 @@ def latest_published_linux_cuda_tag(host: HostInfo, published_repo: str) -> str
def iter_upstream_releases() -> Iterable[dict[str, Any]]:
- for release in github_releases(UPSTREAM_REPO, max_pages = DEFAULT_GITHUB_RELEASE_SCAN_MAX_PAGES):
+ for release in github_releases(
+ UPSTREAM_REPO, max_pages = DEFAULT_GITHUB_RELEASE_SCAN_MAX_PAGES
+ ):
if release.get("draft") or release.get("prerelease"):
continue
yield release
@@ -2431,7 +2551,9 @@ def validated_checksums_for_bundle(
return checksums
-def published_release_matches_request(bundle: PublishedReleaseBundle, requested_ref: str) -> bool:
+def published_release_matches_request(
+ bundle: PublishedReleaseBundle, requested_ref: str
+) -> bool:
if requested_ref == "latest":
return True
for candidate in (
@@ -2486,7 +2608,9 @@ def resolve_published_release(
raise PrebuiltFallback(
f"no usable published llama.cpp releases were available in {repo}"
)
- raise PrebuiltFallback(f"no published llama.cpp releases were available in {repo}")
+ raise PrebuiltFallback(
+ f"no published llama.cpp releases were available in {repo}"
+ )
raise PrebuiltFallback(
f"no published prebuilt release in {repo} matched upstream tag {normalized_requested}"
@@ -2545,7 +2669,9 @@ def iter_resolved_published_releases(
return
if normalized_requested == "latest":
- raise PrebuiltFallback(f"no published llama.cpp releases were available in {repo}")
+ raise PrebuiltFallback(
+ f"no published llama.cpp releases were available in {repo}"
+ )
raise PrebuiltFallback(
f"no published prebuilt release in {repo} matched upstream tag {normalized_requested}"
@@ -2603,10 +2729,14 @@ def resolve_requested_install_tag(
).bundle.upstream_tag
-def exact_source_archive_hash(checksums: ApprovedReleaseChecksums) -> ApprovedArtifactHash | None:
+def exact_source_archive_hash(
+ checksums: ApprovedReleaseChecksums,
+) -> ApprovedArtifactHash | None:
if not checksums.source_commit:
return None
- return checksums.artifacts.get(exact_source_archive_logical_name(checksums.source_commit))
+ return checksums.artifacts.get(
+ exact_source_archive_logical_name(checksums.source_commit)
+ )
def source_clone_url_for_release(
@@ -2626,8 +2756,12 @@ def source_build_plan_for_release(release: ResolvedPublishedRelease) -> SourceBu
exact_source = exact_source_archive_hash(checksums)
source_repo = checksums.source_repo or release.bundle.source_repo
source_repo_url = checksums.source_repo_url or release.bundle.source_repo_url
- requested_source_ref = checksums.requested_source_ref or release.bundle.requested_source_ref
- resolved_source_ref = checksums.resolved_source_ref or release.bundle.resolved_source_ref
+ requested_source_ref = (
+ checksums.requested_source_ref or release.bundle.requested_source_ref
+ )
+ resolved_source_ref = (
+ checksums.resolved_source_ref or release.bundle.resolved_source_ref
+ )
source_commit = checksums.source_commit or release.bundle.source_commit
source_ref_kind = checksums.source_ref_kind or release.bundle.source_ref_kind
source_url = source_clone_url_for_release(checksums, release.bundle)
@@ -2643,8 +2777,14 @@ def source_build_plan_for_release(release: ResolvedPublishedRelease) -> SourceBu
resolved_source_ref = resolved_source_ref,
source_commit = source_commit,
)
- source_ref = checkout_friendly_ref(source_ref_kind, resolved_source_ref or requested_source_ref)
- if source_url and source_ref and source_ref_kind in {"tag", "branch", "pull", "commit"}:
+ source_ref = checkout_friendly_ref(
+ source_ref_kind, resolved_source_ref or requested_source_ref
+ )
+ if (
+ source_url
+ and source_ref
+ and source_ref_kind in {"tag", "branch", "pull", "commit"}
+ ):
return SourceBuildPlan(
source_url = source_url,
source_ref = source_ref,
@@ -2837,7 +2977,9 @@ def detect_host() -> HostInfo:
# ROCm host as NVIDIA and short-circuit the ROCm path.
try:
listing = run_capture([nvidia_smi, "-L"], timeout = 20)
- gpu_lines = [line for line in listing.stdout.splitlines() if line.startswith("GPU ")]
+ gpu_lines = [
+ line for line in listing.stdout.splitlines() if line.startswith("GPU ")
+ ]
if gpu_lines:
has_physical_nvidia = True
has_usable_nvidia = visible_device_tokens != []
@@ -2995,7 +3137,9 @@ def detect_host() -> HostInfo:
# AMD torch wheels ship hipInfo.exe into the venv Scripts dir
# (next to python.exe) -- resolvable on driver-only hosts where no
# SDK dir exists, so a standalone rerun can still detect the GPU.
- _venv_candidate = os.path.join(os.path.dirname(sys.executable), f"{name}.exe")
+ _venv_candidate = os.path.join(
+ os.path.dirname(sys.executable), f"{name}.exe"
+ )
if os.path.isfile(_venv_candidate):
return _venv_candidate
return None
@@ -3082,7 +3226,9 @@ def _apply_host_overrides(
return host
-def published_repo_for_host(host: HostInfo, *, linux_amd_tooling_present: bool = False) -> str:
+def published_repo_for_host(
+ host: HostInfo, *, linux_amd_tooling_present: bool = False
+) -> str:
"""The release repo setup.sh / setup.ps1 pick for this host: macOS always the
fork (ggml-org macOS bundles need too-new macOS); else CPU-only Linux/Windows
-> ggml-org upstream (the fork ships no CPU bundle) and any usable GPU (NVIDIA
@@ -3092,7 +3238,9 @@ def published_repo_for_host(host: HostInfo, *, linux_amd_tooling_present: bool =
if host.is_macos:
return DEFAULT_PUBLISHED_REPO
has_gpu = (
- host.has_usable_nvidia or host.has_rocm or (host.is_linux and linux_amd_tooling_present)
+ host.has_usable_nvidia
+ or host.has_rocm
+ or (host.is_linux and linux_amd_tooling_present)
)
return DEFAULT_PUBLISHED_REPO if has_gpu else UPSTREAM_REPO
@@ -3194,7 +3342,9 @@ def detect_torch_cuda_runtime_preference(host: HostInfo) -> CudaRuntimePreferenc
try:
cuda_available = bool(torch.cuda.is_available())
except Exception as exc:
- selection_log.append(f"torch_cuda_preference: torch.cuda.is_available() failed: {exc}")
+ selection_log.append(
+ f"torch_cuda_preference: torch.cuda.is_available() failed: {exc}"
+ )
return CudaRuntimePreference(runtime_line = None, selection_log = selection_log)
if not cuda_available:
@@ -3282,10 +3432,14 @@ def windows_cuda_attempts(
f"{preferred_runtime_line} unavailable_or_incompatible"
)
else:
- selection_log.append("windows_cuda_selection: no Torch runtime preference available")
+ selection_log.append(
+ "windows_cuda_selection: no Torch runtime preference available"
+ )
runtime_order.extend(
- runtime_line for runtime_line in normal_runtime_lines if runtime_line not in runtime_order
+ runtime_line
+ for runtime_line in normal_runtime_lines
+ if runtime_line not in runtime_order
)
# Keep every driver-compatible line reachable as a fallback, so a line gated
# out by the driver version still drops to an older major (cuda13 -> cuda12).
@@ -3309,7 +3463,9 @@ def windows_cuda_attempts(
# Track whatever minor llama.cpp actually ships for this major
# (cuda13 -> 13.1, 13.3, ...). Skip the line when the release has no
# matching asset instead of guessing a now-missing name.
- runtime = _published_windows_cuda_runtime(upstream_assets, major, host.driver_cuda_version)
+ runtime = _published_windows_cuda_runtime(
+ upstream_assets, major, host.driver_cuda_version
+ )
if runtime is None:
selection_log.append(
f"windows_cuda_selection: no driver-supported asset for {runtime_line}"
@@ -3405,7 +3561,8 @@ def _drop_blackwell_incapable_windows_cuda(
return [
attempt
for attempt in attempts
- if attempt.install_kind != "windows-cuda" or _windows_cuda_attempt_covers_blackwell(attempt)
+ if attempt.install_kind != "windows-cuda"
+ or _windows_cuda_attempt_covers_blackwell(attempt)
]
@@ -3430,7 +3587,10 @@ def _pinned_windows_cuda_fallback(
caps = normalize_compute_caps(host.compute_caps)
if not caps or int(caps[-1]) < _BLACKWELL_MIN_SM:
return None
- if any(_windows_cuda_attempt_covers_blackwell(attempt) for attempt in existing_cuda_attempts):
+ if any(
+ _windows_cuda_attempt_covers_blackwell(attempt)
+ for attempt in existing_cuda_attempts
+ ):
return None
tag = _PINNED_BLACKWELL_FALLBACK_TAG
runtime = _PINNED_BLACKWELL_FALLBACK_RUNTIME
@@ -3505,7 +3665,9 @@ def published_windows_cuda_attempts(
) -> list[AssetChoice]:
selection_log = list(release.selection_log) + list(selection_preamble)
published_artifacts = [
- artifact for artifact in release.artifacts if artifact.install_kind == "windows-cuda"
+ artifact
+ for artifact in release.artifacts
+ if artifact.install_kind == "windows-cuda"
]
artifacts_by_runtime: dict[str, list[PublishedLlamaArtifact]] = {}
for artifact in published_artifacts:
@@ -3549,7 +3711,9 @@ def published_windows_cuda_attempts(
# the driver major is the real constraint. Mirrors the legacy
# windows_cuda_attempts fallback; without it a torch-only host gets no
# fork attempt and silently drops to the upstream build.
- ordered_lines = [line for line in compatible if line in detected] or list(compatible)
+ ordered_lines = [line for line in compatible if line in detected] or list(
+ compatible
+ )
if preferred_runtime_line and preferred_runtime_line in ordered_lines:
ordered_lines = [preferred_runtime_line] + [
line for line in ordered_lines if line != preferred_runtime_line
@@ -3594,7 +3758,11 @@ def published_windows_cuda_attempts(
and artifact.min_sm is not None
and artifact.max_sm is not None
)
- if host_sms and has_sm_info and not _artifact_covers_sms(artifact, host_sms):
+ if (
+ host_sms
+ and has_sm_info
+ and not _artifact_covers_sms(artifact, host_sms)
+ ):
continue
if not host_sms and has_sm_info and artifact.coverage_class != "portable":
continue
@@ -3690,7 +3858,11 @@ def published_asset_choice_for_kind(
release: PublishedReleaseBundle, install_kind: str
) -> AssetChoice | None:
candidates = sorted(
- (artifact for artifact in release.artifacts if artifact.install_kind == install_kind),
+ (
+ artifact
+ for artifact in release.artifacts
+ if artifact.install_kind == install_kind
+ ),
key = lambda artifact: (artifact.rank, artifact.asset_name),
)
for artifact in candidates:
@@ -3706,7 +3878,9 @@ def published_asset_choice_for_kind(
install_kind = install_kind,
runtime_line = artifact.runtime_line,
selection_log = list(release.selection_log)
- + [f"published_selection: selected {artifact.asset_name} install_kind={install_kind}"],
+ + [
+ f"published_selection: selected {artifact.asset_name} install_kind={install_kind}"
+ ],
)
return None
@@ -3759,7 +3933,11 @@ def _detect_host_rocm_version() -> tuple[int, int] | None:
if result.returncode == 0:
raw = (result.stdout or "").strip().split("\n")[0]
parts = raw.split(".")
- if len(parts) >= 2 and parts[0].isdigit() and parts[1].split("-")[0].isdigit():
+ if (
+ len(parts) >= 2
+ and parts[0].isdigit()
+ and parts[1].split("-")[0].isdigit()
+ ):
return int(parts[0]), int(parts[1].split("-")[0])
except Exception:
pass
@@ -3873,7 +4051,9 @@ def resolve_upstream_asset_choice(host: HostInfo, llama_tag: str) -> AssetChoice
_compatible: list[tuple[tuple[int, ...], str]] = rocm_candidates
if _host_rocm_version is not None:
_compatible = [
- item for item in rocm_candidates if item[0][:2] <= _host_rocm_version
+ item
+ for item in rocm_candidates
+ if item[0][:2] <= _host_rocm_version
]
if rocm_candidates and not _compatible:
# Fall back to the newest candidate so a source build is
@@ -3931,7 +4111,9 @@ def resolve_upstream_asset_choice(host: HostInfo, llama_tag: str) -> AssetChoice
if host.has_rocm:
hip_name = f"llama-{llama_tag}-bin-win-hip-radeon-x64.zip"
if hip_name in upstream_assets:
- log(f"AMD ROCm detected on Windows -- trying upstream HIP prebuilt {hip_name}")
+ log(
+ f"AMD ROCm detected on Windows -- trying upstream HIP prebuilt {hip_name}"
+ )
return AssetChoice(
repo = UPSTREAM_REPO,
tag = llama_tag,
@@ -3940,7 +4122,9 @@ def resolve_upstream_asset_choice(host: HostInfo, llama_tag: str) -> AssetChoice
source_label = "upstream",
install_kind = "windows-hip",
)
- log("AMD ROCm detected on Windows but no HIP prebuilt found -- falling back to CPU")
+ log(
+ "AMD ROCm detected on Windows but no HIP prebuilt found -- falling back to CPU"
+ )
upstream_name = f"llama-{llama_tag}-bin-win-cpu-x64.zip"
if upstream_name not in upstream_assets:
@@ -3980,7 +4164,9 @@ def resolve_upstream_asset_choice(host: HostInfo, llama_tag: str) -> AssetChoice
install_kind = "macos-x64",
)
- raise PrebuiltFallback(f"no prebuilt policy exists for {host.system} {host.machine}")
+ raise PrebuiltFallback(
+ f"no prebuilt policy exists for {host.system} {host.machine}"
+ )
def resolve_asset_choice(host: HostInfo, llama_tag: str) -> AssetChoice:
@@ -4034,7 +4220,9 @@ def resolve_release_asset_choice(
# prebuilt. We still avoid hard-pinning windows-cpu here so a CPU bundle
# never shadows that ROCm path.
if host.has_rocm:
- published_choice = published_rocm_choice_for_host(release, host, "windows-rocm")
+ published_choice = published_rocm_choice_for_host(
+ release, host, "windows-rocm"
+ )
else:
published_choice = published_asset_choice_for_kind(release, "windows-cpu")
elif host.is_macos and host.is_arm64:
@@ -4062,14 +4250,18 @@ def extract_archive(archive_path: Path, destination: Path) -> None:
normalized = member_name.replace("\\", "/")
member_path = Path(normalized)
if member_path.is_absolute():
- raise PrebuiltFallback(f"archive member used an absolute path: {member_name}")
+ raise PrebuiltFallback(
+ f"archive member used an absolute path: {member_name}"
+ )
target = (base / member_path).resolve()
base_resolved = base.resolve()
try:
target.relative_to(base_resolved)
except ValueError as exc:
- raise PrebuiltFallback(f"archive member escaped destination: {member_name}") from exc
+ raise PrebuiltFallback(
+ f"archive member escaped destination: {member_name}"
+ ) from exc
return target
def _try_repair_missing_slash(
@@ -4115,7 +4307,11 @@ def extract_archive(archive_path: Path, destination: Path) -> None:
return candidates[0][len(prefix) :]
def safe_link_target(
- base: Path, member_name: str, link_name: str, target: Path, archive_names: set[str]
+ base: Path,
+ member_name: str,
+ link_name: str,
+ target: Path,
+ archive_names: set[str],
) -> tuple[str, Path]:
normalized = link_name.replace("\\", "/")
repaired = _try_repair_missing_slash(member_name, normalized, archive_names)
@@ -4175,7 +4371,9 @@ def extract_archive(archive_path: Path, destination: Path) -> None:
target.parent.mkdir(parents = True, exist_ok = True)
extracted = archive.extractfile(member)
if extracted is None:
- raise PrebuiltFallback(f"tar archive entry could not be read: {member.name}")
+ raise PrebuiltFallback(
+ f"tar archive entry could not be read: {member.name}"
+ )
with extracted, target.open("wb") as dst:
shutil.copyfileobj(extracted, dst)
@@ -4209,7 +4407,9 @@ def extract_archive(archive_path: Path, destination: Path) -> None:
details = ", ".join(
f"{member.name} -> {member.linkname}" for member, _ in next_round
)
- raise PrebuiltFallback(f"tar archive contained unresolved link entries: {details}")
+ raise PrebuiltFallback(
+ f"tar archive contained unresolved link entries: {details}"
+ )
unresolved = next_round
destination.mkdir(parents = True, exist_ok = True)
@@ -4392,7 +4592,9 @@ def hydrate_source_tree(
for index, source_url in enumerate(source_urls):
try:
if index > 0:
- log(f"retrying source tree download from fallback URL: {source_url}")
+ log(
+ f"retrying source tree download from fallback URL: {source_url}"
+ )
download_file_verified(
source_url,
archive_path,
@@ -4417,11 +4619,14 @@ def hydrate_source_tree(
source_root / "gguf-py",
]
missing = [
- str(path.relative_to(source_root)) for path in required_paths if not path.exists()
+ str(path.relative_to(source_root))
+ for path in required_paths
+ if not path.exists()
]
if missing:
raise PrebuiltFallback(
- "upstream source archive was missing required repo files: " + ", ".join(missing)
+ "upstream source archive was missing required repo files: "
+ + ", ".join(missing)
)
copy_directory_contents(source_root, install_dir)
except PrebuiltFallback:
@@ -4448,7 +4653,9 @@ def discover_installed_executable(install_dir: Path, executable_name: str) -> Pa
direct = install_dir / executable_name
if direct.exists() and direct.is_file():
return direct
- candidate = next((path for path in install_dir.rglob(executable_name) if path.is_file()), None)
+ candidate = next(
+ (path for path in install_dir.rglob(executable_name) if path.is_file()), None
+ )
if candidate is None:
raise PrebuiltFallback(f"{executable_name} was not installed")
return candidate
@@ -4478,7 +4685,9 @@ def create_exec_entrypoint(entrypoint: Path, target: Path) -> None:
write_exec_wrapper(entrypoint, target)
-def overlay_directory_for_choice(install_dir: Path, choice: AssetChoice, host: HostInfo) -> Path:
+def overlay_directory_for_choice(
+ install_dir: Path, choice: AssetChoice, host: HostInfo
+) -> Path:
if host.is_windows or choice.install_kind.startswith("windows"):
path = install_dir / "build" / "bin" / "Release"
else:
@@ -4514,7 +4723,12 @@ def runtime_patterns_for_choice(choice: AssetChoice) -> list[str]:
"linux-rocm",
"linux-arm64",
}:
- return ["llama-server", "llama-quantize", "llama-diffusion-gemma-visual-server", "lib*.so*"]
+ return [
+ "llama-server",
+ "llama-quantize",
+ "llama-diffusion-gemma-visual-server",
+ "lib*.so*",
+ ]
if choice.install_kind in {"macos-arm64", "macos-x64"}:
return [
"llama-server",
@@ -4535,7 +4749,9 @@ def runtime_patterns_for_choice(choice: AssetChoice) -> list[str]:
"llama-diffusion-gemma-visual-server.exe",
"*.dll",
]
- raise PrebuiltFallback(f"unsupported install kind for runtime overlay: {choice.install_kind}")
+ raise PrebuiltFallback(
+ f"unsupported install kind for runtime overlay: {choice.install_kind}"
+ )
def runtime_subdirs_for_choice(choice: AssetChoice) -> list[str]:
@@ -4741,7 +4957,9 @@ def confirm_install_tree(install_dir: Path, host: HostInfo) -> None:
expected.append(install_dir / "UNSLOTH_PREBUILT_INFO.json")
missing = [str(path) for path in expected if not path.exists()]
if missing:
- raise RuntimeError("activated install was missing expected files: " + ", ".join(missing))
+ raise RuntimeError(
+ "activated install was missing expected files: " + ", ".join(missing)
+ )
def activate_staged_dir(staging_dir: Path, dst: Path) -> None:
@@ -4768,7 +4986,9 @@ def activate_staged_dir(staging_dir: Path, dst: Path) -> None:
except OSError as exc:
if not is_busy_lock_error(exc):
raise
- log(f"os.replace failed ({exc!r}); falling back to file-by-file copy of staging tree")
+ log(
+ f"os.replace failed ({exc!r}); falling back to file-by-file copy of staging tree"
+ )
shutil.copytree(staging_dir, dst, dirs_exist_ok = True)
remove_tree(staging_dir)
@@ -4893,11 +5113,15 @@ def install_from_archives(
expected_sha256 = choice.runtime_sha256,
label = f"prebuilt runtime archive {choice.runtime_name}",
)
- runtime_extract_dir = Path(tempfile.mkdtemp(prefix = "extract-runtime-", dir = work_dir))
+ runtime_extract_dir = Path(
+ tempfile.mkdtemp(prefix = "extract-runtime-", dir = work_dir)
+ )
extract_archive(runtime_archive, runtime_extract_dir)
source_dir = extract_dir
overlay_dir = overlay_directory_for_choice(install_dir, choice, host)
- copy_globs(source_dir, overlay_dir, runtime_patterns_for_choice(choice), required = True)
+ copy_globs(
+ source_dir, overlay_dir, runtime_patterns_for_choice(choice), required = True
+ )
for _subdir in runtime_subdirs_for_choice(choice):
_src_subdir = source_dir / _subdir
if _src_subdir.is_dir():
@@ -4942,7 +5166,9 @@ def install_from_archives(
source_server = build_bin / "llama-server"
source_quantize = build_bin / "llama-quantize"
if not source_server.exists() or not source_quantize.exists():
- raise PrebuiltFallback("unix executables were not installed correctly into build/bin")
+ raise PrebuiltFallback(
+ "unix executables were not installed correctly into build/bin"
+ )
os.chmod(source_server, 0o755)
os.chmod(source_quantize, 0o755)
@@ -4968,9 +5194,13 @@ def ensure_repo_shape(install_dir: Path) -> None:
install_dir / "convert_hf_to_gguf.py",
install_dir / "gguf-py",
]
- missing = [str(path.relative_to(install_dir)) for path in required if not path.exists()]
+ missing = [
+ str(path.relative_to(install_dir)) for path in required if not path.exists()
+ ]
if missing:
- raise PrebuiltFallback("hydrated llama.cpp source tree was missing: " + ", ".join(missing))
+ raise PrebuiltFallback(
+ "hydrated llama.cpp source tree was missing: " + ", ".join(missing)
+ )
def validation_model_cache_path(install_dir: Path) -> Path:
@@ -5015,7 +5245,9 @@ def _fetch_validation_model_bytes() -> bytes:
repo_id, revision, filename = parts
try:
from huggingface_hub import hf_hub_download
- local = hf_hub_download(repo_id = repo_id, filename = filename, revision = revision)
+ local = hf_hub_download(
+ repo_id = repo_id, filename = filename, revision = revision
+ )
return validated_validation_model_bytes(Path(local).read_bytes())
except Exception as exc:
log(
@@ -5038,7 +5270,9 @@ def download_validation_model(path: Path, cache_path: Path | None = None) -> Non
data = validated_validation_model_bytes(cache_path.read_bytes())
log(f"using cached tiny GGUF validation model from {cache_path}")
except Exception as exc:
- log(f"cached tiny GGUF validation model was invalid; refreshing cache ({exc})")
+ log(
+ f"cached tiny GGUF validation model was invalid; refreshing cache ({exc})"
+ )
data = None
if data is None:
log("downloading tiny GGUF validation model")
@@ -5122,7 +5356,9 @@ def dedupe_existing_dirs(paths: Iterable[str | Path]) -> list[str]:
return unique
-def linux_missing_libraries(binary_path: Path, *, env: dict[str, str] | None = None) -> list[str]:
+def linux_missing_libraries(
+ binary_path: Path, *, env: dict[str, str] | None = None
+) -> list[str]:
try:
result = run_capture(["ldd", str(binary_path)], timeout = 20, env = env)
except Exception:
@@ -5279,7 +5515,9 @@ def _macho_slice_minos(data: bytes, offset: int) -> tuple[int, int] | None:
return None
-def macho_minimum_macos(path: Path, host: HostInfo | None = None) -> tuple[int, int] | None:
+def macho_minimum_macos(
+ path: Path, host: HostInfo | None = None
+) -> tuple[int, int] | None:
"""Minimum macOS (major, minor) a Mach-O binary or dylib requires.
Pure-Python so it works on consumer Macs without the Xcode command line
@@ -5318,7 +5556,9 @@ def macho_minimum_macos(path: Path, host: HostInfo | None = None) -> tuple[int,
return None
if host is not None:
want = (
- _CPU_TYPE_ARM64 if host.is_arm64 else (_CPU_TYPE_X86_64 if host.is_x86_64 else None)
+ _CPU_TYPE_ARM64
+ if host.is_arm64
+ else (_CPU_TYPE_X86_64 if host.is_x86_64 else None)
)
for cputype, minos in slices:
if cputype == want:
@@ -5379,7 +5619,8 @@ def preflight_macos_installed_binaries(
issues = macos_binary_minos_issues(binaries, install_dir, host)
if issues:
raise PrebuiltFallback(
- "macos prebuilt requires a newer macOS than this host:\n" + "\n".join(issues)
+ "macos prebuilt requires a newer macOS than this host:\n"
+ + "\n".join(issues)
)
@@ -5395,14 +5636,18 @@ def preflight_linux_installed_binaries(
missing = linux_missing_libraries(binary_path, env = env)
if not missing:
continue
- runtime_dirs = [part for part in env.get("LD_LIBRARY_PATH", "").split(os.pathsep) if part]
+ runtime_dirs = [
+ part for part in env.get("LD_LIBRARY_PATH", "").split(os.pathsep) if part
+ ]
issues.append(
f"{binary_path.name}: missing={','.join(missing)} "
f"ld_library_path={','.join(runtime_dirs) if runtime_dirs else 'none'}"
)
if issues:
- raise PrebuiltFallback("linux extracted binary preflight failed:\n" + "\n".join(issues))
+ raise PrebuiltFallback(
+ "linux extracted binary preflight failed:\n" + "\n".join(issues)
+ )
def glob_paths(*patterns: str) -> list[str]:
@@ -5448,7 +5693,9 @@ def windows_runtime_dirs() -> list[str]:
def windows_runtime_dirs_for_patterns(
required_patterns: Iterable[str], candidate_dirs: Iterable[str] | None = None
) -> list[str]:
- directories = list(candidate_dirs) if candidate_dirs is not None else windows_runtime_dirs()
+ directories = (
+ list(candidate_dirs) if candidate_dirs is not None else windows_runtime_dirs()
+ )
matching_dirs: list[str] = []
for pattern in required_patterns:
matched_dirs = [
@@ -5524,12 +5771,20 @@ def binary_env(
if _wsl_rocm:
ld_dirs = [*_wsl_rocm, *ld_dirs]
env.setdefault("HSA_ENABLE_DXG_DETECTION", "1")
- existing = [part for part in env.get("LD_LIBRARY_PATH", "").split(os.pathsep) if part]
- env["LD_LIBRARY_PATH"] = os.pathsep.join(dedupe_existing_dirs([*ld_dirs, *existing]))
+ existing = [
+ part for part in env.get("LD_LIBRARY_PATH", "").split(os.pathsep) if part
+ ]
+ env["LD_LIBRARY_PATH"] = os.pathsep.join(
+ dedupe_existing_dirs([*ld_dirs, *existing])
+ )
elif host.is_macos:
dyld_dirs = [str(binary_path.parent), str(install_dir)]
- existing = [part for part in env.get("DYLD_LIBRARY_PATH", "").split(os.pathsep) if part]
- env["DYLD_LIBRARY_PATH"] = os.pathsep.join(dedupe_existing_dirs([*dyld_dirs, *existing]))
+ existing = [
+ part for part in env.get("DYLD_LIBRARY_PATH", "").split(os.pathsep) if part
+ ]
+ env["DYLD_LIBRARY_PATH"] = os.pathsep.join(
+ dedupe_existing_dirs([*dyld_dirs, *existing])
+ )
return env
@@ -5551,7 +5806,11 @@ def validate_quantize(
env = binary_env(quantize_path, install_dir, host, runtime_line = runtime_line),
**windows_hidden_subprocess_kwargs(),
)
- if result.returncode != 0 or not quantized_path.exists() or quantized_path.stat().st_size == 0:
+ if (
+ result.returncode != 0
+ or not quantized_path.exists()
+ or quantized_path.stat().st_size == 0
+ ):
combined = result.stdout + ("\n" + result.stderr if result.stderr else "")
# Backstop for prebuilts the static minos scan could not read: a dyld
# "built for macOS N" / missing Metal symbol failure means this binary
@@ -5561,7 +5820,9 @@ def validate_quantize(
if looks_like_macos_incompatibility(combined)
else ""
)
- raise PrebuiltFallback(prefix + "llama-quantize validation failed:\n" + combined)
+ raise PrebuiltFallback(
+ prefix + "llama-quantize validation failed:\n" + combined
+ )
def validate_server(
@@ -5619,7 +5880,9 @@ def validate_server(
# is exercised against the actual hardware rather than the
# CPU fallback. NVIDIA and macOS-arm64 are already covered.
_enable_gpu_layers = (
- host.has_usable_nvidia or host.has_rocm or (host.is_macos and host.is_arm64)
+ host.has_usable_nvidia
+ or host.has_rocm
+ or (host.is_macos and host.is_arm64)
)
if _enable_gpu_layers:
command.extend(["--n-gpu-layers", "1"])
@@ -5635,7 +5898,9 @@ def validate_server(
stdout = log_handle,
stderr = subprocess.STDOUT,
text = True,
- env = binary_env(server_path, install_dir, host, runtime_line = runtime_line),
+ env = binary_env(
+ server_path, install_dir, host, runtime_line = runtime_line
+ ),
**windows_hidden_subprocess_kwargs(),
)
deadline = time.time() + 60
@@ -5650,7 +5915,9 @@ def validate_server(
exited_quickly = (
time.time() - startup_started
) <= SERVER_BIND_RETRY_WINDOW_SECONDS
- failure = PrebuiltFallback("llama-server exited during startup:\n" + output)
+ failure = PrebuiltFallback(
+ "llama-server exited during startup:\n" + output
+ )
if (
port_attempt < SERVER_PORT_BIND_ATTEMPTS
and is_retryable_server_bind_error(
@@ -5667,7 +5934,9 @@ def validate_server(
break
raise failure
- payload = json.dumps({"prompt": "a", "n_predict": 1}).encode("utf-8")
+ payload = json.dumps({"prompt": "a", "n_predict": 1}).encode(
+ "utf-8"
+ )
request = urllib.request.Request(
f"http://127.0.0.1:{port}/completion",
data = payload,
@@ -5679,7 +5948,9 @@ def validate_server(
response_body = response.read().decode("utf-8", "replace")
if status_code == 200:
return
- last_error = RuntimeError(f"unexpected HTTP status {status_code}")
+ last_error = RuntimeError(
+ f"unexpected HTTP status {status_code}"
+ )
except urllib.error.HTTPError as exc:
response_body = exc.read().decode("utf-8", "replace")
last_error = exc
@@ -5713,7 +5984,9 @@ def validate_server(
raise PrebuiltFallback("llama-server validation failed unexpectedly")
-def collect_system_report(host: HostInfo, choice: AssetChoice | None, install_dir: Path) -> str:
+def collect_system_report(
+ host: HostInfo, choice: AssetChoice | None, install_dir: Path
+) -> str:
lines = [
f"platform={host.system} machine={host.machine}",
f"driver_cuda_version={host.driver_cuda_version}",
@@ -5727,7 +6000,8 @@ def collect_system_report(host: HostInfo, choice: AssetChoice | None, install_di
if host.is_linux and host.has_physical_nvidia:
runtime_lines, runtime_dirs = detected_linux_runtime_lines()
lines.append(
- "linux_runtime_lines=" + (",".join(runtime_lines) if runtime_lines else "none")
+ "linux_runtime_lines="
+ + (",".join(runtime_lines) if runtime_lines else "none")
)
for runtime_line in ("cuda13", "cuda12"):
lines.append(
@@ -5756,7 +6030,10 @@ def collect_system_report(host: HostInfo, choice: AssetChoice | None, install_di
server_env = binary_env(server_binary, install_dir, host)
lines.append(
"linux_missing_libs="
- + (",".join(linux_missing_libraries(server_binary, env = server_env)) or "none")
+ + (
+ ",".join(linux_missing_libraries(server_binary, env = server_env))
+ or "none"
+ )
)
lines.append(
"linux_runtime_dirs="
@@ -5764,7 +6041,9 @@ def collect_system_report(host: HostInfo, choice: AssetChoice | None, install_di
",".join(
[
part
- for part in server_env.get("LD_LIBRARY_PATH", "").split(os.pathsep)
+ for part in server_env.get("LD_LIBRARY_PATH", "").split(
+ os.pathsep
+ )
if part
]
)
@@ -5772,16 +6051,21 @@ def collect_system_report(host: HostInfo, choice: AssetChoice | None, install_di
)
)
try:
- ldd = run_capture(["ldd", str(server_binary)], timeout = 20, env = server_env)
+ ldd = run_capture(
+ ["ldd", str(server_binary)], timeout = 20, env = server_env
+ )
lines.append("ldd llama-server:")
lines.append((ldd.stdout + ldd.stderr).strip())
except Exception as exc:
lines.append(f"ldd error: {exc}")
elif host.is_windows:
- lines.append("windows_runtime_dirs=" + (",".join(windows_runtime_dirs()) or "none"))
+ lines.append(
+ "windows_runtime_dirs=" + (",".join(windows_runtime_dirs()) or "none")
+ )
runtime_lines, runtime_dirs = detected_windows_runtime_lines()
lines.append(
- "windows_runtime_lines=" + (",".join(runtime_lines) if runtime_lines else "none")
+ "windows_runtime_lines="
+ + (",".join(runtime_lines) if runtime_lines else "none")
)
for runtime_line in ("cuda13", "cuda12"):
lines.append(
@@ -5930,7 +6214,9 @@ def resolve_install_attempts(
return requested_tag, plan.llama_tag, plan.attempts, plan.approved_checksums
-def _linux_published_attempts(host: HostInfo, bundle: PublishedReleaseBundle) -> list[AssetChoice]:
+def _linux_published_attempts(
+ host: HostInfo, bundle: PublishedReleaseBundle
+) -> list[AssetChoice]:
"""Build the install attempts for a fork Linux host from a manifest-described
bundle: CUDA (with a CPU fallback), per-gfx ROCm, or CPU. Same selection the
upstream filename path used, just sourced from the manifest instead of
@@ -5978,11 +6264,17 @@ def _fork_manifest_release_plans(
llama-prebuilt-manifest.json rather than in the filename: arm64 CUDA, Windows
CUDA, per-gfx ROCm, and macOS. Linux x64 takes the faster filename path."""
requested_tag = normalized_requested_llama_tag(llama_tag)
- allow_older_release_fallback = requested_tag == "latest" and not published_release_tag
+ allow_older_release_fallback = (
+ requested_tag == "latest" and not published_release_tag
+ )
release_limit = max(1, max_release_fallbacks)
# macOS may need to walk past a run of too-new prebuilts. Only when the host
# version is known; otherwise keep the default (cannot tell up front).
- if host.is_macos and allow_older_release_fallback and host.macos_version is not None:
+ if (
+ host.is_macos
+ and allow_older_release_fallback
+ and host.macos_version is not None
+ ):
release_limit = max(release_limit, DEFAULT_MAX_MACOS_RELEASE_FALLBACKS)
plans: list[InstallReleasePlan] = []
last_error: PrebuiltFallback | None = None
@@ -5999,10 +6291,14 @@ def _fork_manifest_release_plans(
if host.is_linux:
linux_attempts = _linux_published_attempts(host, bundle)
if not linux_attempts:
- raise PrebuiltFallback("no compatible Linux prebuilt asset was found")
+ raise PrebuiltFallback(
+ "no compatible Linux prebuilt asset was found"
+ )
attempts = apply_approved_hashes(linux_attempts, checksums)
if not attempts:
- raise PrebuiltFallback("no compatible Linux prebuilt asset was found")
+ raise PrebuiltFallback(
+ "no compatible Linux prebuilt asset was found"
+ )
if attempts[0].selection_log:
log_lines(attempts[0].selection_log)
else:
@@ -6103,7 +6399,9 @@ def write_prebuilt_metadata(
"prebuilt_fallback_used": prebuilt_fallback_used,
"installed_at_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
}
- (install_dir / "UNSLOTH_PREBUILT_INFO.json").write_text(json.dumps(metadata, indent = 2) + "\n")
+ (install_dir / "UNSLOTH_PREBUILT_INFO.json").write_text(
+ json.dumps(metadata, indent = 2) + "\n"
+ )
def expected_install_fingerprint(
@@ -6220,7 +6518,9 @@ def install_runtime_dir(install_dir: Path, host: HostInfo) -> Path:
return install_dir / "build" / "bin"
-def runtime_payload_is_healthy(install_dir: Path, host: HostInfo, choice: AssetChoice) -> bool:
+def runtime_payload_is_healthy(
+ install_dir: Path, host: HostInfo, choice: AssetChoice
+) -> bool:
runtime_dir = install_runtime_dir(install_dir, host)
if not runtime_dir.exists():
return False
@@ -6343,13 +6643,17 @@ def validate_prebuilt_choice(
# not in any repo, so fetch the asset directly; codeload stays the fallback.
asset_url = (
release_asset_download_url(
- approved_checksums.repo, approved_checksums.release_tag, source_archive.asset_name
+ approved_checksums.repo,
+ approved_checksums.release_tag,
+ source_archive.asset_name,
)
if exact_source and source_archive is not None
else None
)
if exact_source:
- log(f"hydrating exact llama.cpp source for {source_repo}@{source_ref} into {install_dir}")
+ log(
+ f"hydrating exact llama.cpp source for {source_repo}@{source_ref} into {install_dir}"
+ )
else:
log(f"hydrating upstream llama.cpp source for {llama_tag} into {install_dir}")
hydrate_source_tree(
@@ -6367,7 +6671,9 @@ def validate_prebuilt_choice(
asset_url = asset_url,
)
log(f"overlaying prebuilt bundle {choice.name} into {install_dir}")
- server_path, quantize_path = install_from_archives(choice, host, install_dir, work_dir)
+ server_path, quantize_path = install_from_archives(
+ choice, host, install_dir, work_dir
+ )
preflight_linux_installed_binaries((server_path, quantize_path), install_dir, host)
preflight_macos_installed_binaries((server_path, quantize_path), install_dir, host)
ensure_repo_shape(install_dir)
@@ -6551,9 +6857,13 @@ def install_prebuilt(
published_repo,
published_release_tag,
)
- if release_plans and existing_install_matches_plan(install_dir, host, release_plans[0]):
+ if release_plans and existing_install_matches_plan(
+ install_dir, host, release_plans[0]
+ ):
current = release_plans[0]
- if diffusion_visual_server_backfill_needed(install_dir, host, current.attempts[0]):
+ if diffusion_visual_server_backfill_needed(
+ install_dir, host, current.attempts[0]
+ ):
log(
f"existing install matches {current.release_tag} but is missing the "
"DiffusionGemma visual-server; re-extracting the bundle to backfill it"
@@ -6567,11 +6877,15 @@ def install_prebuilt(
with tempfile.TemporaryDirectory(prefix = "unsloth-llama-prebuilt-") as tmp:
work_dir = Path(tmp)
probe_path = work_dir / "stories260K.gguf"
- download_validation_model(probe_path, validation_model_cache_path(install_dir))
+ download_validation_model(
+ probe_path, validation_model_cache_path(install_dir)
+ )
release_count = len(release_plans)
for release_index, plan in enumerate(release_plans):
choice = plan.attempts[0]
- backfill = diffusion_visual_server_backfill_needed(install_dir, host, choice)
+ backfill = diffusion_visual_server_backfill_needed(
+ install_dir, host, choice
+ )
if existing_install_matches_plan(install_dir, host, plan):
if backfill:
log(
@@ -6627,7 +6941,9 @@ def install_prebuilt(
f"({textwrap.shorten(str(exc), width = 200, placeholder = '...')})"
)
try:
- ensure_diffusion_visual_server(install_dir, host, plan.release_tag)
+ ensure_diffusion_visual_server(
+ install_dir, host, plan.release_tag
+ )
except Exception as exc:
log(
"diffusion visual server step skipped; install remains valid "
@@ -6794,7 +7110,9 @@ def main() -> int:
)
emit_resolver_output(
{
- "requested_tag": normalized_requested_llama_tag(args.resolve_install_tag),
+ "requested_tag": normalized_requested_llama_tag(
+ args.resolve_install_tag
+ ),
"llama_tag": resolved,
},
output_format = args.output_format,
@@ -6809,7 +7127,9 @@ def main() -> int:
)
emit_resolver_output(
{
- "requested_tag": normalized_requested_llama_tag(args.resolve_source_build),
+ "requested_tag": normalized_requested_llama_tag(
+ args.resolve_source_build
+ ),
"source_url": plan.source_url,
"source_ref_kind": plan.source_ref_kind,
"source_ref": plan.source_ref,
diff --git a/studio/install_python_stack.py b/studio/install_python_stack.py
index d404041ca7..3bbc80492f 100644
--- a/studio/install_python_stack.py
+++ b/studio/install_python_stack.py
@@ -243,7 +243,11 @@ def _detect_rocm_version() -> tuple[int, int] | None:
if result.returncode == 0:
raw = result.stdout.decode().strip().split("\n")[0]
parts = raw.split(".")
- if len(parts) >= 2 and parts[0].isdigit() and parts[1].split("-")[0].isdigit():
+ if (
+ len(parts) >= 2
+ and parts[0].isdigit()
+ and parts[1].split("-")[0].isdigit()
+ ):
return int(parts[0]), int(parts[1].split("-")[0])
except Exception:
pass
@@ -359,7 +363,8 @@ def _detect_windows_gfx_arch() -> str | None:
# findall gets every gcnArchName line so multi-GPU hosts are
# enumerable and HIP_VISIBLE_DEVICES selects correctly.
_tokens = [
- t.strip().lower() for t in re.findall(r"(?im)^\s*gcnArchName\s*:\s*(\S+)", text)
+ t.strip().lower()
+ for t in re.findall(r"(?im)^\s*gcnArchName\s*:\s*(\S+)", text)
]
_pick = _dedup_pick(_tokens)
if _pick:
@@ -536,7 +541,9 @@ def _persist_bnb_rocm_version(version: str) -> bool:
try:
sitecustomize_path.parent.mkdir(parents = True, exist_ok = True)
existing = (
- sitecustomize_path.read_text(encoding = "utf-8") if sitecustomize_path.exists() else ""
+ sitecustomize_path.read_text(encoding = "utf-8")
+ if sitecustomize_path.exists()
+ else ""
)
# Strip all managed regions, including one whose END marker was lost to
# an interrupted write, then append exactly one fresh block.
@@ -779,7 +786,8 @@ def _install_bnb_windows_rocm() -> bool:
# Fall back to "72" if detection fails (e.g. install was a no-op / dry-run).
_env_ver = os.environ.get("BNB_ROCM_VERSION")
_env_is_persisted_default = (
- os.environ.get(_BNB_ROCM_VERSION_SOURCE_ENV) == _BNB_ROCM_VERSION_SOURCE_SITECUSTOMIZE
+ os.environ.get(_BNB_ROCM_VERSION_SOURCE_ENV)
+ == _BNB_ROCM_VERSION_SOURCE_SITECUSTOMIZE
)
_persist_detected_version = False
if _env_ver and not _env_is_persisted_default:
@@ -913,7 +921,9 @@ def _ensure_cuda_torch() -> None:
# Take the last non-empty stdout line: stray output from sitecustomize or
# an import hook must not mask the marker (fail-closed either way).
_marker_lines = [
- line.strip() for line in probe.stdout.decode(errors = "replace").splitlines() if line.strip()
+ line.strip()
+ for line in probe.stdout.decode(errors = "replace").splitlines()
+ if line.strip()
]
if not _marker_lines or _marker_lines[-1] != "hip":
return # healthy CUDA torch, or a deliberate CPU wheel -- leave as-is
@@ -1022,7 +1032,9 @@ def _ensure_rocm_torch() -> None:
if not _torch_already_rocm:
index_url = _windows_rocm_index_url(gfx_arch)
if index_url is None:
- print(f" No AMD Windows torch index for GPU arch {gfx_arch} -- skipping")
+ print(
+ f" No AMD Windows torch index for GPU arch {gfx_arch} -- skipping"
+ )
return
print(f" {gfx_arch} (Windows) -- installing torch from {index_url}")
pip_install(
@@ -1095,7 +1107,9 @@ def _ensure_rocm_torch() -> None:
except (OSError, subprocess.TimeoutExpired):
probe = None
has_hip_torch = (
- probe is not None and probe.returncode == 0 and probe.stdout.decode().strip() != ""
+ probe is not None
+ and probe.returncode == 0
+ and probe.stdout.decode().strip() != ""
)
rocm_torch_ready = has_hip_torch
@@ -1117,11 +1131,14 @@ def _ensure_rocm_torch() -> None:
# Pick the runtime-visible GPU: use the HIP_VISIBLE_DEVICES index
# into gfx_codes, else default to the first GPU. Skip the override
# unless the resolved GPU is Strix.
- _runtime_gfx = gfx_codes[_pick_visible_index(len(gfx_codes))] if gfx_codes else None
+ _runtime_gfx = (
+ gfx_codes[_pick_visible_index(len(gfx_codes))] if gfx_codes else None
+ )
if _runtime_gfx in _strix_gfx:
_selected_gfx = _runtime_gfx
_amd_mirror = (
- os.environ.get("UNSLOTH_AMD_ROCM_MIRROR") or "https://repo.amd.com/rocm/whl"
+ os.environ.get("UNSLOTH_AMD_ROCM_MIRROR")
+ or "https://repo.amd.com/rocm/whl"
).rstrip("/")
_strix_override_url = f"{_amd_mirror}/{_selected_gfx}/"
_strix_override_pkgs = (
@@ -1182,7 +1199,10 @@ def _ensure_rocm_torch() -> None:
None,
)
if tag is None:
- print(f" No PyTorch wheel for ROCm {ver[0]}.{ver[1]} -- " f"skipping torch reinstall")
+ print(
+ f" No PyTorch wheel for ROCm {ver[0]}.{ver[1]} -- "
+ f"skipping torch reinstall"
+ )
else:
index_url = f"{_PYTORCH_WHL_BASE}/{tag}"
print(f" ROCm {ver[0]}.{ver[1]} -- installing torch from {index_url}")
@@ -1340,7 +1360,9 @@ CONSTRAINTS = SINGLE_ENV / "constraints.txt"
LOCAL_DD_UNSTRUCTURED_PLUGIN = (
SCRIPT_DIR / "backend" / "plugins" / "data-designer-unstructured-seed"
)
-LOCAL_DD_GITHUB_PLUGIN = SCRIPT_DIR / "backend" / "plugins" / "data-designer-github-repo-seed"
+LOCAL_DD_GITHUB_PLUGIN = (
+ SCRIPT_DIR / "backend" / "plugins" / "data-designer-github-repo-seed"
+)
# Apple Silicon: override mlx-vlm/mlx-lm's transformers pin (see overrides).
_MLX_OVERRIDES = SINGLE_ENV / "overrides-darwin-arm64.txt"
@@ -1465,7 +1487,9 @@ def _progress(label: str) -> None:
pad = " " * (_COL - len(_LABEL))
end = "\n" if _STEP >= _TOTAL else ""
try:
- sys.stdout.write(f"\r {_dim(_LABEL)}{pad}[{bar}] {_STEP:2}/{_TOTAL} {label:<20}{end}")
+ sys.stdout.write(
+ f"\r {_dim(_LABEL)}{pad}[{bar}] {_STEP:2}/{_TOTAL} {label:<20}{end}"
+ )
sys.stdout.flush()
except OSError:
pass
@@ -1525,7 +1549,9 @@ def _build_flash_attn_wheel_url(env: dict[str, str]) -> str | None:
return flash_attn_wheel_url(env)
-def _print_optional_install_failure(label: str, result: subprocess.CompletedProcess[str]) -> None:
+def _print_optional_install_failure(
+ label: str, result: subprocess.CompletedProcess[str]
+) -> None:
_step("warning", f"{label} failed (exit code {result.returncode})", _cyan)
if result.stdout:
print(result.stdout.strip())
@@ -1620,7 +1646,9 @@ def _filter_requirements(req: Path, skip: set[str]) -> Path:
"""Return a temp copy of a requirements file with certain packages removed."""
lines = req.read_text(encoding = "utf-8").splitlines(keepends = True)
filtered = [
- line for line in lines if not any(line.strip().lower().startswith(pkg) for pkg in skip)
+ line
+ for line in lines
+ if not any(line.strip().lower().startswith(pkg) for pkg in skip)
]
tmp = tempfile.NamedTemporaryFile(
mode = "w",
@@ -1830,7 +1858,9 @@ def install_python_stack() -> int:
if not IS_MACOS and not NO_TORCH:
base_total += 1 # ROCm torch check (line 1526) -- all non-macOS platforms
if not IS_WINDOWS:
- base_total += 2 # flash-attn (line 1620) + ROCm torch final (line 1705) -- Linux only
+ base_total += (
+ 2 # flash-attn (line 1620) + ROCm torch final (line 1705) -- Linux only
+ )
_TOTAL = (base_total - 1) if skip_base else base_total
# 1. Try uv for faster installs (before pip upgrade -- uv venvs don't
diff --git a/tests/python/conftest.py b/tests/python/conftest.py
index f7b125edf6..9129e384e5 100644
--- a/tests/python/conftest.py
+++ b/tests/python/conftest.py
@@ -2,5 +2,9 @@
def pytest_configure(config):
- config.addinivalue_line("markers", "server: heavyweight tests requiring studio venv")
- config.addinivalue_line("markers", "e2e: end-to-end tests requiring network and venv creation")
+ config.addinivalue_line(
+ "markers", "server: heavyweight tests requiring studio venv"
+ )
+ config.addinivalue_line(
+ "markers", "e2e: end-to-end tests requiring network and venv creation"
+ )
diff --git a/tests/python/test_cross_platform_parity.py b/tests/python/test_cross_platform_parity.py
index 34f984714e..8f7f09477f 100644
--- a/tests/python/test_cross_platform_parity.py
+++ b/tests/python/test_cross_platform_parity.py
@@ -27,11 +27,17 @@ class TestNoTorchBackendAutoInInstallSh:
for i, line in enumerate(lines):
if fallback_start is None and "GPU detection failed" in line:
fallback_start = i
- elif fallback_start is not None and fallback_end is None and line.strip() == "fi":
+ elif (
+ fallback_start is not None
+ and fallback_end is None
+ and line.strip() == "fi"
+ ):
fallback_end = i
break
fallback_range = (
- range(fallback_start or 0, (fallback_end or 0) + 1) if fallback_start else range(0)
+ range(fallback_start or 0, (fallback_end or 0) + 1)
+ if fallback_start
+ else range(0)
)
matches = [
diff --git a/tests/python/test_e2e_no_torch_sandbox.py b/tests/python/test_e2e_no_torch_sandbox.py
index 9b3bda133f..b4ff73882d 100644
--- a/tests/python/test_e2e_no_torch_sandbox.py
+++ b/tests/python/test_e2e_no_torch_sandbox.py
@@ -235,8 +235,12 @@ class TestBeforeAfterImportChain:
exec(source)
""")
result = _run_in_sandbox(no_torch_venv, code)
- assert result.returncode != 0, "BEFORE chat_templates.py should crash without torch"
- assert b"ModuleNotFoundError" in result.stderr or b"ImportError" in result.stderr
+ assert (
+ result.returncode != 0
+ ), "BEFORE chat_templates.py should crash without torch"
+ assert (
+ b"ModuleNotFoundError" in result.stderr or b"ImportError" in result.stderr
+ )
def test_before_data_collators_crashes(self, no_torch_venv, sandbox_dir):
"""BEFORE: data_collators.py with top-level 'import torch' crashes."""
@@ -254,8 +258,12 @@ class TestBeforeAfterImportChain:
exec(open({str(before_file)!r}).read())
""")
result = _run_in_sandbox(no_torch_venv, code)
- assert result.returncode != 0, "BEFORE data_collators.py should crash without torch"
- assert b"ModuleNotFoundError" in result.stderr or b"ImportError" in result.stderr
+ assert (
+ result.returncode != 0
+ ), "BEFORE data_collators.py should crash without torch"
+ assert (
+ b"ModuleNotFoundError" in result.stderr or b"ImportError" in result.stderr
+ )
def test_before_full_import_chain_crashes(self, no_torch_venv, sandbox_dir):
"""BEFORE: full utils/datasets/ package with top-level torch imports crashes."""
@@ -300,8 +308,12 @@ class TestBeforeAfterImportChain:
from utils.datasets import detect_dataset_format
""")
result = _run_in_sandbox(no_torch_venv, code)
- assert result.returncode != 0, "BEFORE full import chain should crash without torch"
- assert b"ModuleNotFoundError" in result.stderr or b"ImportError" in result.stderr
+ assert (
+ result.returncode != 0
+ ), "BEFORE full import chain should crash without torch"
+ assert (
+ b"ModuleNotFoundError" in result.stderr or b"ImportError" in result.stderr
+ )
# -- AFTER: succeeds --
@@ -515,7 +527,9 @@ class TestEdgeCasesBrokenTorch:
print("OK: data_collators works despite broken torch on sys.path")
""")
result = _run_in_sandbox(no_torch_venv, code)
- assert result.returncode == 0, f"Should work with broken torch:\n{result.stderr.decode()}"
+ assert (
+ result.returncode == 0
+ ), f"Should work with broken torch:\n{result.stderr.decode()}"
assert b"OK:" in result.stdout
def test_torch_import_error_hardware_fallback(self, no_torch_venv, sandbox_dir):
@@ -578,10 +592,14 @@ class TestEdgeCasesBrokenTorch:
print("OK: detect_hardware returned CPU with fake torch (no CUDA)")
""")
result = _run_in_sandbox(no_torch_venv, code)
- assert result.returncode == 0, f"Should fall back to CPU:\n{result.stderr.decode()}"
+ assert (
+ result.returncode == 0
+ ), f"Should fall back to CPU:\n{result.stderr.decode()}"
assert b"OK:" in result.stdout
- def test_lazy_torch_fails_at_call_time_not_import_time(self, no_torch_venv, sandbox_dir):
+ def test_lazy_torch_fails_at_call_time_not_import_time(
+ self, no_torch_venv, sandbox_dir
+ ):
"""apply_chat_template_to_dataset is importable without torch.
Calling the alpaca branch triggers the lazy 'from torch.utils.data' inside
@@ -627,7 +645,9 @@ class TestEdgeCasesBrokenTorch:
print("OK: call succeeded (unexpected but not a crash)")
""")
result = _run_in_sandbox(no_torch_venv, code)
- assert result.returncode == 0, f"Should not crash at import time:\n{result.stderr.decode()}"
+ assert (
+ result.returncode == 0
+ ), f"Should not crash at import time:\n{result.stderr.decode()}"
assert b"OK: import succeeded" in result.stdout
@@ -979,7 +999,9 @@ class TestInstallPythonStackFiltering:
source = Path(ips.__file__).read_text(encoding = "utf-8")
# NO_TORCH guard before overrides
- assert "if NO_TORCH:" in source, "NO_TORCH guard not found in install_python_stack.py"
+ assert (
+ "if NO_TORCH:" in source
+ ), "NO_TORCH guard not found in install_python_stack.py"
# macOS guard for triton
assert (
@@ -1082,7 +1104,9 @@ class TestLiveServerStartup:
for _ in range(30):
time.sleep(1)
try:
- resp = urllib.request.urlopen(f"http://127.0.0.1:{port}/api/health", timeout = 2)
+ resp = urllib.request.urlopen(
+ f"http://127.0.0.1:{port}/api/health", timeout = 2
+ )
if resp.status == 200:
ready = True
break
@@ -1106,8 +1130,12 @@ class TestLiveServerStartup:
capture_output = True,
timeout = 300,
)
- server_output = stdout.decode(errors = "replace") + stderr.decode(errors = "replace")
- pytest.skip(f"Server failed to start within 30 seconds. Output:\n{server_output}")
+ server_output = stdout.decode(errors = "replace") + stderr.decode(
+ errors = "replace"
+ )
+ pytest.skip(
+ f"Server failed to start within 30 seconds. Output:\n{server_output}"
+ )
yield proc, port
@@ -1151,7 +1179,9 @@ class TestLiveServerStartup:
import urllib.request
_, port = server_process
- resp = urllib.request.urlopen(f"http://127.0.0.1:{port}/openapi.json", timeout = 5)
+ resp = urllib.request.urlopen(
+ f"http://127.0.0.1:{port}/openapi.json", timeout = 5
+ )
spec = json.loads(resp.read())
assert (
len(spec.get("paths", {})) >= 20
diff --git a/tests/python/test_fast_language_model_text_only.py b/tests/python/test_fast_language_model_text_only.py
index ce4dd74439..9061240c05 100644
--- a/tests/python/test_fast_language_model_text_only.py
+++ b/tests/python/test_fast_language_model_text_only.py
@@ -126,7 +126,9 @@ def test_fast_language_model_forwards_text_only_to_fast_model():
# text_only defaults False (opt-in, not forced True), and both FastModel
# delegations forward it.
text_only_default = _param_default(method, "text_only")
- assert isinstance(text_only_default, ast.Constant) and text_only_default.value is False
+ assert (
+ isinstance(text_only_default, ast.Constant) and text_only_default.value is False
+ )
fast_model_calls = [
node
@@ -150,13 +152,16 @@ def test_fast_model_text_only_does_not_override_explicit_auto_model():
method = _class_method(ast.parse(source), "FastModel", "from_pretrained")
text_only_default = _param_default(method, "text_only")
- assert isinstance(text_only_default, ast.Constant) and text_only_default.value is False
+ assert (
+ isinstance(text_only_default, ast.Constant) and text_only_default.value is False
+ )
# load_text_only is text_only AND a check that the caller did not pass auto_model.
def _is_guarded_bool(value):
names = _names_in(value)
has_none_check = any(
- isinstance(n, ast.Compare) and any(isinstance(op, (ast.Is, ast.IsNot)) for op in n.ops)
+ isinstance(n, ast.Compare)
+ and any(isinstance(op, (ast.Is, ast.IsNot)) for op in n.ops)
for n in ast.walk(value)
)
return "text_only" in names and "auto_model" in names and has_none_check
@@ -192,7 +197,9 @@ def test_fast_base_model_text_only_bypasses_vision_auto_model():
method = _class_method(ast.parse(source), "FastBaseModel", "from_pretrained")
text_only_default = _param_default(method, "text_only")
- assert isinstance(text_only_default, ast.Constant) and text_only_default.value is False
+ assert (
+ isinstance(text_only_default, ast.Constant) and text_only_default.value is False
+ )
assert _assigns_name(
method,
@@ -327,7 +334,9 @@ def test_text_only_key_mapping_targets_published_prefixes():
# transformers >=5 (on 4.x base_model_prefix handles it and a mapping hurts).
transformers = pytest.importorskip("transformers")
get_key_mapping = _load_util_func("_get_text_only_key_mapping")
- mapping = get_key_mapping(transformers.Gemma3Config(), transformers.Gemma3TextConfig())
+ mapping = get_key_mapping(
+ transformers.Gemma3Config(), transformers.Gemma3TextConfig()
+ )
if int(transformers.__version__.split(".")[0]) < 5:
assert mapping is None
else:
diff --git a/tests/python/test_fast_model_config_passthrough.py b/tests/python/test_fast_model_config_passthrough.py
index b2ba3d2eef..35bdb6b619 100644
--- a/tests/python/test_fast_model_config_passthrough.py
+++ b/tests/python/test_fast_model_config_passthrough.py
@@ -29,7 +29,8 @@ def _assigns_from_kwargs_pop(method, target_name, key_name):
if not isinstance(node, ast.Assign):
continue
if not any(
- isinstance(target, ast.Name) and target.id == target_name for target in node.targets
+ isinstance(target, ast.Name) and target.id == target_name
+ for target in node.targets
):
continue
value = node.value
@@ -50,7 +51,9 @@ def _assigns_from_kwargs_pop(method, target_name, key_name):
def _calls_name(method, name):
return any(
- isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == name
+ isinstance(node, ast.Call)
+ and isinstance(node.func, ast.Name)
+ and node.func.id == name
for node in ast.walk(method)
)
@@ -130,7 +133,9 @@ def test_fast_model_uses_user_config_num_labels_for_task_model_selection():
def test_fast_model_captures_user_config_num_labels_before_text_only_switch():
source = _source(LOADER_PATH)
- fallback = source.index("task_config_attrs = _get_user_task_config_attrs(user_config)")
+ fallback = source.index(
+ "task_config_attrs = _get_user_task_config_attrs(user_config)"
+ )
text_only_switch = source.index("model_config = text_config")
assert fallback < text_only_switch
diff --git a/tests/python/test_fast_sentence_transformer_redirect_lifecycle.py b/tests/python/test_fast_sentence_transformer_redirect_lifecycle.py
index 6fa7d06ec3..82d5278075 100644
--- a/tests/python/test_fast_sentence_transformer_redirect_lifecycle.py
+++ b/tests/python/test_fast_sentence_transformer_redirect_lifecycle.py
@@ -127,10 +127,18 @@ def _build_driver(transformer_class):
return model if is_requested_model_name(a, kw) else original_model(*a, **kw)
def return_existing_tokenizer(*a, **kw):
- return tokenizer if is_requested_model_name(a, kw) else original_tokenizer(*a, **kw)
+ return (
+ tokenizer
+ if is_requested_model_name(a, kw)
+ else original_tokenizer(*a, **kw)
+ )
def return_existing_processor(*a, **kw):
- return tokenizer if is_requested_model_name(a, kw) else original_processor(*a, **kw)
+ return (
+ tokenizer
+ if is_requested_model_name(a, kw)
+ else original_processor(*a, **kw)
+ )
try:
AutoModel.from_pretrained = return_existing_model
diff --git a/tests/python/test_flash_attn_install_python_stack.py b/tests/python/test_flash_attn_install_python_stack.py
index 26ff03505a..a09b8e468f 100644
--- a/tests/python/test_flash_attn_install_python_stack.py
+++ b/tests/python/test_flash_attn_install_python_stack.py
@@ -33,42 +33,64 @@ class TestHasBlackwellGpu:
def test_returns_true_for_sm_100(self):
with (
- mock.patch.object(wheel_utils.shutil, "which", return_value = "/usr/bin/nvidia-smi"),
- mock.patch.object(wheel_utils.subprocess, "run", return_value = _smi_result("10.0\n")),
+ mock.patch.object(
+ wheel_utils.shutil, "which", return_value = "/usr/bin/nvidia-smi"
+ ),
+ mock.patch.object(
+ wheel_utils.subprocess, "run", return_value = _smi_result("10.0\n")
+ ),
):
assert wheel_utils.has_blackwell_gpu() is True
def test_returns_true_for_sm_120(self):
with (
- mock.patch.object(wheel_utils.shutil, "which", return_value = "/usr/bin/nvidia-smi"),
- mock.patch.object(wheel_utils.subprocess, "run", return_value = _smi_result("12.0\n")),
+ mock.patch.object(
+ wheel_utils.shutil, "which", return_value = "/usr/bin/nvidia-smi"
+ ),
+ mock.patch.object(
+ wheel_utils.subprocess, "run", return_value = _smi_result("12.0\n")
+ ),
):
assert wheel_utils.has_blackwell_gpu() is True
def test_returns_true_for_sm_121(self):
with (
- mock.patch.object(wheel_utils.shutil, "which", return_value = "/usr/bin/nvidia-smi"),
- mock.patch.object(wheel_utils.subprocess, "run", return_value = _smi_result("12.1\n")),
+ mock.patch.object(
+ wheel_utils.shutil, "which", return_value = "/usr/bin/nvidia-smi"
+ ),
+ mock.patch.object(
+ wheel_utils.subprocess, "run", return_value = _smi_result("12.1\n")
+ ),
):
assert wheel_utils.has_blackwell_gpu() is True
def test_returns_false_for_sm_90(self):
with (
- mock.patch.object(wheel_utils.shutil, "which", return_value = "/usr/bin/nvidia-smi"),
- mock.patch.object(wheel_utils.subprocess, "run", return_value = _smi_result("9.0\n")),
+ mock.patch.object(
+ wheel_utils.shutil, "which", return_value = "/usr/bin/nvidia-smi"
+ ),
+ mock.patch.object(
+ wheel_utils.subprocess, "run", return_value = _smi_result("9.0\n")
+ ),
):
assert wheel_utils.has_blackwell_gpu() is False
def test_returns_false_for_sm_89(self):
with (
- mock.patch.object(wheel_utils.shutil, "which", return_value = "/usr/bin/nvidia-smi"),
- mock.patch.object(wheel_utils.subprocess, "run", return_value = _smi_result("8.9\n")),
+ mock.patch.object(
+ wheel_utils.shutil, "which", return_value = "/usr/bin/nvidia-smi"
+ ),
+ mock.patch.object(
+ wheel_utils.subprocess, "run", return_value = _smi_result("8.9\n")
+ ),
):
assert wheel_utils.has_blackwell_gpu() is False
def test_mixed_gpus_with_one_blackwell_returns_true(self):
with (
- mock.patch.object(wheel_utils.shutil, "which", return_value = "/usr/bin/nvidia-smi"),
+ mock.patch.object(
+ wheel_utils.shutil, "which", return_value = "/usr/bin/nvidia-smi"
+ ),
mock.patch.object(
wheel_utils.subprocess,
"run",
@@ -79,7 +101,9 @@ class TestHasBlackwellGpu:
def test_returns_false_when_nvidia_smi_fails(self):
with (
- mock.patch.object(wheel_utils.shutil, "which", return_value = "/usr/bin/nvidia-smi"),
+ mock.patch.object(
+ wheel_utils.shutil, "which", return_value = "/usr/bin/nvidia-smi"
+ ),
mock.patch.object(
wheel_utils.subprocess,
"run",
@@ -90,7 +114,9 @@ class TestHasBlackwellGpu:
def test_returns_false_on_subprocess_timeout(self):
with (
- mock.patch.object(wheel_utils.shutil, "which", return_value = "/usr/bin/nvidia-smi"),
+ mock.patch.object(
+ wheel_utils.shutil, "which", return_value = "/usr/bin/nvidia-smi"
+ ),
mock.patch.object(
wheel_utils.subprocess,
"run",
@@ -101,7 +127,9 @@ class TestHasBlackwellGpu:
def test_returns_false_on_malformed_output(self):
with (
- mock.patch.object(wheel_utils.shutil, "which", return_value = "/usr/bin/nvidia-smi"),
+ mock.patch.object(
+ wheel_utils.shutil, "which", return_value = "/usr/bin/nvidia-smi"
+ ),
mock.patch.object(
wheel_utils.subprocess,
"run",
@@ -133,7 +161,10 @@ class TestFlashAttnWheelSelection:
)
assert url is not None
assert "v2.8.1" in url
- assert "flash_attn-2.8.1+cu12torch2.10cxx11abiTRUE-cp313-cp313-linux_x86_64.whl" in url
+ assert (
+ "flash_attn-2.8.1+cu12torch2.10cxx11abiTRUE-cp313-cp313-linux_x86_64.whl"
+ in url
+ )
def test_missing_cuda_major_disables_wheel_lookup(self):
assert (
@@ -316,7 +347,10 @@ class TestEnsureFlashAttn:
ips._ensure_flash_attn()
mock_install_wheel.assert_not_called()
- assert ("warning", "No published flash-attn prebuilt wheel found") in step_messages
+ assert (
+ "warning",
+ "No published flash-attn prebuilt wheel found",
+ ) in step_messages
def test_skip_env_disables_setup_install(self):
with (
@@ -357,7 +391,9 @@ class TestEnsureFlashAttn:
mock_probe.assert_not_called()
mock_install_wheel.assert_not_called()
- assert any(label == "warning" and "Blackwell" in msg for label, msg in step_messages)
+ assert any(
+ label == "warning" and "Blackwell" in msg for label, msg in step_messages
+ )
def test_blackwell_gpu_on_windows_emits_blackwell_warning(self):
step_messages: list[tuple[str, str]] = []
@@ -383,7 +419,9 @@ class TestEnsureFlashAttn:
mock_probe.assert_not_called()
mock_install_wheel.assert_not_called()
- assert any(label == "warning" and "Blackwell" in msg for label, msg in step_messages)
+ assert any(
+ label == "warning" and "Blackwell" in msg for label, msg in step_messages
+ )
def test_non_blackwell_windows_does_not_emit_blackwell_warning(self):
step_messages: list[tuple[str, str]] = []
@@ -435,7 +473,9 @@ class TestInstallPythonStackFlashAttnIntegration:
mock.patch("subprocess.run", side_effect = fake_run),
mock.patch.object(ips, "_has_usable_nvidia_gpu", return_value = False),
mock.patch.object(ips, "_has_rocm_gpu", return_value = False),
- mock.patch.object(ips, "LOCAL_DD_UNSTRUCTURED_PLUGIN", Path("/fake/plugin")),
+ mock.patch.object(
+ ips, "LOCAL_DD_UNSTRUCTURED_PLUGIN", Path("/fake/plugin")
+ ),
mock.patch("pathlib.Path.is_dir", return_value = True),
mock.patch("pathlib.Path.is_file", return_value = True),
mock.patch.dict(os.environ, {"SKIP_STUDIO_BASE": "1"}, clear = False),
diff --git a/tests/python/test_gpu_init_ldconfig_guard.py b/tests/python/test_gpu_init_ldconfig_guard.py
index 248bb84faa..081a6132b4 100644
--- a/tests/python/test_gpu_init_ldconfig_guard.py
+++ b/tests/python/test_gpu_init_ldconfig_guard.py
@@ -19,7 +19,9 @@ def _find_geteuid_guard(tree: ast.AST):
def test_gpu_init_has_geteuid_guard():
tree = ast.parse(GPU_INIT.read_text())
guard = _find_geteuid_guard(tree)
- assert guard is not None, "_gpu_init.py must guard ldconfig recovery on os.geteuid()"
+ assert (
+ guard is not None
+ ), "_gpu_init.py must guard ldconfig recovery on os.geteuid()"
def test_ldconfig_calls_only_inside_geteuid_guard():
diff --git a/tests/python/test_no_torch_filtering.py b/tests/python/test_no_torch_filtering.py
index f7a90fc6a8..6460274145 100644
--- a/tests/python/test_no_torch_filtering.py
+++ b/tests/python/test_no_torch_filtering.py
@@ -156,7 +156,9 @@ class TestFilterRequirements:
)
# First filter Windows packages, then NO_TORCH packages
intermediate = ips._filter_requirements(req, ips.WINDOWS_SKIP_PACKAGES)
- result = ips._filter_requirements(Path(intermediate), ips.NO_TORCH_SKIP_PACKAGES)
+ result = ips._filter_requirements(
+ Path(intermediate), ips.NO_TORCH_SKIP_PACKAGES
+ )
lines = Path(result).read_text(encoding = "utf-8").splitlines()
non_blank = [l.strip() for l in lines if l.strip()]
assert non_blank == [
@@ -175,7 +177,9 @@ class TestFilterRequirements:
result = ips._filter_requirements(req, ips.NO_TORCH_SKIP_PACKAGES)
lines = Path(result).read_text(encoding = "utf-8").splitlines()
non_blank = [l.strip() for l in lines if l.strip()]
- assert non_blank == ["numpy"], f"VCS URL line should be filtered, got: {non_blank}"
+ assert non_blank == [
+ "numpy"
+ ], f"VCS URL line should be filtered, got: {non_blank}"
def test_env_marker_line_filtered(self, tmp_path):
"""Package lines with env markers are still filtered by prefix."""
@@ -189,7 +193,9 @@ class TestFilterRequirements:
result = ips._filter_requirements(req, ips.NO_TORCH_SKIP_PACKAGES)
lines = Path(result).read_text(encoding = "utf-8").splitlines()
non_blank = [l.strip() for l in lines if l.strip()]
- assert non_blank == ["numpy"], f"Env marker line should be filtered, got: {non_blank}"
+ assert non_blank == [
+ "numpy"
+ ], f"Env marker line should be filtered, got: {non_blank}"
def test_git_plus_url_not_over_matched(self, tmp_path):
"""A git+ URL whose path contains a skip package name but does NOT start with it."""
@@ -241,7 +247,9 @@ class TestRealRequirementsFiltering:
expected = [
l
for l in original
- if not any(l.strip().lower().startswith(p) for p in ips.NO_TORCH_SKIP_PACKAGES)
+ if not any(
+ l.strip().lower().startswith(p) for p in ips.NO_TORCH_SKIP_PACKAGES
+ )
]
assert filtered == expected, (
f"Filtered extras.txt should match expected.\n"
@@ -251,7 +259,9 @@ class TestRealRequirementsFiltering:
def test_extras_no_deps_txt_torchcodec_and_dlpack_removed(self):
"""extras-no-deps.txt: torchcodec and torch-c-dlpack-ext must be removed."""
- result = ips._filter_requirements(EXTRAS_NO_DEPS_TXT, ips.NO_TORCH_SKIP_PACKAGES)
+ result = ips._filter_requirements(
+ EXTRAS_NO_DEPS_TXT, ips.NO_TORCH_SKIP_PACKAGES
+ )
filtered = self._non_blank_non_comment(Path(result))
original = self._non_blank_non_comment(EXTRAS_NO_DEPS_TXT)
@@ -263,7 +273,9 @@ class TestRealRequirementsFiltering:
expected = [
l
for l in original
- if not any(l.strip().lower().startswith(p) for p in ips.NO_TORCH_SKIP_PACKAGES)
+ if not any(
+ l.strip().lower().startswith(p) for p in ips.NO_TORCH_SKIP_PACKAGES
+ )
]
assert filtered == expected
@@ -279,7 +291,9 @@ class TestRealRequirementsFiltering:
def test_extras_no_deps_txt_trl_preserved(self):
"""trl should survive NO_TORCH filtering in extras-no-deps.txt."""
- result = ips._filter_requirements(EXTRAS_NO_DEPS_TXT, ips.NO_TORCH_SKIP_PACKAGES)
+ result = ips._filter_requirements(
+ EXTRAS_NO_DEPS_TXT, ips.NO_TORCH_SKIP_PACKAGES
+ )
filtered_text = Path(result).read_text(encoding = "utf-8").lower()
assert "trl" in filtered_text, "trl should survive NO_TORCH filtering"
@@ -390,7 +404,9 @@ class TestInstallPythonStackSubprocessMock:
captured_cmds: list[list[str]] = []
def mock_run(cmd, **kw):
- captured_cmds.append(list(cmd) if isinstance(cmd, (list, tuple)) else [str(cmd)])
+ captured_cmds.append(
+ list(cmd) if isinstance(cmd, (list, tuple)) else [str(cmd)]
+ )
return subprocess.CompletedProcess(cmd, 0, b"", b"")
env = {"SKIP_STUDIO_BASE": "1"} if skip_base else {}
@@ -407,7 +423,9 @@ class TestInstallPythonStackSubprocessMock:
mock.patch.object(ips, "_has_rocm_gpu", return_value = False),
mock.patch("subprocess.run", side_effect = mock_run),
mock.patch.object(ips, "_bootstrap_uv", return_value = True),
- mock.patch.object(ips, "LOCAL_DD_UNSTRUCTURED_PLUGIN", Path("/fake/plugin")),
+ mock.patch.object(
+ ips, "LOCAL_DD_UNSTRUCTURED_PLUGIN", Path("/fake/plugin")
+ ),
mock.patch("pathlib.Path.is_dir", return_value = True),
mock.patch("pathlib.Path.is_file", return_value = True),
):
@@ -450,7 +468,9 @@ class TestInstallPythonStackSubprocessMock:
has_extras_nd = self._cmds_contain_file(cmds, "extras-no-deps.txt") or any(
"-r" in cmd and "tmp" in cmd.lower() for cmd in cmds
)
- assert has_extras_nd, "extras-no-deps.txt (or its filtered temp) should be called"
+ assert (
+ has_extras_nd
+ ), "extras-no-deps.txt (or its filtered temp) should be called"
# -- IS_WINDOWS=True + NO_TORCH=True (stacked) --
@@ -549,13 +569,17 @@ class TestOverridesSkip:
def test_no_torch_guard_exists_in_source(self):
"""The install_python_stack source must contain a NO_TORCH guard around overrides."""
source = Path(ips.__file__).read_text(encoding = "utf-8")
- assert "if NO_TORCH:" in source, "NO_TORCH guard not found in install_python_stack.py"
+ assert (
+ "if NO_TORCH:" in source
+ ), "NO_TORCH guard not found in install_python_stack.py"
def test_overrides_skipped_when_no_torch(self):
"""With NO_TORCH=True on the module, pip_install should NOT be called for overrides."""
source = Path(ips.__file__).read_text(encoding = "utf-8")
overrides_match = re.search(r"if NO_TORCH:.*?overrides", source, re.DOTALL)
- assert overrides_match is not None, "Expected NO_TORCH conditional before overrides install"
+ assert (
+ overrides_match is not None
+ ), "Expected NO_TORCH conditional before overrides install"
# ── install.sh --no-torch flag tests ──────────────────────────────────
@@ -574,21 +598,33 @@ class TestInstallShNoTorchFlag:
def test_no_torch_flag_in_case_statement(self):
"""--no-torch must appear in the flag parser case statement."""
- assert "--no-torch)" in self.source, "--no-torch not found in install.sh flag parser"
+ assert (
+ "--no-torch)" in self.source
+ ), "--no-torch not found in install.sh flag parser"
def test_no_torch_flag_variable_initialized(self):
"""_NO_TORCH_FLAG must be initialized to false."""
- assert "_NO_TORCH_FLAG=false" in self.source, "_NO_TORCH_FLAG=false not found in install.sh"
+ assert (
+ "_NO_TORCH_FLAG=false" in self.source
+ ), "_NO_TORCH_FLAG=false not found in install.sh"
def test_skip_torch_variable_exists(self):
"""SKIP_TORCH variable must be defined."""
- assert "SKIP_TORCH=false" in self.source, "SKIP_TORCH=false not found in install.sh"
- assert "SKIP_TORCH=true" in self.source, "SKIP_TORCH=true not found in install.sh"
+ assert (
+ "SKIP_TORCH=false" in self.source
+ ), "SKIP_TORCH=false not found in install.sh"
+ assert (
+ "SKIP_TORCH=true" in self.source
+ ), "SKIP_TORCH=true not found in install.sh"
def test_skip_torch_driven_by_flag_and_mac_intel(self):
"""SKIP_TORCH must check both _NO_TORCH_FLAG and MAC_INTEL."""
- assert "_NO_TORCH_FLAG" in self.source, "_NO_TORCH_FLAG not referenced in SKIP_TORCH logic"
- assert "MAC_INTEL" in self.source, "MAC_INTEL not referenced in SKIP_TORCH logic"
+ assert (
+ "_NO_TORCH_FLAG" in self.source
+ ), "_NO_TORCH_FLAG not referenced in SKIP_TORCH logic"
+ assert (
+ "MAC_INTEL" in self.source
+ ), "MAC_INTEL not referenced in SKIP_TORCH logic"
def test_unsloth_no_torch_uses_skip_torch(self):
"""UNSLOTH_NO_TORCH must reference $SKIP_TORCH, not $MAC_INTEL."""
@@ -596,12 +632,18 @@ class TestInstallShNoTorchFlag:
matches = re.findall(r'UNSLOTH_NO_TORCH="\$(\w+)"', self.source)
for var in matches:
- assert var == "SKIP_TORCH", f"UNSLOTH_NO_TORCH references ${var} instead of $SKIP_TORCH"
+ assert (
+ var == "SKIP_TORCH"
+ ), f"UNSLOTH_NO_TORCH references ${var} instead of $SKIP_TORCH"
def test_cpu_hint_message_exists(self):
"""CPU hint message must exist in install.sh."""
- assert "No GPU detected" in self.source, "CPU hint message not found in install.sh"
- assert "--no-torch" in self.source, "--no-torch suggestion not found in CPU hint"
+ assert (
+ "No GPU detected" in self.source
+ ), "CPU hint message not found in install.sh"
+ assert (
+ "--no-torch" in self.source
+ ), "--no-torch suggestion not found in CPU hint"
def test_no_torch_flag_parsing_subprocess(self):
"""--no-torch flag sets _NO_TORCH_FLAG=true (subprocess test)."""
diff --git a/tests/python/test_studio_import_no_torch.py b/tests/python/test_studio_import_no_torch.py
index 7b7a7103d2..09e04d254a 100644
--- a/tests/python/test_studio_import_no_torch.py
+++ b/tests/python/test_studio_import_no_torch.py
@@ -23,9 +23,15 @@ from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parents[2]
-DATA_COLLATORS = REPO_ROOT / "studio" / "backend" / "utils" / "datasets" / "data_collators.py"
-CHAT_TEMPLATES = REPO_ROOT / "studio" / "backend" / "utils" / "datasets" / "chat_templates.py"
-FORMAT_CONVERSION = REPO_ROOT / "studio" / "backend" / "utils" / "datasets" / "format_conversion.py"
+DATA_COLLATORS = (
+ REPO_ROOT / "studio" / "backend" / "utils" / "datasets" / "data_collators.py"
+)
+CHAT_TEMPLATES = (
+ REPO_ROOT / "studio" / "backend" / "utils" / "datasets" / "chat_templates.py"
+)
+FORMAT_CONVERSION = (
+ REPO_ROOT / "studio" / "backend" / "utils" / "datasets" / "format_conversion.py"
+)
def _has_uv() -> bool:
@@ -65,7 +71,9 @@ def no_torch_venv(request, tmp_path_factory):
[str(venv_python), "-c", "import torch"],
capture_output = True,
)
- assert check.returncode != 0, f"torch should NOT be importable in fresh {py_version} venv"
+ assert (
+ check.returncode != 0
+ ), f"torch should NOT be importable in fresh {py_version} venv"
return str(venv_python)
@@ -214,7 +222,9 @@ class TestDataCollatorsNoTorchVenv:
capture_output = True,
timeout = 30,
)
- assert result.returncode == 0, f"DeepSeekOCRDataCollator failed:\n{result.stderr.decode()}"
+ assert (
+ result.returncode == 0
+ ), f"DeepSeekOCRDataCollator failed:\n{result.stderr.decode()}"
assert b"OK: DeepSeekOCRDataCollator instantiated" in result.stdout
def test_dataclass_vlm_collator_instantiable(self, no_torch_venv):
@@ -235,7 +245,9 @@ class TestDataCollatorsNoTorchVenv:
capture_output = True,
timeout = 30,
)
- assert result.returncode == 0, f"VLMDataCollator failed:\n{result.stderr.decode()}"
+ assert (
+ result.returncode == 0
+ ), f"VLMDataCollator failed:\n{result.stderr.decode()}"
assert b"OK: VLMDataCollator instantiated" in result.stdout
@@ -516,9 +528,12 @@ class TestNegativeControls:
capture_output = True,
timeout = 30,
)
- assert result.returncode != 0, "Expected failure when 'import torch' is prepended"
assert (
- b"ModuleNotFoundError" in result.stderr or b"ImportError" in result.stderr
+ result.returncode != 0
+ ), "Expected failure when 'import torch' is prepended"
+ assert (
+ b"ModuleNotFoundError" in result.stderr
+ or b"ImportError" in result.stderr
), f"Expected ImportError, got:\n{result.stderr.decode()}"
finally:
os.unlink(temp_file)
@@ -561,4 +576,6 @@ class TestNegativeControls:
timeout = 30,
)
assert result.returncode != 0, "import torch should fail in no-torch venv"
- assert b"ModuleNotFoundError" in result.stderr or b"ImportError" in result.stderr
+ assert (
+ b"ModuleNotFoundError" in result.stderr or b"ImportError" in result.stderr
+ )
diff --git a/tests/python/test_tokenizers_and_torch_constraint.py b/tests/python/test_tokenizers_and_torch_constraint.py
index 40924efca2..ccdc879de3 100644
--- a/tests/python/test_tokenizers_and_torch_constraint.py
+++ b/tests/python/test_tokenizers_and_torch_constraint.py
@@ -17,7 +17,9 @@ _TESTS_DIR = pathlib.Path(__file__).resolve().parent.parent # tests/
_REPO_ROOT = _TESTS_DIR.parent # unsloth/
_INSTALL_SH = _REPO_ROOT / "install.sh"
_INSTALL_PS1 = _REPO_ROOT / "install.ps1"
-_NO_TORCH_RT = _REPO_ROOT / "studio" / "backend" / "requirements" / "no-torch-runtime.txt"
+_NO_TORCH_RT = (
+ _REPO_ROOT / "studio" / "backend" / "requirements" / "no-torch-runtime.txt"
+)
def _read(path: pathlib.Path) -> str:
@@ -42,23 +44,30 @@ class TestStructuralTokenizers:
def test_tokenizers_present(self):
"""tokenizers must be a standalone package line."""
pkgs = _lines(_NO_TORCH_RT)
- bare_names = [p.split(">")[0].split("<")[0].split("!")[0].split("=")[0] for p in pkgs]
+ bare_names = [
+ p.split(">")[0].split("<")[0].split("!")[0].split("=")[0] for p in pkgs
+ ]
assert "tokenizers" in bare_names
def test_tokenizers_before_transformers(self):
"""tokenizers should appear before transformers (install order intent)."""
pkgs = _lines(_NO_TORCH_RT)
- bare_names = [p.split(">")[0].split("<")[0].split("!")[0].split("=")[0] for p in pkgs]
+ bare_names = [
+ p.split(">")[0].split("<")[0].split("!")[0].split("=")[0] for p in pkgs
+ ]
idx_tok = bare_names.index("tokenizers")
idx_tf = bare_names.index("transformers")
assert idx_tok < idx_tf, (
- f"tokenizers at index {idx_tok} should appear before " f"transformers at index {idx_tf}"
+ f"tokenizers at index {idx_tok} should appear before "
+ f"transformers at index {idx_tf}"
)
def test_torch_not_in_no_torch_file(self):
"""torch itself must NOT be listed in the no-torch requirements."""
pkgs = _lines(_NO_TORCH_RT)
- bare_names = [p.split(">")[0].split("<")[0].split("!")[0].split("=")[0] for p in pkgs]
+ bare_names = [
+ p.split(">")[0].split("<")[0].split("!")[0].split("=")[0] for p in pkgs
+ ]
assert "torch" not in bare_names
@@ -399,7 +408,9 @@ class TestE2ETokenizersFix:
r = self._pip_install(venv, "--no-deps", "-r", str(_NO_TORCH_RT))
assert r.returncode == 0, f"Install failed: {r.stderr}"
- result = self._run_python(venv, "from transformers import AutoConfig; print('OK')")
+ result = self._run_python(
+ venv, "from transformers import AutoConfig; print('OK')"
+ )
assert (
result.returncode == 0
), f"AutoConfig import failed:\nstdout: {result.stdout}\nstderr: {result.stderr}"
@@ -429,15 +440,22 @@ class TestE2ETokenizersFix:
req_no_tokenizers = tmp_path / "no-tokenizers.txt"
req_no_tokenizers.write_text(
"\n".join(
- line for line in _read(_NO_TORCH_RT).splitlines() if line.strip() != "tokenizers"
+ line
+ for line in _read(_NO_TORCH_RT).splitlines()
+ if line.strip() != "tokenizers"
),
encoding = "utf-8",
)
r = self._pip_install(venv, "--no-deps", "-r", str(req_no_tokenizers))
assert r.returncode == 0, f"Install failed: {r.stderr}"
result = self._run_python(venv, "from transformers import AutoConfig")
- assert result.returncode != 0, "AutoConfig should fail without tokenizers installed"
- assert "tokenizers" in result.stderr.lower() or "ModuleNotFoundError" in result.stderr
+ assert (
+ result.returncode != 0
+ ), "AutoConfig should fail without tokenizers installed"
+ assert (
+ "tokenizers" in result.stderr.lower()
+ or "ModuleNotFoundError" in result.stderr
+ )
# ======================================================================
@@ -516,7 +534,9 @@ class TestE2EFullNoTorchSandbox:
venv = self._create_venv(tmp_path, "full-no-torch")
r = self._pip_install(venv, "--no-deps", "-r", str(_NO_TORCH_RT))
assert r.returncode == 0, f"Install failed: {r.stderr}"
- result = self._run_python(venv, "from transformers import AutoConfig; print('OK')")
+ result = self._run_python(
+ venv, "from transformers import AutoConfig; print('OK')"
+ )
assert (
result.returncode == 0
), f"AutoConfig failed:\nstdout: {result.stdout}\nstderr: {result.stderr}"
diff --git a/tests/python/test_unsloth_run_tool_policy_resolver.py b/tests/python/test_unsloth_run_tool_policy_resolver.py
index 6e3e3a722d..6aff02494b 100644
--- a/tests/python/test_unsloth_run_tool_policy_resolver.py
+++ b/tests/python/test_unsloth_run_tool_policy_resolver.py
@@ -141,11 +141,15 @@ class TestZeroHost:
class TestIsExternalHost:
- @pytest.mark.parametrize("host", ["127.0.0.1", "localhost", "::1", "LOCALHOST", "Localhost"])
+ @pytest.mark.parametrize(
+ "host", ["127.0.0.1", "localhost", "::1", "LOCALHOST", "Localhost"]
+ )
def test_loopback_aliases_are_local(self, host):
assert is_external_host(host) is False
- @pytest.mark.parametrize("host", ["0.0.0.0", "::", "192.168.1.5", "10.0.0.1", "example.com"])
+ @pytest.mark.parametrize(
+ "host", ["0.0.0.0", "::", "192.168.1.5", "10.0.0.1", "example.com"]
+ )
def test_non_loopback_is_external(self, host):
assert is_external_host(host) is True
diff --git a/tests/qlora/test_hf_qlora_train_and_merge.py b/tests/qlora/test_hf_qlora_train_and_merge.py
index 0892627c46..ae975b0266 100644
--- a/tests/qlora/test_hf_qlora_train_and_merge.py
+++ b/tests/qlora/test_hf_qlora_train_and_merge.py
@@ -91,7 +91,9 @@ if __name__ == "__main__":
print(training_args)
print(peft_config)
- trainer = setup_trainer(model, tokenizer, dataset, training_args, peft_config = peft_config)
+ trainer = setup_trainer(
+ model, tokenizer, dataset, training_args, peft_config = peft_config
+ )
with header_footer_context("Model"):
print(type(model.model))
diff --git a/tests/saving/gpt-oss-merge/test_merged_model.py b/tests/saving/gpt-oss-merge/test_merged_model.py
index 024d48f276..d965f44966 100644
--- a/tests/saving/gpt-oss-merge/test_merged_model.py
+++ b/tests/saving/gpt-oss-merge/test_merged_model.py
@@ -42,7 +42,9 @@ inputs = merged_tokenizer.apply_chat_template(
reasoning_effort = "low", # low, medium or high
).to(merged_model.device)
-_ = merged_model.generate(**inputs, max_new_tokens = 512, streamer = TextStreamer(merged_tokenizer))
+_ = merged_model.generate(
+ **inputs, max_new_tokens = 512, streamer = TextStreamer(merged_tokenizer)
+)
print("\n✅ Inference complete.")
# --- Final Cleanup ---
@@ -52,5 +54,7 @@ torch.cuda.empty_cache()
gc.collect()
safe_remove_directory("./gpt-oss-finetuned-merged")
-safe_remove_directory("./unsloth_compiled_cache") # Clean up cache created by this process
+safe_remove_directory(
+ "./unsloth_compiled_cache"
+) # Clean up cache created by this process
print("✅ Final cleanup complete. Exiting inference script.")
diff --git a/tests/saving/gpt-oss-merge/train_and_merge.py b/tests/saving/gpt-oss-merge/train_and_merge.py
index 1d35c6759d..0ca5cd0db5 100644
--- a/tests/saving/gpt-oss-merge/train_and_merge.py
+++ b/tests/saving/gpt-oss-merge/train_and_merge.py
@@ -27,7 +27,9 @@ tokenizer = None
def formatting_prompts_func(examples):
convos = examples["messages"]
texts = [
- tokenizer.apply_chat_template(convo, tokenize = False, add_generation_prompt = False)
+ tokenizer.apply_chat_template(
+ convo, tokenize = False, add_generation_prompt = False
+ )
for convo in convos
]
return {"text": texts}
@@ -81,7 +83,9 @@ print("Fine-tuning complete.")
# --- Merge and Save ---
print("\n💾 Merging and saving the 16-bit model to './gpt-oss-finetuned-merged'...")
-model.save_pretrained_merged(save_directory = "./gpt-oss-finetuned-merged", tokenizer = tokenizer)
+model.save_pretrained_merged(
+ save_directory = "./gpt-oss-finetuned-merged", tokenizer = tokenizer
+)
print("✅ Model merged and saved.")
# --- Cleanup ---
@@ -91,5 +95,7 @@ torch.cuda.empty_cache()
gc.collect()
safe_remove_directory("./outputs")
-safe_remove_directory("./unsloth_compiled_cache") # Clean up the cache created by this process
+safe_remove_directory(
+ "./unsloth_compiled_cache"
+) # Clean up the cache created by this process
print("✅ Cleanup complete. Exiting training script.")
diff --git a/tests/saving/language_models/test_merge_4bit_validation.py b/tests/saving/language_models/test_merge_4bit_validation.py
index 2e2e823bd3..9990028624 100644
--- a/tests/saving/language_models/test_merge_4bit_validation.py
+++ b/tests/saving/language_models/test_merge_4bit_validation.py
@@ -16,7 +16,9 @@ from tests.utils.cleanup_utils import safe_remove_directory
def formatting_prompts_func(examples):
convos = examples["messages"]
texts = [
- tokenizer.apply_chat_template(convo, tokenize = False, add_generation_prompt = False)
+ tokenizer.apply_chat_template(
+ convo, tokenize = False, add_generation_prompt = False
+ )
for convo in convos
]
return {"text": texts}
@@ -48,7 +50,9 @@ tokenizer = get_chat_template(
chat_template = "llama-3.1",
)
-dataset_train = load_dataset("allenai/openassistant-guanaco-reformatted", split = "train[:100]")
+dataset_train = load_dataset(
+ "allenai/openassistant-guanaco-reformatted", split = "train[:100]"
+)
dataset_train = dataset_train.map(formatting_prompts_func, batched = True)
print("✅ Base model loaded successfully!")
diff --git a/tests/saving/language_models/test_merge_model_perplexity_llama-3.2.py b/tests/saving/language_models/test_merge_model_perplexity_llama-3.2.py
index f8f81c550e..710d86ab6b 100644
--- a/tests/saving/language_models/test_merge_model_perplexity_llama-3.2.py
+++ b/tests/saving/language_models/test_merge_model_perplexity_llama-3.2.py
@@ -34,7 +34,9 @@ from tests.utils.perplexity_eval import (
def formatting_prompts_func(examples):
convos = examples["messages"]
texts = [
- tokenizer.apply_chat_template(convo, tokenize = False, add_generation_prompt = False)
+ tokenizer.apply_chat_template(
+ convo, tokenize = False, add_generation_prompt = False
+ )
for convo in convos
]
return {"text": texts}
@@ -62,12 +64,16 @@ def load_and_compute_8bit_ppl(
)
# Load dataset fresh in subprocess
- dataset_ppl = load_dataset("allenai/openassistant-guanaco-reformatted", split = "eval")
+ dataset_ppl = load_dataset(
+ "allenai/openassistant-guanaco-reformatted", split = "eval"
+ )
def formatting_prompts_func(examples):
convos = examples["messages"]
texts = [
- merged_tokenizer.apply_chat_template(convo, tokenize = False, add_generation_prompt = False)
+ merged_tokenizer.apply_chat_template(
+ convo, tokenize = False, add_generation_prompt = False
+ )
for convo in convos
]
return {"text": texts}
@@ -120,8 +126,12 @@ if __name__ == "__main__":
from unsloth.chat_templates import standardize_sharegpt
- dataset_train = load_dataset("allenai/openassistant-guanaco-reformatted", split = "train")
- dataset_ppl = load_dataset("allenai/openassistant-guanaco-reformatted", split = "eval")
+ dataset_train = load_dataset(
+ "allenai/openassistant-guanaco-reformatted", split = "train"
+ )
+ dataset_ppl = load_dataset(
+ "allenai/openassistant-guanaco-reformatted", split = "eval"
+ )
dataset_train = dataset_train.map(formatting_prompts_func, batched = True)
dataset_ppl = dataset_ppl.map(formatting_prompts_func, batched = True)
diff --git a/tests/saving/language_models/test_merge_model_perplexity_mistral.py b/tests/saving/language_models/test_merge_model_perplexity_mistral.py
index d467089a47..46c4062908 100644
--- a/tests/saving/language_models/test_merge_model_perplexity_mistral.py
+++ b/tests/saving/language_models/test_merge_model_perplexity_mistral.py
@@ -51,7 +51,9 @@ def load_and_compute_8bit_ppl(
# )
# Load dataset fresh in subprocess.
- dataset_ppl = load_dataset("allenai/openassistant-guanaco-reformatted", split = "eval")
+ dataset_ppl = load_dataset(
+ "allenai/openassistant-guanaco-reformatted", split = "eval"
+ )
alpaca_prompt = """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.
@@ -87,7 +89,10 @@ def load_and_compute_8bit_ppl(
inputs.append(user_message)
outputs.append(assistant_message)
- text = alpaca_prompt.format(instruction, user_message, assistant_message) + EOS_TOKEN
+ text = (
+ alpaca_prompt.format(instruction, user_message, assistant_message)
+ + EOS_TOKEN
+ )
texts.append(text)
return {
@@ -172,7 +177,10 @@ if __name__ == "__main__":
inputs.append(user_message)
outputs.append(assistant_message)
- text = alpaca_prompt.format(instruction, user_message, assistant_message) + EOS_TOKEN
+ text = (
+ alpaca_prompt.format(instruction, user_message, assistant_message)
+ + EOS_TOKEN
+ )
texts.append(text)
return {
@@ -182,8 +190,12 @@ if __name__ == "__main__":
"text": texts,
}
- dataset_train = load_dataset("allenai/openassistant-guanaco-reformatted", split = "train")
- dataset_ppl = load_dataset("allenai/openassistant-guanaco-reformatted", split = "eval")
+ dataset_train = load_dataset(
+ "allenai/openassistant-guanaco-reformatted", split = "train"
+ )
+ dataset_ppl = load_dataset(
+ "allenai/openassistant-guanaco-reformatted", split = "eval"
+ )
dataset_train = dataset_train.map(formatting_prompts_func, batched = True)
dataset_ppl = dataset_ppl.map(formatting_prompts_func, batched = True)
diff --git a/tests/saving/language_models/test_merge_model_perplexity_phi_4.py b/tests/saving/language_models/test_merge_model_perplexity_phi_4.py
index 391d1b90db..5fbf2bc026 100644
--- a/tests/saving/language_models/test_merge_model_perplexity_phi_4.py
+++ b/tests/saving/language_models/test_merge_model_perplexity_phi_4.py
@@ -34,7 +34,9 @@ from tests.utils.perplexity_eval import (
def formatting_prompts_func(examples):
convos = examples["messages"]
texts = [
- tokenizer.apply_chat_template(convo, tokenize = False, add_generation_prompt = False)
+ tokenizer.apply_chat_template(
+ convo, tokenize = False, add_generation_prompt = False
+ )
for convo in convos
]
return {
@@ -64,12 +66,16 @@ def load_and_compute_8bit_ppl(
)
# Load dataset fresh in subprocess
- dataset_ppl = load_dataset("allenai/openassistant-guanaco-reformatted", split = "eval")
+ dataset_ppl = load_dataset(
+ "allenai/openassistant-guanaco-reformatted", split = "eval"
+ )
def formatting_prompts_func(examples):
convos = examples["messages"]
texts = [
- merged_tokenizer.apply_chat_template(convo, tokenize = False, add_generation_prompt = False)
+ merged_tokenizer.apply_chat_template(
+ convo, tokenize = False, add_generation_prompt = False
+ )
for convo in convos
]
return {"text": texts}
@@ -120,8 +126,12 @@ if __name__ == "__main__":
chat_template = "phi-4",
)
- dataset_train = load_dataset("allenai/openassistant-guanaco-reformatted", split = "train")
- dataset_ppl = load_dataset("allenai/openassistant-guanaco-reformatted", split = "eval")
+ dataset_train = load_dataset(
+ "allenai/openassistant-guanaco-reformatted", split = "train"
+ )
+ dataset_ppl = load_dataset(
+ "allenai/openassistant-guanaco-reformatted", split = "eval"
+ )
dataset_train = dataset_train.map(formatting_prompts_func, batched = True)
dataset_ppl = dataset_ppl.map(formatting_prompts_func, batched = True)
diff --git a/tests/saving/language_models/test_merged_model_perplexity_llama-3.1-8b.py b/tests/saving/language_models/test_merged_model_perplexity_llama-3.1-8b.py
index 9c2c1acb41..1285cbe69e 100644
--- a/tests/saving/language_models/test_merged_model_perplexity_llama-3.1-8b.py
+++ b/tests/saving/language_models/test_merged_model_perplexity_llama-3.1-8b.py
@@ -34,7 +34,9 @@ from tests.utils.perplexity_eval import (
def formatting_prompts_func(examples):
convos = examples["messages"]
texts = [
- tokenizer.apply_chat_template(convo, tokenize = False, add_generation_prompt = False)
+ tokenizer.apply_chat_template(
+ convo, tokenize = False, add_generation_prompt = False
+ )
for convo in convos
]
return {"text": texts}
@@ -62,12 +64,16 @@ def load_and_compute_8bit_ppl(
)
# Load dataset fresh in subprocess
- dataset_ppl = load_dataset("allenai/openassistant-guanaco-reformatted", split = "eval")
+ dataset_ppl = load_dataset(
+ "allenai/openassistant-guanaco-reformatted", split = "eval"
+ )
def formatting_prompts_func(examples):
convos = examples["messages"]
texts = [
- merged_tokenizer.apply_chat_template(convo, tokenize = False, add_generation_prompt = False)
+ merged_tokenizer.apply_chat_template(
+ convo, tokenize = False, add_generation_prompt = False
+ )
for convo in convos
]
return {"text": texts}
@@ -120,8 +126,12 @@ if __name__ == "__main__":
from unsloth.chat_templates import standardize_sharegpt
- dataset_train = load_dataset("allenai/openassistant-guanaco-reformatted", split = "train")
- dataset_ppl = load_dataset("allenai/openassistant-guanaco-reformatted", split = "eval")
+ dataset_train = load_dataset(
+ "allenai/openassistant-guanaco-reformatted", split = "train"
+ )
+ dataset_ppl = load_dataset(
+ "allenai/openassistant-guanaco-reformatted", split = "eval"
+ )
dataset_train = dataset_train.map(formatting_prompts_func, batched = True)
dataset_ppl = dataset_ppl.map(formatting_prompts_func, batched = True)
diff --git a/tests/saving/language_models/test_merged_model_perplexity_qwen_2.5.py b/tests/saving/language_models/test_merged_model_perplexity_qwen_2.5.py
index cdbad174a4..701c5c3f6d 100644
--- a/tests/saving/language_models/test_merged_model_perplexity_qwen_2.5.py
+++ b/tests/saving/language_models/test_merged_model_perplexity_qwen_2.5.py
@@ -96,7 +96,9 @@ def load_and_compute_8bit_ppl(
# )
# Load dataset fresh in subprocess
- dataset_ppl = load_dataset("allenai/openassistant-guanaco-reformatted", split = "eval")
+ dataset_ppl = load_dataset(
+ "allenai/openassistant-guanaco-reformatted", split = "eval"
+ )
alpaca_prompt = """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.
@@ -182,8 +184,12 @@ if __name__ == "__main__":
attn_implementation = attn_implementation,
)
- dataset_train = load_dataset("allenai/openassistant-guanaco-reformatted", split = "train")
- dataset_ppl = load_dataset("allenai/openassistant-guanaco-reformatted", split = "eval")
+ dataset_train = load_dataset(
+ "allenai/openassistant-guanaco-reformatted", split = "train"
+ )
+ dataset_ppl = load_dataset(
+ "allenai/openassistant-guanaco-reformatted", split = "eval"
+ )
dataset_train = dataset_train.map(formatting_prompts_func, batched = True)
dataset_ppl = dataset_ppl.map(formatting_prompts_func, batched = True)
diff --git a/tests/saving/language_models/test_push_to_hub_merged.py b/tests/saving/language_models/test_push_to_hub_merged.py
index cacb1e2a10..857802b9f0 100644
--- a/tests/saving/language_models/test_push_to_hub_merged.py
+++ b/tests/saving/language_models/test_push_to_hub_merged.py
@@ -35,7 +35,9 @@ from tests.utils.perplexity_eval import (
def formatting_prompts_func(examples):
convos = examples["messages"]
texts = [
- tokenizer.apply_chat_template(convo, tokenize = False, add_generation_prompt = False)
+ tokenizer.apply_chat_template(
+ convo, tokenize = False, add_generation_prompt = False
+ )
for convo in convos
]
return {"text": texts}
@@ -173,7 +175,9 @@ try:
print("=== TESTING MODEL DOWNLOAD ===".center(80))
print("=" * 80 + "\n")
# Force download even if cached
- model, tokenizer = FastLanguageModel.from_pretrained(f"{hf_username}/merged_llama_text_model")
+ model, tokenizer = FastLanguageModel.from_pretrained(
+ f"{hf_username}/merged_llama_text_model"
+ )
success["download"] = True
print("✅ Model downloaded successfully!")
except Exception as e:
diff --git a/tests/saving/language_models/test_push_to_hub_merged_sharded_index_file.py b/tests/saving/language_models/test_push_to_hub_merged_sharded_index_file.py
index 59a09f06ec..4a3726dba3 100644
--- a/tests/saving/language_models/test_push_to_hub_merged_sharded_index_file.py
+++ b/tests/saving/language_models/test_push_to_hub_merged_sharded_index_file.py
@@ -36,7 +36,9 @@ from tests.utils.perplexity_eval import (
def formatting_prompts_func(examples):
convos = examples["messages"]
texts = [
- tokenizer.apply_chat_template(convo, tokenize = False, add_generation_prompt = False)
+ tokenizer.apply_chat_template(
+ convo, tokenize = False, add_generation_prompt = False
+ )
for convo in convos
]
return {"text": texts}
@@ -192,7 +194,9 @@ try:
print("=== TESTING MODEL DOWNLOAD ===".center(80))
print("=" * 80 + "\n")
# Force download even if cached
- model, tokenizer = FastLanguageModel.from_pretrained(f"{hf_username}/merged_llama_text_model")
+ model, tokenizer = FastLanguageModel.from_pretrained(
+ f"{hf_username}/merged_llama_text_model"
+ )
success["download"] = True
print("✅ Model downloaded successfully!")
except Exception as e:
diff --git a/tests/saving/language_models/test_save_merged_grpo_model.py b/tests/saving/language_models/test_save_merged_grpo_model.py
index 63e4b970a6..357cee537f 100644
--- a/tests/saving/language_models/test_save_merged_grpo_model.py
+++ b/tests/saving/language_models/test_save_merged_grpo_model.py
@@ -177,7 +177,9 @@ def training_run(result_queue):
avg_length = sum(lengths) / len(lengths)
min_length = min(lengths)
- print(f"Prompt lengths - Min: {min_length}, Max: {max_length}, Avg: {avg_length:.1f}")
+ print(
+ f"Prompt lengths - Min: {min_length}, Max: {max_length}, Avg: {avg_length:.1f}"
+ )
return max_length, avg_length
def extract_unsloth_answer(
@@ -268,7 +270,9 @@ def training_run(result_queue):
ground_truth_num = float(norm_ground_truth)
if ground_truth_num != 0:
- relative_error = abs(extracted_num - ground_truth_num) / abs(ground_truth_num)
+ relative_error = abs(extracted_num - ground_truth_num) / abs(
+ ground_truth_num
+ )
if relative_error < 0.01:
return True, True, 0.9
@@ -303,7 +307,10 @@ def training_run(result_queue):
)
responses = [completion[0]["content"] for completion in completions]
- rewards = [3.0 if re.match(pattern, response, re.DOTALL) else 0.0 for response in responses]
+ rewards = [
+ 3.0 if re.match(pattern, response, re.DOTALL) else 0.0
+ for response in responses
+ ]
return rewards
def match_format_approximately(completions, **kwargs):
@@ -401,7 +408,9 @@ def training_run(result_queue):
format_improvement = (
result["correct_format_pct"] - base_result["correct_format_pct"]
)
- exact_improvement = result["exact_match_pct"] - base_result["exact_match_pct"]
+ exact_improvement = (
+ result["exact_match_pct"] - base_result["exact_match_pct"]
+ )
plausible_improvement = (
result["plausible_match_pct"] - base_result["plausible_match_pct"]
)
@@ -433,7 +442,9 @@ def training_run(result_queue):
if torch.cuda.is_available():
allocated = torch.cuda.memory_allocated() / 1024**3
reserved = torch.cuda.memory_reserved() / 1024**3
- print(f"GPU memory - Allocated: {allocated:.2f} GB, Reserved: {reserved:.2f} GB")
+ print(
+ f"GPU memory - Allocated: {allocated:.2f} GB, Reserved: {reserved:.2f} GB"
+ )
"""#### Data Loading and Preparation"""
@@ -472,7 +483,9 @@ def training_run(result_queue):
def formatting_prompts_func(examples):
convos = examples["prompt"]
texts = [
- tokenizer.apply_chat_template(convo, tokenize = False, add_generation_prompt = False)
+ tokenizer.apply_chat_template(
+ convo, tokenize = False, add_generation_prompt = False
+ )
for convo in convos
]
return {
@@ -689,7 +702,9 @@ def training_run(result_queue):
print(f"{'='*60}")
try:
- model.save_pretrained_merged("final_merged_model", tokenizer, save_method = "merged_16bit")
+ model.save_pretrained_merged(
+ "final_merged_model", tokenizer, save_method = "merged_16bit"
+ )
print("✅ Merged model saved to: final_merged_model/")
except Exception as e:
print(f"⚠️ Could not save merged model: {e}")
diff --git a/tests/saving/test_fix_sentencepiece_gguf_robustness.py b/tests/saving/test_fix_sentencepiece_gguf_robustness.py
index 9c61ca4067..49bd70fa2f 100644
--- a/tests/saving/test_fix_sentencepiece_gguf_robustness.py
+++ b/tests/saving/test_fix_sentencepiece_gguf_robustness.py
@@ -44,7 +44,9 @@ def test_user_defined_special_piece_is_not_retyped(tmp_path):
]
(tmp_path / "tokenizer.model").write_bytes(_build(pieces))
(tmp_path / "tokenizer.json").write_text(
- json.dumps({"added_tokens": [{"id": 2, "content": "", "special": True}]})
+ json.dumps(
+ {"added_tokens": [{"id": 2, "content": "", "special": True}]}
+ )
)
fix_sentencepiece_gguf(str(tmp_path))
got = dict(_read(str(tmp_path / "tokenizer.model")))
@@ -85,7 +87,10 @@ 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":
+ 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)
diff --git a/tests/saving/test_preserve_tokenizer_eos_token.py b/tests/saving/test_preserve_tokenizer_eos_token.py
index 2ea40ab778..6e2f8c7f9d 100644
--- a/tests/saving/test_preserve_tokenizer_eos_token.py
+++ b/tests/saving/test_preserve_tokenizer_eos_token.py
@@ -16,7 +16,8 @@ def _load_preserve_helper():
helper = next(
node
for node in tree.body
- if isinstance(node, ast.FunctionDef) and node.name == "_preserve_tokenizer_eos_token"
+ if isinstance(node, ast.FunctionDef)
+ and node.name == "_preserve_tokenizer_eos_token"
)
module = ast.Module(body = [helper], type_ignores = [])
ast.fix_missing_locations(module)
@@ -45,7 +46,9 @@ def test_preserve_tokenizer_eos_token_supports_processor_tokenizer(tmp_path):
preserve = _load_preserve_helper()
tokenizer_config = tmp_path / "tokenizer_config.json"
tokenizer_config.write_text(json.dumps({"eos_token": ""}), encoding = "utf-8")
- processor = types.SimpleNamespace(tokenizer = types.SimpleNamespace(eos_token = ""))
+ processor = types.SimpleNamespace(
+ tokenizer = types.SimpleNamespace(eos_token = "")
+ )
preserve(processor, tmp_path)
diff --git a/tests/saving/test_save_shell_injection.py b/tests/saving/test_save_shell_injection.py
index b02748c250..c6c2c8fe15 100644
--- a/tests/saving/test_save_shell_injection.py
+++ b/tests/saving/test_save_shell_injection.py
@@ -19,7 +19,10 @@ def _assert_safe_ggml_calls(calls: list[ast.Call]) -> None:
popen_calls = []
for call in calls:
if isinstance(call.func, ast.Attribute) and call.func.attr == "Popen":
- if isinstance(call.func.value, ast.Name) and call.func.value.id == "subprocess":
+ if (
+ isinstance(call.func.value, ast.Name)
+ and call.func.value.id == "subprocess"
+ ):
popen_calls.append(call)
assert popen_calls, "Expected at least one subprocess.Popen call"
@@ -51,7 +54,9 @@ def _assert_safe_ggml_calls(calls: list[ast.Call]) -> None:
assert call.args, "subprocess.Popen must receive argv as a positional argument"
argv = call.args[0]
- assert isinstance(argv, ast.List), "subprocess.Popen must be called with an argv list"
+ assert isinstance(
+ argv, ast.List
+ ), "subprocess.Popen must be called with an argv list"
assert len(argv.elts) == 5, "GGML conversion argv should have five elements"
second_arg = argv.elts[1]
diff --git a/tests/saving/test_save_subprocess_utf8_encoding.py b/tests/saving/test_save_subprocess_utf8_encoding.py
index 4a609cd7b7..c81282369c 100644
--- a/tests/saving/test_save_subprocess_utf8_encoding.py
+++ b/tests/saving/test_save_subprocess_utf8_encoding.py
@@ -79,7 +79,9 @@ def _collect_text_mode_subprocess_calls() -> list[ast.Call]:
return [
node
for node in ast.walk(tree)
- if isinstance(node, ast.Call) and _is_subprocess_call(node) and _is_text_mode(node)
+ if isinstance(node, ast.Call)
+ and _is_subprocess_call(node)
+ and _is_text_mode(node)
]
diff --git a/tests/saving/test_unsloth_save.py b/tests/saving/test_unsloth_save.py
index a85c119e04..3ee2037f39 100644
--- a/tests/saving/test_unsloth_save.py
+++ b/tests/saving/test_unsloth_save.py
@@ -128,13 +128,19 @@ def test_save_merged_16bit(model, tokenizer, temp_save_dir: str):
model.config._name_or_path.replace("/", "_"),
)
- model.save_pretrained_merged(save_path, tokenizer = tokenizer, save_method = "merged_16bit")
+ model.save_pretrained_merged(
+ save_path, tokenizer = tokenizer, save_method = "merged_16bit"
+ )
assert os.path.isdir(save_path), f"Directory {save_path} does not exist."
- assert os.path.isfile(os.path.join(save_path, "config.json")), "config.json not found."
+ assert os.path.isfile(
+ os.path.join(save_path, "config.json")
+ ), "config.json not found."
weight_files = [
- f for f in os.listdir(save_path) if f.endswith(".bin") or f.endswith(".safetensors")
+ f
+ for f in os.listdir(save_path)
+ if f.endswith(".bin") or f.endswith(".safetensors")
]
assert len(weight_files) > 0, "No weight files found in the save directory."
@@ -148,7 +154,9 @@ def test_save_merged_16bit(model, tokenizer, temp_save_dir: str):
with open(config_path, "r") as f:
config = json.load(f)
- assert "quantization_config" not in config, "Quantization config not found in the model config."
+ assert (
+ "quantization_config" not in config
+ ), "Quantization config not found in the model config."
total_size = sum(os.path.getsize(os.path.join(save_path, f)) for f in weight_files)
save_file_sizes["merged_16bit"][model.config._name_or_path] = total_size
@@ -170,13 +178,19 @@ def test_save_merged_4bit(model, tokenizer, temp_save_dir: str):
model.config._name_or_path.replace("/", "_"),
)
- model.save_pretrained_merged(save_path, tokenizer = tokenizer, save_method = "merged_4bit_forced")
+ model.save_pretrained_merged(
+ save_path, tokenizer = tokenizer, save_method = "merged_4bit_forced"
+ )
assert os.path.isdir(save_path), f"Directory {save_path} does not exist."
- assert os.path.isfile(os.path.join(save_path, "config.json")), "config.json not found."
+ assert os.path.isfile(
+ os.path.join(save_path, "config.json")
+ ), "config.json not found."
weight_files = [
- f for f in os.listdir(save_path) if f.endswith(".bin") or f.endswith(".safetensors")
+ f
+ for f in os.listdir(save_path)
+ if f.endswith(".bin") or f.endswith(".safetensors")
]
assert len(weight_files) > 0, "No weight files found in the save directory."
@@ -199,7 +213,9 @@ def test_save_merged_4bit(model, tokenizer, temp_save_dir: str):
with open(config_path, "r") as f:
config = json.load(f)
- assert "quantization_config" in config, "Quantization config not found in the model config."
+ assert (
+ "quantization_config" in config
+ ), "Quantization config not found in the model config."
# Verify the saved model loads
loaded_model, loaded_tokenizer = FastModel.from_pretrained(
@@ -231,18 +247,28 @@ def test_save_torchao(fp16_model_tokenizer, temp_save_dir: str):
)
weight_files_16bit = [
- f for f in os.listdir(save_path) if f.endswith(".bin") or f.endswith(".safetensors")
+ f
+ for f in os.listdir(save_path)
+ if f.endswith(".bin") or f.endswith(".safetensors")
]
- total_16bit_size = sum(os.path.getsize(os.path.join(save_path, f)) for f in weight_files_16bit)
+ total_16bit_size = sum(
+ os.path.getsize(os.path.join(save_path, f)) for f in weight_files_16bit
+ )
save_file_sizes["merged_16bit"][model.config._name_or_path] = total_16bit_size
torchao_save_path = save_path + "-torchao"
- assert os.path.isdir(torchao_save_path), f"Directory {torchao_save_path} does not exist."
- assert os.path.isfile(os.path.join(torchao_save_path, "config.json")), "config.json not found."
+ assert os.path.isdir(
+ torchao_save_path
+ ), f"Directory {torchao_save_path} does not exist."
+ assert os.path.isfile(
+ os.path.join(torchao_save_path, "config.json")
+ ), "config.json not found."
weight_files = [
- f for f in os.listdir(torchao_save_path) if f.endswith(".bin") or f.endswith(".safetensors")
+ f
+ for f in os.listdir(torchao_save_path)
+ if f.endswith(".bin") or f.endswith(".safetensors")
]
assert len(weight_files) > 0, "No weight files found in the save directory."
@@ -251,7 +277,9 @@ def test_save_torchao(fp16_model_tokenizer, temp_save_dir: str):
os.path.join(torchao_save_path, file)
), f"{file} not found in the save directory."
- total_size = sum(os.path.getsize(os.path.join(torchao_save_path, f)) for f in weight_files)
+ total_size = sum(
+ os.path.getsize(os.path.join(torchao_save_path, f)) for f in weight_files
+ )
save_file_sizes["torchao"][model.config._name_or_path] = total_size
assert (
@@ -262,7 +290,9 @@ def test_save_torchao(fp16_model_tokenizer, temp_save_dir: str):
with open(config_path, "r") as f:
config = json.load(f)
- assert "quantization_config" in config, "Quantization config not found in the model config."
+ assert (
+ "quantization_config" in config
+ ), "Quantization config not found in the model config."
# load_in_4bit must stay False: a torchao-quantized model can't be
# re-quantized with bitsandbytes.
@@ -287,7 +317,9 @@ def test_save_and_inference_torchao(fp16_model_tokenizer, temp_save_dir: str):
print(f"Testing TorchAO save and inference for: {model_name}")
- save_path = os.path.join(temp_save_dir, "torchao_models", model_name.replace("/", "_"))
+ save_path = os.path.join(
+ temp_save_dir, "torchao_models", model_name.replace("/", "_")
+ )
from torchao.quantization import Int8DynamicActivationInt8WeightConfig
diff --git a/tests/saving/text_to_speech_models/test_csm.py b/tests/saving/text_to_speech_models/test_csm.py
index 3d04bc39cb..3e4b2d6430 100644
--- a/tests/saving/text_to_speech_models/test_csm.py
+++ b/tests/saving/text_to_speech_models/test_csm.py
@@ -134,7 +134,9 @@ import torch
output_audio_path = "csm_audio.wav"
try:
- text = "We just finished fine tuning a text to speech model... and it's pretty good!"
+ text = (
+ "We just finished fine tuning a text to speech model... and it's pretty good!"
+ )
speaker_id = 0
inputs = processor(f"[{speaker_id}]{text}", add_special_tokens = True).to("cuda")
audio_values = model.generate(
diff --git a/tests/saving/text_to_speech_models/test_lasa.py b/tests/saving/text_to_speech_models/test_lasa.py
index c0c4f80e0e..804ff512f9 100644
--- a/tests/saving/text_to_speech_models/test_lasa.py
+++ b/tests/saving/text_to_speech_models/test_lasa.py
@@ -167,7 +167,9 @@ def extract_speech_ids(speech_tokens_str):
# TTS start!
with torch.inference_mode():
with torch.amp.autocast("cuda", dtype = model.dtype):
- formatted_text = f"<|TEXT_UNDERSTANDING_START|>{input_text}<|TEXT_UNDERSTANDING_END|>"
+ formatted_text = (
+ f"<|TEXT_UNDERSTANDING_START|>{input_text}<|TEXT_UNDERSTANDING_END|>"
+ )
# Tokenize the text
chat = [
diff --git a/tests/saving/text_to_speech_models/test_orpheus.py b/tests/saving/text_to_speech_models/test_orpheus.py
index 83adaf8dbc..c748f023a5 100644
--- a/tests/saving/text_to_speech_models/test_orpheus.py
+++ b/tests/saving/text_to_speech_models/test_orpheus.py
@@ -151,7 +151,9 @@ for prompt in prompts_:
all_input_ids.append(input_ids)
start_token = torch.tensor([[128259]], dtype = torch.int64) # Start of human
-end_tokens = torch.tensor([[128009, 128260]], dtype = torch.int64) # End of text, End of human
+end_tokens = torch.tensor(
+ [[128009, 128260]], dtype = torch.int64
+) # End of text, End of human
all_modified_input_ids = []
for input_ids in all_input_ids:
@@ -162,7 +164,9 @@ for input_ids in all_input_ids:
all_padded_tensors = []
all_attention_masks = []
-max_length = max([modified_input_ids.shape[1] for modified_input_ids in all_modified_input_ids])
+max_length = max(
+ [modified_input_ids.shape[1] for modified_input_ids in all_modified_input_ids]
+)
for modified_input_ids in all_modified_input_ids:
padding = max_length - modified_input_ids.shape[1]
padded_tensor = torch.cat(
diff --git a/tests/saving/text_to_speech_models/test_whisper.py b/tests/saving/text_to_speech_models/test_whisper.py
index e0271d098e..3fa44927b8 100644
--- a/tests/saving/text_to_speech_models/test_whisper.py
+++ b/tests/saving/text_to_speech_models/test_whisper.py
@@ -179,9 +179,13 @@ expected_phrases = [
]
transcribed_lower = transcribed_text["text"].lower()
-all_phrases_found = all(phrase.lower() in transcribed_lower for phrase in expected_phrases)
+all_phrases_found = all(
+ phrase.lower() in transcribed_lower for phrase in expected_phrases
+)
-assert all_phrases_found, f"Expected phrases not found in transcription: {transcribed_text['text']}"
+assert (
+ all_phrases_found
+), f"Expected phrases not found in transcription: {transcribed_text['text']}"
print("✅ Transcription contains all expected phrases!")
diff --git a/tests/saving/vision_models/test_index_file_sharded_model.py b/tests/saving/vision_models/test_index_file_sharded_model.py
index 6f2d0ff782..048df45469 100644
--- a/tests/saving/vision_models/test_index_file_sharded_model.py
+++ b/tests/saving/vision_models/test_index_file_sharded_model.py
@@ -128,7 +128,9 @@ try:
per_device_train_batch_size = 2,
gradient_accumulation_steps = 4,
gradient_checkpointing = True,
- gradient_checkpointing_kwargs = {"use_reentrant": False}, # use reentrant checkpointing
+ gradient_checkpointing_kwargs = {
+ "use_reentrant": False
+ }, # use reentrant checkpointing
max_grad_norm = 0.3, # max gradient norm based on QLoRA paper
warmup_ratio = 0.03,
# num_train_epochs = 2, # Set this instead of max_steps for full training runs
diff --git a/tests/saving/vision_models/test_push_to_hub_merged.py b/tests/saving/vision_models/test_push_to_hub_merged.py
index d83c66de4b..ea1ec0d191 100644
--- a/tests/saving/vision_models/test_push_to_hub_merged.py
+++ b/tests/saving/vision_models/test_push_to_hub_merged.py
@@ -138,7 +138,9 @@ try:
per_device_train_batch_size = 2,
gradient_accumulation_steps = 4,
gradient_checkpointing = True,
- gradient_checkpointing_kwargs = {"use_reentrant": False}, # use reentrant checkpointing
+ gradient_checkpointing_kwargs = {
+ "use_reentrant": False
+ }, # use reentrant checkpointing
max_grad_norm = 0.3, # max gradient norm based on QLoRA paper
warmup_ratio = 0.03,
# num_train_epochs = 2, # Set this instead of max_steps for full training runs
diff --git a/tests/saving/vision_models/test_save_merge_qwen2.5vl32B_model_ocr_benchmark.py b/tests/saving/vision_models/test_save_merge_qwen2.5vl32B_model_ocr_benchmark.py
index 548e6bfc37..84e2d4ea2b 100644
--- a/tests/saving/vision_models/test_save_merge_qwen2.5vl32B_model_ocr_benchmark.py
+++ b/tests/saving/vision_models/test_save_merge_qwen2.5vl32B_model_ocr_benchmark.py
@@ -128,7 +128,9 @@ trainer = SFTTrainer(
per_device_train_batch_size = 2,
gradient_accumulation_steps = 4,
gradient_checkpointing = True,
- gradient_checkpointing_kwargs = {"use_reentrant": False}, # use reentrant checkpointing
+ gradient_checkpointing_kwargs = {
+ "use_reentrant": False
+ }, # use reentrant checkpointing
max_grad_norm = 0.3, # max gradient norm based on QLoRA paper
warmup_ratio = 0.03,
# num_train_epochs = 2, # Set this instead of max_steps for full training runs
diff --git a/tests/saving/vision_models/test_save_merge_vision_model_ocr_benchmark.py b/tests/saving/vision_models/test_save_merge_vision_model_ocr_benchmark.py
index 66064600ea..8351cb8282 100644
--- a/tests/saving/vision_models/test_save_merge_vision_model_ocr_benchmark.py
+++ b/tests/saving/vision_models/test_save_merge_vision_model_ocr_benchmark.py
@@ -124,7 +124,9 @@ trainer = SFTTrainer(
per_device_train_batch_size = 2,
gradient_accumulation_steps = 4,
gradient_checkpointing = True,
- gradient_checkpointing_kwargs = {"use_reentrant": False}, # use reentrant checkpointing
+ gradient_checkpointing_kwargs = {
+ "use_reentrant": False
+ }, # use reentrant checkpointing
max_grad_norm = 0.3, # max gradient norm based on QLoRA paper
warmup_ratio = 0.03,
# num_train_epochs = 2, # Set this instead of max_steps for full training runs
diff --git a/tests/security/test_lockfile_supply_chain_audit.py b/tests/security/test_lockfile_supply_chain_audit.py
index 905c4afcac..1e5d950a45 100644
--- a/tests/security/test_lockfile_supply_chain_audit.py
+++ b/tests/security/test_lockfile_supply_chain_audit.py
@@ -254,7 +254,9 @@ def test_advisory_finding_emitted_as_single_line_annotation(tmp_path):
npm_lockfiles = [FIXTURES / "clean_lockfile.json"],
cargo_lockfiles = [lockfile],
)
- warning_lines = [line for line in proc.stderr.splitlines() if line.startswith("::warning::")]
+ warning_lines = [
+ line for line in proc.stderr.splitlines() if line.startswith("::warning::")
+ ]
assert warning_lines, (
"expected at least one ::warning:: annotation; " f"stderr was:\n{proc.stderr}"
)
diff --git a/tests/security/test_new_install_scripts.py b/tests/security/test_new_install_scripts.py
index e71659dd88..e27d778651 100644
--- a/tests/security/test_new_install_scripts.py
+++ b/tests/security/test_new_install_scripts.py
@@ -104,7 +104,9 @@ def test_new_dep_with_postinstall_exits_1(tmp_path: Path):
head_pkgs = dict(base_pkgs)
head_pkgs["node_modules/evil-postinstall"] = {
"version": "1.0.0",
- "resolved": ("https://registry.npmjs.org/evil-postinstall/-/evil-postinstall-1.0.0.tgz"),
+ "resolved": (
+ "https://registry.npmjs.org/evil-postinstall/-/evil-postinstall-1.0.0.tgz"
+ ),
"integrity": "sha512-fake",
"hasInstallScript": True,
}
@@ -184,7 +186,8 @@ def test_v2_v3_lockfile_format_support(tmp_path: Path):
head = _write(tmp_path / "head.json", _v2_lockfile(head_pkgs, head_deps))
result = _run(base, head)
assert result.returncode == 1, (
- f"expected exit 1 for v2 lockfile, got {result.returncode}; " f"stderr:\n{result.stderr}"
+ f"expected exit 1 for v2 lockfile, got {result.returncode}; "
+ f"stderr:\n{result.stderr}"
)
assert "v2-postinstall-dep" in result.stderr
diff --git a/tests/security/test_scan_npm_packages.py b/tests/security/test_scan_npm_packages.py
index 2c79b9c3ed..bf04847b13 100644
--- a/tests/security/test_scan_npm_packages.py
+++ b/tests/security/test_scan_npm_packages.py
@@ -101,10 +101,16 @@ def test_blocked_npm_versions_complete():
table = snp.BLOCKED_NPM_VERSIONS
tanstack_keys = [k for k in table if k.startswith("@tanstack/")]
assert len(tanstack_keys) == 42, (
- f"expected 42 @tanstack/* entries, got {len(tanstack_keys)}: " f"{sorted(tanstack_keys)}"
+ f"expected 42 @tanstack/* entries, got {len(tanstack_keys)}: "
+ f"{sorted(tanstack_keys)}"
)
assert "@opensearch-project/opensearch" in table
- assert table["@opensearch-project/opensearch"] == {"3.5.3", "3.6.2", "3.7.0", "3.8.0"}
+ assert table["@opensearch-project/opensearch"] == {
+ "3.5.3",
+ "3.6.2",
+ "3.7.0",
+ "3.8.0",
+ }
squawk = [k for k in table if k.startswith("@squawk/")]
assert len(squawk) >= 22, (
f"expected at least 22 @squawk/* entries (full safedep.io enumeration), "
diff --git a/tests/security/test_scan_packages.py b/tests/security/test_scan_packages.py
index 5b6e115213..a6301c749e 100644
--- a/tests/security/test_scan_packages.py
+++ b/tests/security/test_scan_packages.py
@@ -116,7 +116,9 @@ def test_clean_wheel_no_findings():
str(FIXTURES / "clean_wheel.whl"),
"clean_fixture",
)
- assert findings == [], f"unexpected findings on clean wheel: {[str(f) for f in findings]}"
+ assert (
+ findings == []
+ ), f"unexpected findings on clean wheel: {[str(f) for f in findings]}"
# ---------------------------------------------------------------------------
@@ -231,7 +233,8 @@ def test_archive_corruption_produces_critical_finding(tmp_path):
assert findings, "scan_archive returned 0 findings on corrupt wheel"
corrupted = [f for f in findings if f.check == "archive_corrupted"]
assert corrupted, (
- "no archive_corrupted finding; got " f"{[(f.severity, f.check) for f in findings]}"
+ "no archive_corrupted finding; got "
+ f"{[(f.severity, f.check) for f in findings]}"
)
assert all(f.severity == sp.CRITICAL for f in corrupted)
diff --git a/tests/studio/_playwright_robust.py b/tests/studio/_playwright_robust.py
index bc0e3f783b..7fe94df613 100644
--- a/tests/studio/_playwright_robust.py
+++ b/tests/studio/_playwright_robust.py
@@ -149,7 +149,9 @@ def wait_for_health(
# but accept any 200 -- different Studio builds report differently.
if status == 200:
if info is not None:
- info(f"health pre-flight OK: status=200, body keys={list((body or {}).keys())}")
+ info(
+ f"health pre-flight OK: status=200, body keys={list((body or {}).keys())}"
+ )
return True
time.sleep(0.5)
if info is not None:
@@ -188,7 +190,9 @@ def recover_or_replace_page(
info(f"recovery: page.is_closed() check failed: {exc!r}")
if goto_url is not None:
try:
- page.goto(goto_url, wait_until = "domcontentloaded", timeout = default_timeout_ms)
+ page.goto(
+ goto_url, wait_until = "domcontentloaded", timeout = default_timeout_ms
+ )
if settle_networkidle:
try:
page.wait_for_load_state("networkidle", timeout = 30_000)
diff --git a/tests/studio/install/smoke_test_llama_prebuilt.py b/tests/studio/install/smoke_test_llama_prebuilt.py
index f7fd58aaa4..d87537dc94 100644
--- a/tests/studio/install/smoke_test_llama_prebuilt.py
+++ b/tests/studio/install/smoke_test_llama_prebuilt.py
@@ -15,7 +15,9 @@ INSTALLER_PATH = PACKAGE_ROOT / "studio" / "install_llama_prebuilt.py"
def load_installer_module():
- spec = importlib.util.spec_from_file_location("studio_install_llama_prebuilt", INSTALLER_PATH)
+ spec = importlib.util.spec_from_file_location(
+ "studio_install_llama_prebuilt", INSTALLER_PATH
+ )
if spec is None or spec.loader is None:
raise RuntimeError(f"unable to load installer module from {INSTALLER_PATH}")
module = importlib.util.module_from_spec(spec)
@@ -110,13 +112,17 @@ def main() -> int:
published_release_tag = args.published_release_tag,
)
print(f"[smoke] PASS install_dir={install_dir}")
- print("[smoke] note=This was a real prebuilt install into an isolated temp directory.")
+ print(
+ "[smoke] note=This was a real prebuilt install into an isolated temp directory."
+ )
return installer.EXIT_SUCCESS
except SystemExit as exc:
code = int(exc.code) if isinstance(exc.code, int) else installer.EXIT_ERROR
if code == installer.EXIT_FALLBACK:
print(f"[smoke] FALLBACK install_dir={install_dir}")
- print("[smoke] note=Prebuilt path failed and would fall back to source build in setup.")
+ print(
+ "[smoke] note=Prebuilt path failed and would fall back to source build in setup."
+ )
print(installer.collect_system_report(host, choice, install_dir))
else:
print(f"[smoke] ERROR exit_code={code} install_dir={install_dir}")
diff --git a/tests/studio/install/smoke_test_parallel_studio_home.py b/tests/studio/install/smoke_test_parallel_studio_home.py
index be01147740..318a39dc74 100644
--- a/tests/studio/install/smoke_test_parallel_studio_home.py
+++ b/tests/studio/install/smoke_test_parallel_studio_home.py
@@ -65,7 +65,12 @@ def _free_port() -> int:
def _run_one_install(
- label: str, repo: Path, studio_home: Path, fake_home: Path, uv_cache: Path, log_path: Path
+ label: str,
+ repo: Path,
+ studio_home: Path,
+ fake_home: Path,
+ uv_cache: Path,
+ log_path: Path,
) -> tuple[str, int]:
studio_home.mkdir(parents = True, exist_ok = True)
fake_home.mkdir(parents = True, exist_ok = True)
@@ -133,7 +138,9 @@ def _wait_for_health(port: int, timeout: float) -> dict:
except (urllib.error.URLError, ConnectionError, OSError) as e:
last_err = e
time.sleep(HEALTH_POLL_INTERVAL_S)
- raise TestFailure(f"port {port}: /api/health never returned 200 (last_err={last_err})")
+ raise TestFailure(
+ f"port {port}: /api/health never returned 200 (last_err={last_err})"
+ )
def _http_status(
@@ -187,7 +194,9 @@ def _check_install_layout(label: str, studio_home: Path) -> dict:
raise TestFailure(f"[{label}] launch-studio.sh kept @@DATA_DIR@@ placeholder")
expected_data_dir_line = f"DATA_DIR='{studio_home}/share'"
if expected_data_dir_line not in launcher:
- raise TestFailure(f"[{label}] launch-studio.sh missing {expected_data_dir_line!r}")
+ raise TestFailure(
+ f"[{label}] launch-studio.sh missing {expected_data_dir_line!r}"
+ )
return {"label": label, "studio_home": str(studio_home), "install_id": install_id}
@@ -204,7 +213,9 @@ def _check_fake_home_clean(fake_home: Path) -> None:
]
leaked = [str(p) for p in forbidden if (fake_home / p).exists()]
if leaked:
- raise TestFailure(f"redirected HOME picked up persistent install pollution: {leaked}")
+ raise TestFailure(
+ f"redirected HOME picked up persistent install pollution: {leaked}"
+ )
def _backend_pid_python(pid: int) -> Path | None:
@@ -228,7 +239,9 @@ def run(n_installs: int, keep: bool) -> int:
repo = PACKAGE_ROOT
if not (repo / "install.sh").is_file():
- raise TestFailure(f"install.sh not found at {repo}; run from a clone of unslothai/unsloth")
+ raise TestFailure(
+ f"install.sh not found at {repo}; run from a clone of unslothai/unsloth"
+ )
test_root = Path(tempfile.mkdtemp(prefix = "unsloth_studio_clash_"))
_log(f"test root: {test_root}")
@@ -316,7 +329,8 @@ def run(n_installs: int, keep: bool) -> int:
raise TestFailure(f"[{label}] chat_only is not true under --no-torch")
if health["studio_root_id"] in seen_root_ids:
raise TestFailure(
- f"[{label}] studio_root_id collision at runtime: " f"{health['studio_root_id']}"
+ f"[{label}] studio_root_id collision at runtime: "
+ f"{health['studio_root_id']}"
)
seen_root_ids.add(health["studio_root_id"])
@@ -327,7 +341,9 @@ def run(n_installs: int, keep: bool) -> int:
exe = _backend_pid_python(proc.pid)
if exe is not None:
- expected_python = (studio_home / "unsloth_studio" / "bin" / "python").resolve()
+ expected_python = (
+ studio_home / "unsloth_studio" / "bin" / "python"
+ ).resolve()
if exe != expected_python:
raise TestFailure(
f"[{label}] PID {proc.pid} exe={exe}, expected {expected_python}"
@@ -337,7 +353,10 @@ def run(n_installs: int, keep: bool) -> int:
if len(versions) != 1:
raise TestFailure(f"version mismatch across installs: {versions}")
- _log(f"PASS: all install + runtime invariants hold " f"(version={next(iter(versions))})")
+ _log(
+ f"PASS: all install + runtime invariants hold "
+ f"(version={next(iter(versions))})"
+ )
return 0
except TestFailure as e:
diff --git a/tests/studio/install/test_cuda_repair.py b/tests/studio/install/test_cuda_repair.py
index 83c2d962e8..ac3a5a7ff2 100644
--- a/tests/studio/install/test_cuda_repair.py
+++ b/tests/studio/install/test_cuda_repair.py
@@ -19,7 +19,9 @@ import pytest
PACKAGE_ROOT = Path(__file__).resolve().parents[3]
_STACK_PATH = PACKAGE_ROOT / "studio" / "install_python_stack.py"
-_STACK_SPEC = importlib.util.spec_from_file_location("studio_install_python_stack", _STACK_PATH)
+_STACK_SPEC = importlib.util.spec_from_file_location(
+ "studio_install_python_stack", _STACK_PATH
+)
assert _STACK_SPEC is not None and _STACK_SPEC.loader is not None
stack_mod = importlib.util.module_from_spec(_STACK_SPEC)
sys.modules[_STACK_SPEC.name] = stack_mod
@@ -54,7 +56,9 @@ def _make_run(
return result
# nvidia-smi version probe (text = True)
result.returncode = smi_rc
- out = f"CUDA Version: {cuda_version}\n" if cuda_version else "No devices found\n"
+ out = (
+ f"CUDA Version: {cuda_version}\n" if cuda_version else "No devices found\n"
+ )
result.stdout = out if kwargs.get("text") else out.encode()
return result
diff --git a/tests/studio/install/test_gpu_detection_followups.py b/tests/studio/install/test_gpu_detection_followups.py
index 983969a4d3..3386dda3a6 100644
--- a/tests/studio/install/test_gpu_detection_followups.py
+++ b/tests/studio/install/test_gpu_detection_followups.py
@@ -91,9 +91,15 @@ def _run_detect_host(
patches = [
patch.object(prebuilt_mod.platform, "system", return_value = system),
patch.object(prebuilt_mod.platform, "machine", return_value = machine),
- patch.object(prebuilt_mod.platform, "mac_ver", return_value = ("", ("", "", ""), "")),
- patch.object(prebuilt_mod.shutil, "which", side_effect = lambda n: which_map.get(n)),
- patch.object(prebuilt_mod, "run_capture", side_effect = _make_run_capture(rocminfo_stdout)),
+ patch.object(
+ prebuilt_mod.platform, "mac_ver", return_value = ("", ("", "", ""), "")
+ ),
+ patch.object(
+ prebuilt_mod.shutil, "which", side_effect = lambda n: which_map.get(n)
+ ),
+ patch.object(
+ prebuilt_mod, "run_capture", side_effect = _make_run_capture(rocminfo_stdout)
+ ),
patch.object(prebuilt_mod.os.path, "isdir", side_effect = fake_isdir),
patch.object(prebuilt_mod.os, "listdir", side_effect = fake_listdir),
patch.object(prebuilt_mod.os, "access", return_value = False),
@@ -326,7 +332,9 @@ class TestBackendExportLeafClassification:
out = sp.run(
["sh", str(script), url], capture_output = True, text = True, timeout = 30
).stdout.strip()
- assert out == expected, f"{url} classified as {out!r}, expected {expected!r}"
+ assert (
+ out == expected
+ ), f"{url} classified as {out!r}, expected {expected!r}"
# TEST: CUDA_VISIBLE_DEVICES=""/-1 hides NVIDIA in every usable-GPU helper
@@ -414,7 +422,9 @@ class TestHiddenCvdNotUsable:
with (
patch.object(stack_mod.shutil, "which", side_effect = which_map.get),
patch.object(stack_mod.subprocess, "run", side_effect = fake_run),
- patch.dict(stack_mod.os.environ, {"CUDA_VISIBLE_DEVICES": "-1"}, clear = False),
+ patch.dict(
+ stack_mod.os.environ, {"CUDA_VISIBLE_DEVICES": "-1"}, clear = False
+ ),
):
assert stack_mod._has_rocm_gpu() is True
@@ -474,7 +484,11 @@ class TestHiddenCvdNotUsable:
out = self._run_sh_helper(
tmp_path,
src,
- ["_setup_run_smi", "_setup_cvd_hides_nvidia", "_setup_has_usable_nvidia_gpu"],
+ [
+ "_setup_run_smi",
+ "_setup_cvd_hides_nvidia",
+ "_setup_has_usable_nvidia_gpu",
+ ],
cvd,
)
assert out == expected
diff --git a/tests/studio/install/test_hf_auth.py b/tests/studio/install/test_hf_auth.py
index 7c3296d4fd..b516b6641a 100644
--- a/tests/studio/install/test_hf_auth.py
+++ b/tests/studio/install/test_hf_auth.py
@@ -28,7 +28,9 @@ sys.modules[_SPEC.name] = mod
_SPEC.loader.exec_module(mod)
_TOKEN_VARS = ("GH_TOKEN", "GITHUB_TOKEN", "HF_TOKEN", "HUGGING_FACE_HUB_TOKEN")
-HF_URL = "https://huggingface.co/ggml-org/models/resolve/main/tinyllamas/stories260K.gguf"
+HF_URL = (
+ "https://huggingface.co/ggml-org/models/resolve/main/tinyllamas/stories260K.gguf"
+)
GH_URL = "https://api.github.com/repos/unslothai/llama.cpp/releases"
diff --git a/tests/studio/install/test_install_llama_prebuilt_logic.py b/tests/studio/install/test_install_llama_prebuilt_logic.py
index 9ccfe15a88..6b619cb392 100644
--- a/tests/studio/install/test_install_llama_prebuilt_logic.py
+++ b/tests/studio/install/test_install_llama_prebuilt_logic.py
@@ -13,7 +13,9 @@ import pytest
PACKAGE_ROOT = Path(__file__).resolve().parents[3]
MODULE_PATH = PACKAGE_ROOT / "studio" / "install_llama_prebuilt.py"
-SPEC = importlib.util.spec_from_file_location("studio_install_llama_prebuilt", MODULE_PATH)
+SPEC = importlib.util.spec_from_file_location(
+ "studio_install_llama_prebuilt", MODULE_PATH
+)
assert SPEC is not None and SPEC.loader is not None
INSTALL_LLAMA_PREBUILT = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = INSTALL_LLAMA_PREBUILT
@@ -211,7 +213,9 @@ def test_hydrate_source_tree_extracts_upstream_archive_contents(
def test_release_asset_download_url():
fn = INSTALL_LLAMA_PREBUILT.release_asset_download_url
assert fn(
- "unslothai/llama.cpp", "b9000-mix-abc1234", "llama.cpp-source-commit-deadbeef.tar.gz"
+ "unslothai/llama.cpp",
+ "b9000-mix-abc1234",
+ "llama.cpp-source-commit-deadbeef.tar.gz",
) == (
"https://github.com/unslothai/llama.cpp/releases/download/"
"b9000-mix-abc1234/llama.cpp-source-commit-deadbeef.tar.gz"
@@ -225,14 +229,18 @@ def test_release_asset_download_url():
def _mk_source_tarball(path: Path, tag: str) -> None:
with tarfile.open(path, "w:gz") as archive:
add_bytes_to_tar(
- archive, f"llama.cpp-{tag}/CMakeLists.txt", b"cmake_minimum_required(VERSION 3.14)\n"
+ archive,
+ f"llama.cpp-{tag}/CMakeLists.txt",
+ b"cmake_minimum_required(VERSION 3.14)\n",
)
add_bytes_to_tar(
archive,
f"llama.cpp-{tag}/convert_hf_to_gguf.py",
b"#!/usr/bin/env python3\nimport gguf\n",
)
- add_bytes_to_tar(archive, f"llama.cpp-{tag}/gguf-py/gguf/__init__.py", b"__all__ = []\n")
+ add_bytes_to_tar(
+ archive, f"llama.cpp-{tag}/gguf-py/gguf/__init__.py", b"__all__ = []\n"
+ )
def test_hydrate_source_tree_prefers_release_asset_for_mix(
@@ -244,7 +252,9 @@ def test_hydrate_source_tree_prefers_release_asset_for_mix(
archive_path = tmp_path / "merged-source.tar.gz"
_mk_source_tarball(archive_path, f"b9000-mix-{commit[:7]}")
asset_url = INSTALL_LLAMA_PREBUILT.release_asset_download_url(
- "unslothai/llama.cpp", "b9000-mix-abc1234", f"llama.cpp-source-commit-{commit}.tar.gz"
+ "unslothai/llama.cpp",
+ "b9000-mix-abc1234",
+ f"llama.cpp-source-commit-{commit}.tar.gz",
)
codeload_urls = set(
INSTALL_LLAMA_PREBUILT.commit_source_archive_urls("unslothai/llama.cpp", commit)
@@ -254,7 +264,9 @@ def test_hydrate_source_tree_prefers_release_asset_for_mix(
def fake_download_file(url: str, destination: Path) -> None:
seen.append(url)
if url in codeload_urls:
- raise AssertionError("codeload was hit even though the release asset was available")
+ raise AssertionError(
+ "codeload was hit even though the release asset was available"
+ )
assert url == asset_url
destination.write_bytes(archive_path.read_bytes())
@@ -287,7 +299,9 @@ def test_hydrate_source_tree_falls_back_to_codeload_when_asset_missing(
asset_url = INSTALL_LLAMA_PREBUILT.release_asset_download_url(
"unslothai/llama.cpp", "b9000", f"llama.cpp-source-commit-{commit}.tar.gz"
)
- codeload_urls = INSTALL_LLAMA_PREBUILT.commit_source_archive_urls("unslothai/llama.cpp", commit)
+ codeload_urls = INSTALL_LLAMA_PREBUILT.commit_source_archive_urls(
+ "unslothai/llama.cpp", commit
+ )
def fake_download_file(url: str, destination: Path) -> None:
if url == asset_url:
@@ -378,8 +392,12 @@ def test_validate_prebuilt_choice_creates_repo_shaped_linux_install(
"preflight_linux_installed_binaries",
lambda *args, **kwargs: None,
)
- monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "validate_quantize", lambda *args, **kwargs: None)
- monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "validate_server", lambda *args, **kwargs: None)
+ monkeypatch.setattr(
+ INSTALL_LLAMA_PREBUILT, "validate_quantize", lambda *args, **kwargs: None
+ )
+ monkeypatch.setattr(
+ INSTALL_LLAMA_PREBUILT, "validate_server", lambda *args, **kwargs: None
+ )
host = HostInfo(
system = "Linux",
@@ -495,8 +513,12 @@ def test_validate_prebuilt_choice_creates_repo_shaped_windows_install(
"preflight_linux_installed_binaries",
lambda *args, **kwargs: None,
)
- monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "validate_quantize", lambda *args, **kwargs: None)
- monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "validate_server", lambda *args, **kwargs: None)
+ monkeypatch.setattr(
+ INSTALL_LLAMA_PREBUILT, "validate_quantize", lambda *args, **kwargs: None
+ )
+ monkeypatch.setattr(
+ INSTALL_LLAMA_PREBUILT, "validate_server", lambda *args, **kwargs: None
+ )
host = HostInfo(
system = "Windows",
@@ -587,7 +609,9 @@ def test_activate_install_tree_restores_existing_install_after_activation_failur
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"confirm_install_tree",
- lambda *_args, **_kwargs: (_ for _ in ()).throw(RuntimeError("activation confirm failed")),
+ lambda *_args, **_kwargs: (_ for _ in ()).throw(
+ RuntimeError("activation confirm failed")
+ ),
)
with pytest.raises(
@@ -636,7 +660,9 @@ def test_activate_install_tree_cleans_all_paths_when_rollback_restore_fails(
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"confirm_install_tree",
- lambda *_args, **_kwargs: (_ for _ in ()).throw(RuntimeError("activation confirm failed")),
+ lambda *_args, **_kwargs: (_ for _ in ()).throw(
+ RuntimeError("activation confirm failed")
+ ),
)
original_replace = INSTALL_LLAMA_PREBUILT.os.replace
@@ -663,7 +689,10 @@ def test_activate_install_tree_cleans_all_paths_when_rollback_restore_fails(
captured = capsys.readouterr()
output = captured.out + captured.err
assert "rollback after failed activation also failed: restore failed" in output
- assert "cleaning staging, install, and rollback paths before source build fallback" in output
+ assert (
+ "cleaning staging, install, and rollback paths before source build fallback"
+ in output
+ )
assert "removing failed install path" in output
assert "removing rollback path" in output
@@ -889,7 +918,9 @@ def write_linux_install_shape(install_dir: Path) -> None:
(runtime_dir / "libggml-base.so.0").write_bytes(b"DLL")
(runtime_dir / "libggml-cpu-x64.so.0").write_bytes(b"DLL")
(runtime_dir / "libmtmd.so.0").write_bytes(b"DLL")
- (install_dir / "convert_hf_to_gguf.py").write_text("#!/usr/bin/env python3\n", encoding = "utf-8")
+ (install_dir / "convert_hf_to_gguf.py").write_text(
+ "#!/usr/bin/env python3\n", encoding = "utf-8"
+ )
(install_dir / "gguf-py" / "gguf").mkdir(parents = True, exist_ok = True)
@@ -913,7 +944,9 @@ def write_windows_install_shape(
(runtime_dir / "cudart64_12.dll").write_bytes(b"DLL")
(runtime_dir / "cublas64_12.dll").write_bytes(b"DLL")
(runtime_dir / "cublasLt64_12.dll").write_bytes(b"DLL")
- (install_dir / "convert_hf_to_gguf.py").write_text("#!/usr/bin/env python3\n", encoding = "utf-8")
+ (install_dir / "convert_hf_to_gguf.py").write_text(
+ "#!/usr/bin/env python3\n", encoding = "utf-8"
+ )
(install_dir / "gguf-py" / "gguf").mkdir(parents = True, exist_ok = True)
@@ -936,7 +969,9 @@ def write_macos_install_shape(
(runtime_dir / "libggml.0.dylib").write_bytes(b"DLL")
if include_libmtmd:
(runtime_dir / "libmtmd.0.dylib").write_bytes(b"DLL")
- (install_dir / "convert_hf_to_gguf.py").write_text("#!/usr/bin/env python3\n", encoding = "utf-8")
+ (install_dir / "convert_hf_to_gguf.py").write_text(
+ "#!/usr/bin/env python3\n", encoding = "utf-8"
+ )
(install_dir / "gguf-py" / "gguf").mkdir(parents = True, exist_ok = True)
@@ -1015,7 +1050,8 @@ def test_existing_install_matches_plan_false_without_fingerprint(tmp_path: Path)
install_dir.mkdir()
write_linux_install_shape(install_dir)
(install_dir / "UNSLOTH_PREBUILT_INFO.json").write_text(
- json.dumps({"tag": "b9001", "asset": "llama-b9001-bin-ubuntu-x64.tar.gz"}) + "\n",
+ json.dumps({"tag": "b9001", "asset": "llama-b9001-bin-ubuntu-x64.tar.gz"})
+ + "\n",
encoding = "utf-8",
)
@@ -1078,7 +1114,9 @@ def test_existing_install_matches_plan_false_with_malformed_metadata(tmp_path: P
install_dir = tmp_path / "llama.cpp"
install_dir.mkdir()
write_linux_install_shape(install_dir)
- (install_dir / "UNSLOTH_PREBUILT_INFO.json").write_text("{not-json\n", encoding = "utf-8")
+ (install_dir / "UNSLOTH_PREBUILT_INFO.json").write_text(
+ "{not-json\n", encoding = "utf-8"
+ )
host = HostInfo(
system = "Linux",
@@ -1209,7 +1247,9 @@ def test_existing_install_matches_plan_windows_cpu_requires_llama_dll(tmp_path:
def test_existing_install_matches_plan_windows_cuda_requires_cuda_dll(tmp_path: Path):
install_dir = tmp_path / "llama.cpp"
install_dir.mkdir()
- write_windows_install_shape(install_dir, include_llama_dll = True, include_cuda_dll = True)
+ write_windows_install_shape(
+ install_dir, include_llama_dll = True, include_cuda_dll = True
+ )
host = HostInfo(
system = "Windows",
@@ -1278,7 +1318,9 @@ def test_existing_install_matches_plan_windows_cuda_requires_cuda_dll(tmp_path:
assert existing_install_matches_plan(install_dir, host, plan) is False
-def test_existing_install_matches_plan_windows_cuda_paired_requires_cudart(tmp_path: Path):
+def test_existing_install_matches_plan_windows_cuda_paired_requires_cudart(
+ tmp_path: Path,
+):
"""When the choice ships a paired cudart bundle (#5106), the install
is considered stale unless cudart64_*.dll and cublas64_*.dll are
actually on disk. Otherwise existing broken installs would keep
@@ -1394,7 +1436,9 @@ def test_existing_install_matches_plan_windows_cuda_paired_requires_cudart(tmp_p
assert existing_install_matches_plan(install_dir, host, plan) is False
-def test_existing_install_matches_plan_windows_cuda_unpaired_skips_cudart_check(tmp_path: Path):
+def test_existing_install_matches_plan_windows_cuda_unpaired_skips_cudart_check(
+ tmp_path: Path,
+):
"""If the choice has no paired runtime archive (manifest dropped it,
or upstream did not ship cudart), legacy installs without cudart on
disk must still pass the health check -- otherwise the installer
@@ -1749,7 +1793,9 @@ def test_install_prebuilt_skips_download_when_existing_install_matches(
INSTALL_LLAMA_PREBUILT,
"download_validation_model",
lambda *args, **kwargs: (_ for _ in ()).throw(
- AssertionError("matching install should skip before validation model download")
+ AssertionError(
+ "matching install should skip before validation model download"
+ )
),
)
@@ -2265,7 +2311,9 @@ def test_install_prebuilt_same_tag_upstream_failure_uses_older_unsloth_release_p
(staging_dir / "marker.txt").write_text("ready\n")
return attempts[0], staging_dir, initial_fallback_used
- monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "validate_prebuilt_attempts", fake_validate)
+ monkeypatch.setattr(
+ INSTALL_LLAMA_PREBUILT, "validate_prebuilt_attempts", fake_validate
+ )
activated = {}
monkeypatch.setattr(
@@ -2283,7 +2331,10 @@ def test_install_prebuilt_same_tag_upstream_failure_uses_older_unsloth_release_p
install_prebuilt(install_dir, "latest", "unslothai/llama.cpp", "")
- assert attempted == [("b9002", "release-2", "upstream"), ("b9001", "release-1", "upstream")]
+ assert attempted == [
+ ("b9002", "release-2", "upstream"),
+ ("b9001", "release-1", "upstream"),
+ ]
assert activated["install_dir"] == install_dir
@@ -2311,7 +2362,9 @@ def add_symlink_to_tar(archive: tarfile.TarFile, name: str, target: str) -> None
archive.addfile(info)
-def test_existing_install_matches_choice_fails_when_install_tree_incomplete(tmp_path: Path):
+def test_existing_install_matches_choice_fails_when_install_tree_incomplete(
+ tmp_path: Path,
+):
"""confirm_install_tree guard rejects installs missing critical files."""
install_dir = tmp_path / "llama.cpp"
install_dir.mkdir()
@@ -2400,7 +2453,9 @@ def test_existing_install_matches_choice_fails_when_install_tree_incomplete(tmp_
)
-def test_existing_install_matches_choice_fails_when_install_tree_incomplete_macos(tmp_path: Path):
+def test_existing_install_matches_choice_fails_when_install_tree_incomplete_macos(
+ tmp_path: Path,
+):
"""confirm_install_tree guard rejects macOS arm64 installs missing critical files."""
install_dir = tmp_path / "llama.cpp"
install_dir.mkdir()
@@ -2614,7 +2669,9 @@ def test_runtime_overlay_cannot_overwrite_main_archive_payload(tmp_path: Path) -
if expected_sha256:
actual = hashlib.sha256(Path(target_path).read_bytes()).hexdigest()
if actual != expected_sha256:
- raise INSTALL_LLAMA_PREBUILT.PrebuiltFallback(f"sha256 mismatch on {label}")
+ raise INSTALL_LLAMA_PREBUILT.PrebuiltFallback(
+ f"sha256 mismatch on {label}"
+ )
INSTALL_LLAMA_PREBUILT.download_file_verified = fake_download
try:
@@ -2626,7 +2683,8 @@ def test_runtime_overlay_cannot_overwrite_main_archive_payload(tmp_path: Path) -
server = release_dir / "llama-server.exe"
assert server.exists()
assert server.read_bytes() == b"MAIN-SERVER", (
- "runtime archive overwrote main llama-server.exe; " f"got {server.read_bytes()!r}"
+ "runtime archive overwrote main llama-server.exe; "
+ f"got {server.read_bytes()!r}"
)
for name in ("cudart64_12.dll", "cublas64_12.dll", "cublasLt64_12.dll"):
assert (release_dir / name).exists(), f"missing {name}"
@@ -2708,7 +2766,9 @@ def test_linux_runtime_overlay_copies_llama_tool_impl_libraries(tmp_path: Path)
if expected_sha256:
actual = hashlib.sha256(Path(target_path).read_bytes()).hexdigest()
if actual != expected_sha256:
- raise INSTALL_LLAMA_PREBUILT.PrebuiltFallback(f"sha256 mismatch on {label}")
+ raise INSTALL_LLAMA_PREBUILT.PrebuiltFallback(
+ f"sha256 mismatch on {label}"
+ )
INSTALL_LLAMA_PREBUILT.download_file_verified = fake_download
try:
@@ -2726,7 +2786,9 @@ def test_linux_runtime_overlay_copies_llama_tool_impl_libraries(tmp_path: Path)
assert not (runtime_dir / "llama-cli").exists()
-def test_python_runtime_dirs_covers_cu13_and_library_bin(monkeypatch, tmp_path: Path) -> None:
+def test_python_runtime_dirs_covers_cu13_and_library_bin(
+ monkeypatch, tmp_path: Path
+) -> None:
"""Installer-side runtime DLL discovery must scan the same path
set as the backend ``_windows_pip_nvidia_dll_dirs``: legacy
``nvidia//bin``, current ``nvidia//bin/x86_64``
@@ -2793,7 +2855,9 @@ def _run_validate_prebuilt_choice(monkeypatch, tmp_path, *, expected_sha256):
src, "preferred_source_archive", lambda *a, **k: ("repo", "ref", None, False)
)
monkeypatch.setattr(src, "hydrate_source_tree", lambda *a, **k: None)
- monkeypatch.setattr(src, "install_from_archives", lambda *a, **k: (server_path, quantize_path))
+ monkeypatch.setattr(
+ src, "install_from_archives", lambda *a, **k: (server_path, quantize_path)
+ )
monkeypatch.setattr(src, "preflight_linux_installed_binaries", lambda *a, **k: None)
monkeypatch.setattr(src, "preflight_macos_installed_binaries", lambda *a, **k: None)
monkeypatch.setattr(src, "ensure_repo_shape", lambda *a, **k: None)
@@ -2804,7 +2868,9 @@ def _run_validate_prebuilt_choice(monkeypatch, tmp_path, *, expected_sha256):
lambda *a, **k: calls.__setitem__("quantize", calls["quantize"] + 1),
)
monkeypatch.setattr(
- src, "validate_server", lambda *a, **k: calls.__setitem__("server", calls["server"] + 1)
+ src,
+ "validate_server",
+ lambda *a, **k: calls.__setitem__("server", calls["server"] + 1),
)
bundle_name = "app-b9998-linux-x64-cuda13-newer.tar.gz"
@@ -2846,14 +2912,20 @@ def _run_validate_prebuilt_choice(monkeypatch, tmp_path, *, expected_sha256):
return calls
-def test_validate_prebuilt_choice_approved_validation_skipped_when_flag_off(tmp_path, monkeypatch):
+def test_validate_prebuilt_choice_approved_validation_skipped_when_flag_off(
+ tmp_path, monkeypatch
+):
# An approved (sha256-verified) bundle skips the staged smoke test while the
# flag is off: the manifest hash is its integrity gate.
- calls = _run_validate_prebuilt_choice(monkeypatch, tmp_path, expected_sha256 = "ab" * 32)
+ calls = _run_validate_prebuilt_choice(
+ monkeypatch, tmp_path, expected_sha256 = "ab" * 32
+ )
assert calls == {"quantize": 0, "server": 0}
-def test_validate_prebuilt_choice_hashless_build_always_validated(tmp_path, monkeypatch):
+def test_validate_prebuilt_choice_hashless_build_always_validated(
+ tmp_path, monkeypatch
+):
# A hashless external build has no approved sha256, so the
# functional smoke test is its only integrity gate and must run even while the
# flag is off -- otherwise a corrupted/replaced archive could be activated.
@@ -2861,9 +2933,13 @@ def test_validate_prebuilt_choice_hashless_build_always_validated(tmp_path, monk
assert calls == {"quantize": 1, "server": 1}
-def test_validate_prebuilt_choice_approved_validation_runs_when_flag_enabled(tmp_path, monkeypatch):
+def test_validate_prebuilt_choice_approved_validation_runs_when_flag_enabled(
+ tmp_path, monkeypatch
+):
# Flipping _RUN_STAGED_PREBUILT_VALIDATION back on restores the full smoke test
# for approved bundles too, proving the check is kept intact, only gated off.
monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "_RUN_STAGED_PREBUILT_VALIDATION", True)
- calls = _run_validate_prebuilt_choice(monkeypatch, tmp_path, expected_sha256 = "ab" * 32)
+ calls = _run_validate_prebuilt_choice(
+ monkeypatch, tmp_path, expected_sha256 = "ab" * 32
+ )
assert calls == {"quantize": 1, "server": 1}
diff --git a/tests/studio/install/test_llama_pr_force_and_source.py b/tests/studio/install/test_llama_pr_force_and_source.py
index 8643806127..10e20c3ed7 100644
--- a/tests/studio/install/test_llama_pr_force_and_source.py
+++ b/tests/studio/install/test_llama_pr_force_and_source.py
@@ -386,7 +386,10 @@ class TestSourcePatternsSh:
assert '_DEFAULT_LLAMA_PR_FORCE=""' in self.content
def test_has_default_source(self):
- assert '_DEFAULT_LLAMA_SOURCE="https://github.com/ggml-org/llama.cpp"' in self.content
+ assert (
+ '_DEFAULT_LLAMA_SOURCE="https://github.com/ggml-org/llama.cpp"'
+ in self.content
+ )
def test_has_pr_force_env_read(self):
assert "UNSLOTH_LLAMA_PR_FORCE" in self.content
@@ -436,7 +439,9 @@ class TestSourcePatternsSh:
lines = self.content.splitlines()
for i, line in enumerate(lines, 1):
if "git clone" in line and "ggml-org/llama.cpp.git" in line:
- pytest.fail(f"Line {i} has hardcoded ggml-org clone URL: {line.strip()}")
+ pytest.fail(
+ f"Line {i} has hardcoded ggml-org clone URL: {line.strip()}"
+ )
# =========================================================================
@@ -453,7 +458,10 @@ class TestSourcePatternsPs1:
assert '$DefaultLlamaPrForce = ""' in self.content
def test_has_default_source(self):
- assert '$DefaultLlamaSource = "https://github.com/ggml-org/llama.cpp"' in self.content
+ assert (
+ '$DefaultLlamaSource = "https://github.com/ggml-org/llama.cpp"'
+ in self.content
+ )
def test_has_pr_force_env_read(self):
assert "$env:UNSLOTH_LLAMA_PR_FORCE" in self.content
@@ -465,7 +473,10 @@ class TestSourcePatternsPs1:
def test_release_repo_override_removed(self):
# No env-based release-repo override; the repo is chosen by GPU detection
# (GPU -> fork, CPU -> ggml-org), mirroring setup.sh.
- assert "$HelperReleaseRepo = if ($env:UNSLOTH_LLAMA_RELEASE_REPO)" not in self.content
+ assert (
+ "$HelperReleaseRepo = if ($env:UNSLOTH_LLAMA_RELEASE_REPO)"
+ not in self.content
+ )
assert (
"$HelperReleaseRepo = if ($HasNvidiaSmi -or $HasROCm -or $script:ROCmGfxArch) "
'{ "unslothai/llama.cpp" } else { "ggml-org/llama.cpp" }' in self.content
@@ -487,7 +498,9 @@ class TestSourcePatternsPs1:
def test_clone_urls_parameterized_pr_path(self):
"""PR clone path uses $LlamaSource.git, not hardcoded URL."""
- pr_idx = self.content.index("if ($LlamaPr) {\n", self.content.index("Cloning llama.cpp"))
+ pr_idx = self.content.index(
+ "if ($LlamaPr) {\n", self.content.index("Cloning llama.cpp")
+ )
else_idx = self.content.index("} else {", pr_idx)
pr_block = self.content[pr_idx:else_idx]
assert '"$LlamaSource.git"' in pr_block
@@ -505,7 +518,9 @@ class TestSourcePatternsPs1:
lines = self.content.splitlines()
for i, line in enumerate(lines, 1):
if "git clone" in line and "ggml-org/llama.cpp.git" in line:
- pytest.fail(f"Line {i} has hardcoded ggml-org clone URL: {line.strip()}")
+ pytest.fail(
+ f"Line {i} has hardcoded ggml-org clone URL: {line.strip()}"
+ )
# =========================================================================
diff --git a/tests/studio/install/test_macos_version_compat.py b/tests/studio/install/test_macos_version_compat.py
index 7f93b295eb..2b96d6c5e4 100644
--- a/tests/studio/install/test_macos_version_compat.py
+++ b/tests/studio/install/test_macos_version_compat.py
@@ -20,7 +20,9 @@ import pytest
PACKAGE_ROOT = Path(__file__).resolve().parents[3]
MODULE_PATH = PACKAGE_ROOT / "studio" / "install_llama_prebuilt.py"
-SPEC = importlib.util.spec_from_file_location("studio_install_llama_prebuilt_macos", MODULE_PATH)
+SPEC = importlib.util.spec_from_file_location(
+ "studio_install_llama_prebuilt_macos", MODULE_PATH
+)
assert SPEC is not None and SPEC.loader is not None
ILP = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = ILP
@@ -142,7 +144,10 @@ class TestMachoMinimumMacos:
)
)
assert ILP.macho_minimum_macos(path, make_macos_host((14, 0))) == (14, 0)
- assert ILP.macho_minimum_macos(path, make_macos_host((26, 0), arm64 = False)) == (26, 0)
+ assert ILP.macho_minimum_macos(path, make_macos_host((26, 0), arm64 = False)) == (
+ 26,
+ 0,
+ )
def test_non_macho_returns_none(self, tmp_path):
path = tmp_path / "script.sh"
@@ -185,17 +190,23 @@ class TestPreflightMacosInstalledBinaries:
def test_rejects_too_new_dylib(self, tmp_path):
install_dir, binaries = self._install_dir(tmp_path, (26, 0))
with pytest.raises(PrebuiltFallback, match = "newer macOS"):
- ILP.preflight_macos_installed_binaries(binaries, install_dir, make_macos_host((14, 0)))
+ ILP.preflight_macos_installed_binaries(
+ binaries, install_dir, make_macos_host((14, 0))
+ )
def test_accepts_compatible_prebuilt(self, tmp_path):
install_dir, binaries = self._install_dir(tmp_path, (14, 0))
# Must not raise on a macOS 15 host.
- ILP.preflight_macos_installed_binaries(binaries, install_dir, make_macos_host((15, 5)))
+ ILP.preflight_macos_installed_binaries(
+ binaries, install_dir, make_macos_host((15, 5))
+ )
def test_skips_when_host_version_unknown(self, tmp_path):
install_dir, binaries = self._install_dir(tmp_path, (26, 0))
# Unknown host version -> defer to runtime validation, do not raise.
- ILP.preflight_macos_installed_binaries(binaries, install_dir, make_macos_host(None))
+ ILP.preflight_macos_installed_binaries(
+ binaries, install_dir, make_macos_host(None)
+ )
def test_noop_on_non_macos_host(self, tmp_path):
install_dir, binaries = self._install_dir(tmp_path, (26, 0))
diff --git a/tests/studio/install/test_pr4562_bugfixes.py b/tests/studio/install/test_pr4562_bugfixes.py
index 9dfa4e0005..35e3151ff7 100644
--- a/tests/studio/install/test_pr4562_bugfixes.py
+++ b/tests/studio/install/test_pr4562_bugfixes.py
@@ -29,7 +29,9 @@ import pytest
# ---------------------------------------------------------------------------
PACKAGE_ROOT = Path(__file__).resolve().parents[3]
MODULE_PATH = PACKAGE_ROOT / "studio" / "install_llama_prebuilt.py"
-SPEC = importlib.util.spec_from_file_location("studio_install_llama_prebuilt", MODULE_PATH)
+SPEC = importlib.util.spec_from_file_location(
+ "studio_install_llama_prebuilt", MODULE_PATH
+)
assert SPEC is not None and SPEC.loader is not None
MOD = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = MOD
@@ -116,7 +118,9 @@ class TestBinaryEnvCrossPlatform:
env = binary_env(binary_path, install_dir, host)
ld_dirs = env["LD_LIBRARY_PATH"].split(os.pathsep)
assert str(bin_dir) in ld_dirs, f"build/bin not in LD_LIBRARY_PATH: {ld_dirs}"
- assert str(install_dir) in ld_dirs, f"install_dir not in LD_LIBRARY_PATH: {ld_dirs}"
+ assert (
+ str(install_dir) in ld_dirs
+ ), f"install_dir not in LD_LIBRARY_PATH: {ld_dirs}"
def test_linux_binary_parent_comes_before_install_dir(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
@@ -135,7 +139,9 @@ class TestBinaryEnvCrossPlatform:
ld_dirs = env["LD_LIBRARY_PATH"].split(os.pathsep)
bin_idx = ld_dirs.index(str(bin_dir))
install_idx = ld_dirs.index(str(install_dir))
- assert bin_idx < install_idx, "binary_path.parent should come before install_dir"
+ assert (
+ bin_idx < install_idx
+ ), "binary_path.parent should come before install_dir"
def test_linux_deduplicates_when_binary_parent_equals_install_dir(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
@@ -194,13 +200,17 @@ class TestBinaryEnvCrossPlatform:
binary_path.write_bytes(b"MZ")
host = make_host(system = "Windows")
- monkeypatch.setattr(MOD, "windows_runtime_dirs_for_runtime_line", lambda _rt: [])
+ monkeypatch.setattr(
+ MOD, "windows_runtime_dirs_for_runtime_line", lambda _rt: []
+ )
env = binary_env(binary_path, install_dir, host)
path_dirs = env["PATH"].split(os.pathsep)
assert str(bin_dir) in path_dirs, f"build/bin/Release not in PATH: {path_dirs}"
- def test_macos_sets_dyld_library_path(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
+ def test_macos_sets_dyld_library_path(
+ self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+ ):
install_dir = tmp_path / "llama.cpp"
install_dir.mkdir(parents = True)
bin_dir = install_dir / "build" / "bin"
@@ -213,8 +223,12 @@ class TestBinaryEnvCrossPlatform:
env = binary_env(binary_path, install_dir, host)
dyld_parts = [p for p in env["DYLD_LIBRARY_PATH"].split(os.pathsep) if p]
- assert str(bin_dir) in dyld_parts, f"build/bin not in DYLD_LIBRARY_PATH: {dyld_parts}"
- assert str(install_dir) in dyld_parts, f"install_dir not in DYLD_LIBRARY_PATH: {dyld_parts}"
+ assert (
+ str(bin_dir) in dyld_parts
+ ), f"build/bin not in DYLD_LIBRARY_PATH: {dyld_parts}"
+ assert (
+ str(install_dir) in dyld_parts
+ ), f"install_dir not in DYLD_LIBRARY_PATH: {dyld_parts}"
# binary_path.parent (build/bin) should come before install_dir
assert dyld_parts.index(str(bin_dir)) < dyld_parts.index(str(install_dir))
@@ -345,7 +359,9 @@ class TestResolveRequestedLlamaTag:
class TestFetchJsonRetries:
- def test_fetch_json_retries_invalid_github_api_json(self, monkeypatch: pytest.MonkeyPatch):
+ def test_fetch_json_retries_invalid_github_api_json(
+ self, monkeypatch: pytest.MonkeyPatch
+ ):
calls = {"count": 0}
def fake_download_bytes(url, **kwargs):
@@ -680,7 +696,10 @@ class TestSourceCodePatterns:
content = SETUP_SH.read_text()
assert "--resolve-source-build" not in content
assert "--resolve-install-tag" not in content
- assert '--resolve-llama-tag latest --published-repo "ggml-org/llama.cpp"' in content
+ assert (
+ '--resolve-llama-tag latest --published-repo "ggml-org/llama.cpp"'
+ in content
+ )
assert "--output-format json" in content
assert "_RESOLVED_SOURCE_URL" in content
assert "_RESOLVED_SOURCE_REF_KIND" in content
@@ -756,7 +775,9 @@ class TestSourceCodePatterns:
# Delivered via NVCC_PREPEND_FLAGS (covers the configure-time compiler
# probe too), not embedded in the word-split CMAKE_ARGS string.
assert "export NVCC_PREPEND_FLAGS=" in content
- cmake_args_lines = [line for line in content.splitlines() if "CMAKE_ARGS=" in line]
+ cmake_args_lines = [
+ line for line in content.splitlines() if "CMAKE_ARGS=" in line
+ ]
assert all(
"-allow-unsupported-compiler" not in line for line in cmake_args_lines
), "flag must stay out of CMAKE_ARGS (bash word-splitting safety)"
@@ -772,7 +793,9 @@ class TestSourceCodePatterns:
# Delivered via the process environment, not the $CmakeArgs array, so it
# reaches both the configure-time compiler probe and `cmake --build`.
assert "$env:NVCC_PREPEND_FLAGS" in content
- cmake_args_lines = [line for line in content.splitlines() if "$CmakeArgs +=" in line]
+ cmake_args_lines = [
+ line for line in content.splitlines() if "$CmakeArgs +=" in line
+ ]
assert all(
"-allow-unsupported-compiler" not in line for line in cmake_args_lines
), "flag must not be pushed into the $CmakeArgs array"
@@ -788,10 +811,15 @@ class TestSourceCodePatterns:
def test_macos_arm64_cpu_fallback_args_exclude_rpath(self):
"""CPU fallback args must NOT contain Metal-only RPATH flags at runtime."""
- script = '_IS_MACOS_ARM64=true\nNVCC_PATH=""\nGPU_BACKEND=""\n' + _GPU_BACKEND_FRAGMENT
+ script = (
+ '_IS_MACOS_ARM64=true\nNVCC_PATH=""\nGPU_BACKEND=""\n'
+ + _GPU_BACKEND_FRAGMENT
+ )
output = run_bash(script)
fallback_line = next(
- line for line in output.splitlines() if line.startswith("CPU_FALLBACK_CMAKE_ARGS=")
+ line
+ for line in output.splitlines()
+ if line.startswith("CPU_FALLBACK_CMAKE_ARGS=")
)
assert "-DGGML_METAL=OFF" in fallback_line
assert (
@@ -812,7 +840,8 @@ class TestSourceCodePatterns:
assert (
"x86_64"
not in content[
- content.find("-DGGML_METAL=ON") - 200 : content.find("-DGGML_METAL=ON") + 200
+ content.find("-DGGML_METAL=ON") - 200 : content.find("-DGGML_METAL=ON")
+ + 200
]
)
@@ -842,7 +871,9 @@ class TestSourceCodePatterns:
# Allow git pull in other contexts
context = "\n".join(lines[max(0, i - 5) : i + 5])
if "LlamaCppDir" in context:
- pytest.fail(f"Found 'git pull' in llama.cpp build section at line {i+1}")
+ pytest.fail(
+ f"Found 'git pull' in llama.cpp build section at line {i+1}"
+ )
def test_setup_ps1_prebuilt_install_entrypoint(self):
"""PS1 prebuilt path should call the helper install entrypoint, not the
@@ -869,7 +900,8 @@ class TestSourceCodePatterns:
assert "--resolve-source-build" not in content
assert "--resolve-install-tag" not in content
assert (
- '"--resolve-llama-tag", "latest", "--published-repo", "ggml-org/llama.cpp"' in content
+ '"--resolve-llama-tag", "latest", "--published-repo", "ggml-org/llama.cpp"'
+ in content
)
assert '--output-format", "json"' in content
assert "$ResolvedSourceUrl" in content
@@ -883,7 +915,10 @@ class TestSourceCodePatterns:
block = content[max(0, install_idx - 800) : install_idx + 800]
assert "$PSNativeCommandUseErrorActionPreference = $false" in block
assert "$restoreNativeErrorPreference = $true" in block
- assert "$PSNativeCommandUseErrorActionPreference = $previousNativeErrorPreference" in block
+ assert (
+ "$PSNativeCommandUseErrorActionPreference = $previousNativeErrorPreference"
+ in block
+ )
def test_setup_ps1_helper_disables_error_action_abort(self):
"""Helper resolution should suppress terminating NativeCommandError on PS 5.1."""
@@ -904,7 +939,9 @@ class TestSourceCodePatterns:
"""The unconstrained nvcc fallback should not sort toolkit dirs lexicographically."""
content = SETUP_PS1.read_text()
assert "Sort-Object Name | Select-Object -Last 1" not in content
- assert "Sort-Object { [version]($_.Name -replace '^v','') } -Descending" in content
+ assert (
+ "Sort-Object { [version]($_.Name -replace '^v','') } -Descending" in content
+ )
def test_binary_env_linux_has_binary_parent(self):
"""The Linux branch of binary_env should include binary_path.parent."""
@@ -965,7 +1002,10 @@ class TestMacOSMetalBuildLogic:
def test_macos_arm64_cmake_args_contain_metal_flags(self):
"""macOS arm64 should enable Metal, not CUDA."""
- script = '_IS_MACOS_ARM64=true\nNVCC_PATH=""\nGPU_BACKEND=""\n' + _GPU_BACKEND_FRAGMENT
+ script = (
+ '_IS_MACOS_ARM64=true\nNVCC_PATH=""\nGPU_BACKEND=""\n'
+ + _GPU_BACKEND_FRAGMENT
+ )
output = run_bash(script)
assert "-DGGML_METAL=ON" in output
assert "-DGGML_CUDA=ON" not in output
@@ -973,7 +1013,10 @@ class TestMacOSMetalBuildLogic:
def test_intel_macos_no_metal_flags(self):
"""Intel macOS (not arm64) should not get Metal flags."""
- script = '_IS_MACOS_ARM64=false\nNVCC_PATH=""\nGPU_BACKEND=""\n' + _GPU_BACKEND_FRAGMENT
+ script = (
+ '_IS_MACOS_ARM64=false\nNVCC_PATH=""\nGPU_BACKEND=""\n'
+ + _GPU_BACKEND_FRAGMENT
+ )
output = run_bash(script)
assert "-DGGML_METAL=ON" not in output
assert "BUILD_DESC=building (CPU)" in output
@@ -1059,14 +1102,18 @@ class TestMacOSMetalBuildLogic:
# Verify cmake args: first call has Metal ON, second has Metal OFF
calls = calls_file.read_text().splitlines()
assert len(calls) >= 2, f"Expected >= 2 cmake calls, got {len(calls)}"
- assert "-DGGML_METAL=ON" in calls[0], f"First cmake call should have Metal ON: {calls[0]}"
+ assert (
+ "-DGGML_METAL=ON" in calls[0]
+ ), f"First cmake call should have Metal ON: {calls[0]}"
assert (
"-DGGML_METAL=OFF" in calls[1]
), f"Second cmake call should have Metal OFF: {calls[1]}"
assert (
"-DGGML_METAL=ON" not in calls[1]
), f"Second cmake call should NOT have Metal ON: {calls[1]}"
- assert "@loader_path" not in calls[1], f"CPU fallback should not have RPATH: {calls[1]}"
+ assert (
+ "@loader_path" not in calls[1]
+ ), f"CPU fallback should not have RPATH: {calls[1]}"
assert (
"-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON" not in calls[1]
), f"CPU fallback should not have RPATH build flag: {calls[1]}"
@@ -1174,7 +1221,9 @@ class TestMacOSMetalBuildLogic:
# Third call: re-configure with Metal OFF and no RPATH flags
assert "-DGGML_METAL=OFF" in calls[2]
assert "-DGGML_METAL=ON" not in calls[2]
- assert "@loader_path" not in calls[2], f"CPU fallback should not have RPATH: {calls[2]}"
+ assert (
+ "@loader_path" not in calls[2]
+ ), f"CPU fallback should not have RPATH: {calls[2]}"
assert (
"-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON" not in calls[2]
), f"CPU fallback should not have RPATH build flag: {calls[2]}"
diff --git a/tests/studio/install/test_pr5940_followups.py b/tests/studio/install/test_pr5940_followups.py
index 583532eb74..02e49aae93 100644
--- a/tests/studio/install/test_pr5940_followups.py
+++ b/tests/studio/install/test_pr5940_followups.py
@@ -67,8 +67,12 @@ def test_fetch_validation_model_prefers_huggingface_hub(tmp_path):
model.write_bytes(b"GGUF-via-hf")
fake_hf = MagicMock(return_value = str(model))
with (
- patch.object(prebuilt, "validated_validation_model_bytes", side_effect = lambda b: b),
- patch.dict(sys.modules, {"huggingface_hub": MagicMock(hf_hub_download = fake_hf)}),
+ patch.object(
+ prebuilt, "validated_validation_model_bytes", side_effect = lambda b: b
+ ),
+ patch.dict(
+ sys.modules, {"huggingface_hub": MagicMock(hf_hub_download = fake_hf)}
+ ),
):
assert prebuilt._fetch_validation_model_bytes() == b"GGUF-via-hf"
assert fake_hf.called # hf path was taken, urllib not needed
@@ -77,8 +81,12 @@ def test_fetch_validation_model_prefers_huggingface_hub(tmp_path):
def test_fetch_validation_model_falls_back_to_urllib_on_hf_failure():
fake_hf = MagicMock(side_effect = RuntimeError("hf unreachable"))
with (
- patch.object(prebuilt, "validated_validation_model_bytes", side_effect = lambda b: b),
- patch.dict(sys.modules, {"huggingface_hub": MagicMock(hf_hub_download = fake_hf)}),
+ patch.object(
+ prebuilt, "validated_validation_model_bytes", side_effect = lambda b: b
+ ),
+ patch.dict(
+ sys.modules, {"huggingface_hub": MagicMock(hf_hub_download = fake_hf)}
+ ),
patch.object(prebuilt, "download_bytes", return_value = b"GGUF-via-urllib") as dl,
):
assert prebuilt._fetch_validation_model_bytes() == b"GGUF-via-urllib"
@@ -192,7 +200,9 @@ def test_install_sh_name_arch_agrees_with_ps_for_strix_and_non_amd():
assert sh == expect, f"install.sh: {name!r} -> {sh!r}, expected {expect!r}"
if expect is not None: # cross-check bash agrees with the PowerShell table
ps = next((a for p, a in ps_rows if re.search(p, name)), None)
- assert sh == ps, f"install.sh/install.ps1 drift for {name!r}: {sh!r} vs {ps!r}"
+ assert (
+ sh == ps
+ ), f"install.sh/install.ps1 drift for {name!r}: {sh!r} vs {ps!r}"
def test_setup_sh_name_arch_table_in_sync_with_install_sh():
@@ -268,7 +278,9 @@ def test_amd_smi_opt_in_forces_on_windows_no_sdk():
def test_amd_smi_opt_out_overrides_hip_sdk():
assert (
- _amd_smi_allowed_under("Windows", hipinfo_present = True, env = {"UNSLOTH_ENABLE_AMD_SMI": "0"})
+ _amd_smi_allowed_under(
+ "Windows", hipinfo_present = True, env = {"UNSLOTH_ENABLE_AMD_SMI": "0"}
+ )
is False
)
@@ -278,7 +290,9 @@ def test_ps_installers_gate_amd_smi_on_windows():
# UNSLOTH_ENABLE_AMD_SMI opt-in, mirroring _amd_smi_allowed().
for ps in (_INSTALL_PS1, _SETUP_PS1):
text = ps.read_text(encoding = "utf-8")
- assert "UNSLOTH_ENABLE_AMD_SMI" in text, f"{ps.name} missing amd-smi opt-in gate"
+ assert (
+ "UNSLOTH_ENABLE_AMD_SMI" in text
+ ), f"{ps.name} missing amd-smi opt-in gate"
assert "amdSmiAllowed" in text, f"{ps.name} missing amd-smi gate variable"
@@ -289,7 +303,9 @@ def test_install_python_stack_gates_every_amd_smi_spawn():
# list` ungated on Adrenalin-only hosts; not-spawning is the only fix.
import ast
- src = (PACKAGE_ROOT / "studio" / "install_python_stack.py").read_text(encoding = "utf-8")
+ src = (PACKAGE_ROOT / "studio" / "install_python_stack.py").read_text(
+ encoding = "utf-8"
+ )
tree = ast.parse(src)
def _names_amd_smi_command(node):
@@ -313,7 +329,10 @@ def test_install_python_stack_gates_every_amd_smi_spawn():
return False
def _references_gate(node):
- return any(isinstance(n, ast.Name) and n.id == "_amd_smi_allowed" for n in ast.walk(node))
+ return any(
+ isinstance(n, ast.Name) and n.id == "_amd_smi_allowed"
+ for n in ast.walk(node)
+ )
offenders = [
node.name
diff --git a/tests/studio/install/test_probe_timeouts.py b/tests/studio/install/test_probe_timeouts.py
index acea0ed34d..e5d25af0f8 100644
--- a/tests/studio/install/test_probe_timeouts.py
+++ b/tests/studio/install/test_probe_timeouts.py
@@ -173,11 +173,15 @@ def test_has_usable_nvidia_gpu_returns_under_timeout():
fake_dir.mkdir()
fake_smi = fake_dir / "nvidia-smi"
fake_smi.write_text("#!/bin/sh\nsleep 30\n")
- fake_smi.chmod(fake_smi.stat().st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)
+ fake_smi.chmod(
+ fake_smi.stat().st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH
+ )
# Build a minimal PATH that includes the fake nvidia-smi plus the real
# `timeout`/`awk`/`ls` it needs. Use the fake dir first so it wins.
- real_bins = {Path(shutil.which(c)).parent for c in ("timeout", "awk", "ls", "sh")}
+ real_bins = {
+ Path(shutil.which(c)).parent for c in ("timeout", "awk", "ls", "sh")
+ }
path_env = os.pathsep.join([str(fake_dir)] + [str(p) for p in real_bins])
# Force the /proc fallback off so the result depends only on the probe,
diff --git a/tests/studio/install/test_rocm_support.py b/tests/studio/install/test_rocm_support.py
index a3c9555d0b..4f748eaa99 100644
--- a/tests/studio/install/test_rocm_support.py
+++ b/tests/studio/install/test_rocm_support.py
@@ -40,7 +40,9 @@ _normalize_forwarded_gfx = prebuilt_mod._normalize_forwarded_gfx
# install_python_stack.py
_STACK_PATH = PACKAGE_ROOT / "studio" / "install_python_stack.py"
-_STACK_SPEC = importlib.util.spec_from_file_location("studio_install_python_stack", _STACK_PATH)
+_STACK_SPEC = importlib.util.spec_from_file_location(
+ "studio_install_python_stack", _STACK_PATH
+)
assert _STACK_SPEC is not None and _STACK_SPEC.loader is not None
stack_mod = importlib.util.module_from_spec(_STACK_SPEC)
sys.modules[_STACK_SPEC.name] = stack_mod
@@ -300,7 +302,9 @@ class TestResolveUpstreamAssetChoice:
def test_rocm_linux_no_prebuilt_falls_back(self, mock_assets):
"""AMD ROCm host should fall back to source build when no ROCm prebuilt exists."""
# Remove the ROCm asset from available assets
- assets_without_rocm = {k: v for k, v in UPSTREAM_ASSETS.items() if "rocm" not in k}
+ assets_without_rocm = {
+ k: v for k, v in UPSTREAM_ASSETS.items() if "rocm" not in k
+ }
mock_assets.return_value = assets_without_rocm
host = rocm_host()
with pytest.raises(PrebuiltFallback, match = "ROCm detected"):
@@ -591,7 +595,9 @@ class TestEnsureRocmTorch:
@patch.object(stack_mod, "_has_usable_nvidia_gpu", return_value = False)
@patch.object(stack_mod, "_has_rocm_gpu", return_value = True)
@patch.object(stack_mod, "_detect_rocm_version", return_value = (7, 1))
- def test_torch_already_has_cuda_skips(self, mock_ver, mock_gpu, mock_nvidia, mock_pip):
+ def test_torch_already_has_cuda_skips(
+ self, mock_ver, mock_gpu, mock_nvidia, mock_pip
+ ):
"""If torch already has CUDA, should skip ROCm reinstall."""
mock_probe = MagicMock()
mock_probe.returncode = 0
@@ -605,7 +611,9 @@ class TestEnsureRocmTorch:
@patch.object(stack_mod, "_has_usable_nvidia_gpu", return_value = False)
@patch.object(stack_mod, "_has_rocm_gpu", return_value = True)
@patch.object(stack_mod, "_detect_rocm_version", return_value = (7, 1))
- def test_torch_already_has_hip_skips(self, mock_ver, mock_gpu, mock_nvidia, mock_pip):
+ def test_torch_already_has_hip_skips(
+ self, mock_ver, mock_gpu, mock_nvidia, mock_pip
+ ):
"""If torch already has HIP, should skip ROCm reinstall."""
mock_probe = MagicMock()
mock_probe.returncode = 0
@@ -643,7 +651,9 @@ class TestEnsureRocmTorch:
@patch.object(stack_mod, "_has_usable_nvidia_gpu", return_value = False)
@patch.object(stack_mod, "_has_rocm_gpu", return_value = True)
@patch.object(stack_mod, "_detect_rocm_version", return_value = (6, 3))
- def test_rocm_63_selects_correct_tag(self, mock_ver, mock_gpu, mock_nvidia, mock_pip):
+ def test_rocm_63_selects_correct_tag(
+ self, mock_ver, mock_gpu, mock_nvidia, mock_pip
+ ):
"""ROCm 6.3 should select rocm6.3 tag."""
mock_probe = MagicMock()
mock_probe.returncode = 0
@@ -710,7 +720,9 @@ class TestEnsureRocmTorch:
):
"""Probe subprocess timeout should not crash; should proceed to reinstall."""
with patch("os.path.isdir", return_value = True):
- with patch("subprocess.run", side_effect = subprocess.TimeoutExpired("python", 30)):
+ with patch(
+ "subprocess.run", side_effect = subprocess.TimeoutExpired("python", 30)
+ ):
_ensure_rocm_torch()
# If probe times out, the function should treat torch as unusable and reinstall
# both torch (via pip_install) and bitsandbytes (via pip_install_try).
@@ -735,7 +747,9 @@ class TestEnsureRocmTorch:
@patch.object(stack_mod, "pip_install")
@patch.object(stack_mod, "_has_rocm_gpu", return_value = True)
@patch.object(stack_mod, "_has_usable_nvidia_gpu", return_value = True)
- def test_torch_backend_cuda_env_skips_entirely(self, mock_nvidia, mock_gpu, mock_pip):
+ def test_torch_backend_cuda_env_skips_entirely(
+ self, mock_nvidia, mock_gpu, mock_pip
+ ):
"""UNSLOTH_TORCH_BACKEND=cuda must short-circuit before any GPU probe."""
with patch.dict(os.environ, {"UNSLOTH_TORCH_BACKEND": "cuda"}):
# Reload _TORCH_BACKEND from the patched environment.
@@ -746,7 +760,9 @@ class TestEnsureRocmTorch:
@patch.object(stack_mod, "pip_install")
@patch.object(stack_mod, "_has_rocm_gpu", return_value = True)
@patch.object(stack_mod, "_has_usable_nvidia_gpu", return_value = True)
- def test_torch_backend_cpu_env_skips_entirely(self, mock_nvidia, mock_gpu, mock_pip):
+ def test_torch_backend_cpu_env_skips_entirely(
+ self, mock_nvidia, mock_gpu, mock_pip
+ ):
"""UNSLOTH_TORCH_BACKEND=cpu must short-circuit before any GPU probe."""
with patch.dict(os.environ, {"UNSLOTH_TORCH_BACKEND": "cpu"}):
with patch.object(stack_mod, "_TORCH_BACKEND", "cpu"):
@@ -796,7 +812,9 @@ class TestHasRocmGpuKfdVendorGuard:
def test_sysfs_fallback_guarded_by_non_win32(self):
"""KFD sysfs fallback must be Linux-only (guarded by sys.platform != 'win32')."""
src = self._src()
- assert "win32" in src, "_has_rocm_gpu sysfs fallback must be guarded by sys.platform check"
+ assert (
+ "win32" in src
+ ), "_has_rocm_gpu sysfs fallback must be guarded by sys.platform check"
def test_cpu_node_excluded(self):
"""gpu_id == '0' must be excluded (CPU topology nodes)."""
@@ -812,8 +830,12 @@ class TestHasRocmGpuKfdVendorGuard:
func_start = source.find("_has_amd_rocm_gpu()")
func_end = source.find("\n}", func_start)
func_body = source[func_start:func_end]
- assert "vendor_id" in func_body, "_has_amd_rocm_gpu sysfs fallback must check vendor_id"
- assert "4098" in func_body, "_has_amd_rocm_gpu must require AMD vendor_id 4098 (0x1002)"
+ assert (
+ "vendor_id" in func_body
+ ), "_has_amd_rocm_gpu sysfs fallback must check vendor_id"
+ assert (
+ "4098" in func_body
+ ), "_has_amd_rocm_gpu must require AMD vendor_id 4098 (0x1002)"
def test_has_rocm_gpu_returns_false_when_nvidia_present(self):
"""_has_rocm_gpu must return False immediately when _has_usable_nvidia_gpu is True.
@@ -935,19 +957,25 @@ class TestHardwareRocmFlag:
def test_hardware_py_has_is_rocm(self):
"""hardware.py should define IS_ROCM."""
- hw_path = PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ hw_path = (
+ PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ )
source = hw_path.read_text(encoding = "utf-8")
assert "IS_ROCM: bool" in source and "False" in source
def test_hardware_py_sets_is_rocm_on_hip(self):
"""detect_hardware() should set IS_ROCM when torch.version.hip is set."""
- hw_path = PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ hw_path = (
+ PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ )
source = hw_path.read_text(encoding = "utf-8")
assert 'torch.version, "hip"' in source or "torch.version.hip" in source
def test_hardware_py_still_returns_cuda_for_rocm(self):
"""DeviceType should remain CUDA even on ROCm -- no DeviceType.ROCM."""
- hw_path = PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ hw_path = (
+ PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ )
source = hw_path.read_text(encoding = "utf-8")
# Ensure ROCM is NOT a DeviceType member
enum_section = source.split("class DeviceType")[1].split("\n\n")[0]
@@ -955,13 +983,17 @@ class TestHardwareRocmFlag:
def test_hardware_py_has_rocm_in_package_versions(self):
"""get_package_versions() should include 'rocm' key."""
- hw_path = PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ hw_path = (
+ PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ )
source = hw_path.read_text(encoding = "utf-8")
assert '"rocm"' in source
def test_hardware_py_device_type_cuda_references_intact(self):
"""All existing DeviceType.CUDA references should still be present."""
- hw_path = PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ hw_path = (
+ PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ )
source = hw_path.read_text(encoding = "utf-8")
# Key functions that must still reference DeviceType.CUDA
assert "DeviceType.CUDA" in source
@@ -969,20 +1001,26 @@ class TestHardwareRocmFlag:
def test_is_rocm_exported_from_init(self):
"""IS_ROCM should be exported from hardware __init__.py."""
- init_path = PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "__init__.py"
+ init_path = (
+ PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "__init__.py"
+ )
source = init_path.read_text(encoding = "utf-8")
assert "IS_ROCM" in source
def test_is_rocm_in_all_list(self):
"""IS_ROCM should be in __all__ list in __init__.py."""
- init_path = PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "__init__.py"
+ init_path = (
+ PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "__init__.py"
+ )
source = init_path.read_text(encoding = "utf-8")
# Extract __all__ section
assert '"IS_ROCM"' in source
def test_get_package_versions_returns_rocm_key(self):
"""get_package_versions() source should return both 'cuda' and 'rocm' keys."""
- hw_path = PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ hw_path = (
+ PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ )
source = hw_path.read_text(encoding = "utf-8")
# Find the get_package_versions function body
func_start = source.find("def get_package_versions")
@@ -997,16 +1035,22 @@ class TestHardwareRocmFlag:
Windows ROCm where torch.distributed ships without that helper, causing
a warning: 'module torch.distributed has no attribute is_torchelastic_launched'.
"""
- hw_path = PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ hw_path = (
+ PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ )
source = hw_path.read_text(encoding = "utf-8")
assert "is_torchelastic_launched" in source
def test_distributed_stubs_cover_core_helpers(self):
"""_determine_attention_impl_for_gpu_estimate must stub the four core distributed helpers."""
- hw_path = PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ hw_path = (
+ PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ )
source = hw_path.read_text(encoding = "utf-8")
for attr in ("is_initialized", "is_available", "get_rank", "get_world_size"):
- assert attr in source, f"distributed stub for '{attr}' missing from hardware.py"
+ assert (
+ attr in source
+ ), f"distributed stub for '{attr}' missing from hardware.py"
# TEST: tokenizer_utils.py -- error message
@@ -1054,7 +1098,9 @@ class TestInstallShStructure:
# a genuine here-string operator lives outside any quotes.
unquoted = re.sub(r"'[^']*'", "", line)
unquoted = re.sub(r'"[^"]*"', "", unquoted)
- assert "<<<" not in unquoted, f"install.sh:{i} uses non-POSIX <<< here-string"
+ assert (
+ "<<<" not in unquoted
+ ), f"install.sh:{i} uses non-POSIX <<< here-string"
def test_rocm_detection_present(self):
"""install.sh should have ROCm detection in get_torch_index_url."""
@@ -1084,8 +1130,12 @@ class TestInstallShStructure:
if no_nvidia_branch < 0:
no_nvidia_branch = body.find('if [ -z "$_smi" ]')
rocm_call = body.find("_has_amd_rocm_gpu")
- assert nvidia_call >= 0, "get_torch_index_url should call _has_usable_nvidia_gpu"
- assert no_nvidia_branch >= 0, "get_torch_index_url should gate ROCm on no-nvidia branch"
+ assert (
+ nvidia_call >= 0
+ ), "get_torch_index_url should call _has_usable_nvidia_gpu"
+ assert (
+ no_nvidia_branch >= 0
+ ), "get_torch_index_url should gate ROCm on no-nvidia branch"
assert (
rocm_call > no_nvidia_branch
), "ROCm detection should sit inside the 'no NVIDIA' branch"
@@ -1146,7 +1196,9 @@ class TestInstallShStructure:
continue
# Remove POSIX character classes [[:foo:]] before checking for [[ ]]
cleaned = re.sub(r"\[\[:[a-z]+:\]\]", "", line)
- assert "[[" not in cleaned, f"get_torch_index_url line {i} uses non-POSIX [["
+ assert (
+ "[[" not in cleaned
+ ), f"get_torch_index_url line {i} uses non-POSIX [["
def test_no_arithmetic_expansion_in_rocm_block(self):
"""ROCm detection block should not use (( )) (bash-only)."""
@@ -1307,7 +1359,9 @@ class TestLiveRegression:
# Load worker.py module
_WORKER_PATH = PACKAGE_ROOT / "studio" / "backend" / "core" / "training" / "worker.py"
-_EXPORT_WORKER_PATH = PACKAGE_ROOT / "studio" / "backend" / "core" / "export" / "worker.py"
+_EXPORT_WORKER_PATH = (
+ PACKAGE_ROOT / "studio" / "backend" / "core" / "export" / "worker.py"
+)
# The torchao Windows-ROCm stub was de-duplicated out of the export/training
# workers into a shared module; both workers now call into it.
_TORCHAO_STUB_PATH = PACKAGE_ROOT / "studio" / "backend" / "core" / "_torchao_stub.py"
@@ -1333,7 +1387,9 @@ class TestWorkerRocmMambaSsm:
def test_direct_wheel_url_returns_none_without_cuda_major(self, monkeypatch):
"""_direct_wheel_url should return None when cuda_major is empty (ROCm)."""
# Load module for function access
- _worker_spec = importlib.util.spec_from_file_location("test_worker", _WORKER_PATH)
+ _worker_spec = importlib.util.spec_from_file_location(
+ "test_worker", _WORKER_PATH
+ )
assert _worker_spec is not None and _worker_spec.loader is not None
worker_mod = importlib.util.module_from_spec(_worker_spec)
@@ -1565,7 +1621,9 @@ class TestHardwareAmdBranching:
def test_hardware_imports_amd_module(self):
"""hardware.py should import from amd module when IS_ROCM."""
- hw_path = PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ hw_path = (
+ PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ )
source = hw_path.read_text(encoding = "utf-8")
assert "from . import amd" in source
@@ -1573,13 +1631,17 @@ class TestHardwareAmdBranching:
"""get_gpu_utilization should dispatch to amd.py via _smi_query
when IS_ROCM, and the dispatcher itself must check IS_ROCM and
import the amd backend."""
- hw_path = PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ hw_path = (
+ PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ )
source = hw_path.read_text(encoding = "utf-8")
func_start = source.find("def get_gpu_utilization")
func_body = source[func_start : source.find("\ndef ", func_start + 1)]
assert '_smi_query("get_primary_gpu_utilization"' in func_body
smi = source[
- source.find("def _smi_query") : source.find("\ndef ", source.find("def _smi_query") + 1)
+ source.find("def _smi_query") : source.find(
+ "\ndef ", source.find("def _smi_query") + 1
+ )
]
assert "IS_ROCM" in smi
assert "from . import amd" in smi
@@ -1587,7 +1649,9 @@ class TestHardwareAmdBranching:
def test_hardware_branches_on_is_rocm_for_visible(self):
"""get_visible_gpu_utilization should dispatch to amd.py via
_smi_query when IS_ROCM."""
- hw_path = PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ hw_path = (
+ PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ )
source = hw_path.read_text(encoding = "utf-8")
func_start = source.find("def get_visible_gpu_utilization")
func_body = source[func_start : source.find("\ndef ", func_start + 1)]
@@ -1597,14 +1661,18 @@ class TestHardwareAmdBranching:
assert _re.search(r'_smi_query\(\s*"get_visible_gpu_utilization"', func_body)
smi = source[
- source.find("def _smi_query") : source.find("\ndef ", source.find("def _smi_query") + 1)
+ source.find("def _smi_query") : source.find(
+ "\ndef ", source.find("def _smi_query") + 1
+ )
]
assert "IS_ROCM" in smi
assert "from . import amd" in smi
def test_hardware_branches_on_is_rocm_for_physical_count(self):
"""get_physical_gpu_count should try amd.py when IS_ROCM."""
- hw_path = PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ hw_path = (
+ PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ )
source = hw_path.read_text(encoding = "utf-8")
func_start = source.find("def get_physical_gpu_count")
func_body = source[func_start : source.find("\ndef ", func_start + 1)]
@@ -1621,7 +1689,9 @@ class TestApplyGpuIdsRocmFallback:
def test_apply_gpu_ids_falls_back_to_torch_version_hip(self):
"""apply_gpu_ids should probe torch.version.hip when IS_ROCM is False and no ROCm env vars are set."""
- hw_path = PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ hw_path = (
+ PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ )
source = hw_path.read_text(encoding = "utf-8")
func_start = source.find("def apply_gpu_ids")
func_body = source[func_start : source.find("\ndef ", func_start + 1)]
@@ -1633,7 +1703,9 @@ class TestApplyGpuIdsRocmFallback:
ROCR_VISIBLE_DEVICES uses HSA agent-level indexing, not physical GPU indices.
Overwriting it breaks multi-GPU ROCm systems (see issue #6118).
"""
- hw_path = PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ hw_path = (
+ PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ )
source = hw_path.read_text(encoding = "utf-8")
func_start = source.find("def apply_gpu_ids")
func_body = source[func_start : source.find("\ndef ", func_start + 1)]
@@ -1642,7 +1714,9 @@ class TestApplyGpuIdsRocmFallback:
def test_apply_gpu_ids_rocm_fallback_is_guarded_by_try_except(self):
"""torch import in apply_gpu_ids must be wrapped in try/except so a missing torch never crashes."""
- hw_path = PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ hw_path = (
+ PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+ )
source = hw_path.read_text(encoding = "utf-8")
func_start = source.find("def apply_gpu_ids")
func_body = source[func_start : source.find("\ndef ", func_start + 1)]
@@ -1787,7 +1861,9 @@ class TestWindowsRocmIndexUrl:
assert "repo.amd.com" in url
def test_mirror_env_var_overrides_base(self, monkeypatch):
- monkeypatch.setenv("UNSLOTH_ROCM_WINDOWS_MIRROR", "https://my-mirror.example.com/rocm/whl")
+ monkeypatch.setenv(
+ "UNSLOTH_ROCM_WINDOWS_MIRROR", "https://my-mirror.example.com/rocm/whl"
+ )
# Reload module-level constant by calling helper directly
url = stack_mod._windows_rocm_index_url("gfx1200")
# The env var is read at module load time for _ROCM_WINDOWS_INDEX_BASE,
@@ -1900,7 +1976,9 @@ class TestGfxArchNameFallback:
"""hipinfo absent everywhere + amd-smi absent -> WMI name fallback."""
ps_result = MagicMock()
ps_result.returncode = 0
- ps_result.stdout = b"AMD Radeon(TM) 8060S Graphics\r\nMicrosoft Basic Display Adapter\r\n"
+ ps_result.stdout = (
+ b"AMD Radeon(TM) 8060S Graphics\r\nMicrosoft Basic Display Adapter\r\n"
+ )
def _run(cmd, **kwargs):
if cmd and "powershell.exe" in str(cmd[0]).lower():
@@ -2032,7 +2110,9 @@ class TestInstallBnbWindowsRocm:
os.environ.pop("BNB_ROCM_VERSION", None)
os.environ.pop(stack_mod._BNB_ROCM_VERSION_SOURCE_ENV, None)
with patch.object(stack_mod, "pip_install_try", return_value = True):
- with patch.object(stack_mod, "_detect_bnb_rocm_dll_ver", return_value = "72"):
+ with patch.object(
+ stack_mod, "_detect_bnb_rocm_dll_ver", return_value = "72"
+ ):
stack_mod._install_bnb_windows_rocm()
assert os.environ.get("BNB_ROCM_VERSION") == "72"
@@ -2042,7 +2122,9 @@ class TestInstallBnbWindowsRocm:
os.environ.pop("BNB_ROCM_VERSION", None)
os.environ.pop(stack_mod._BNB_ROCM_VERSION_SOURCE_ENV, None)
with patch.object(stack_mod, "pip_install_try", return_value = True):
- with patch.object(stack_mod, "_detect_bnb_rocm_dll_ver", return_value = "713"):
+ with patch.object(
+ stack_mod, "_detect_bnb_rocm_dll_ver", return_value = "713"
+ ):
stack_mod._install_bnb_windows_rocm()
assert os.environ.get("BNB_ROCM_VERSION") == "713"
@@ -2052,7 +2134,9 @@ class TestInstallBnbWindowsRocm:
os.environ.pop("BNB_ROCM_VERSION", None)
os.environ.pop(stack_mod._BNB_ROCM_VERSION_SOURCE_ENV, None)
with patch.object(stack_mod, "pip_install_try", return_value = True):
- with patch.object(stack_mod, "_detect_bnb_rocm_dll_ver", return_value = None):
+ with patch.object(
+ stack_mod, "_detect_bnb_rocm_dll_ver", return_value = None
+ ):
stack_mod._install_bnb_windows_rocm()
assert os.environ.get("BNB_ROCM_VERSION") == "72"
@@ -2091,7 +2175,9 @@ class TestInstallBnbWindowsRocm:
},
):
with patch.object(stack_mod, "pip_install_try", return_value = True):
- with patch.object(stack_mod, "_detect_bnb_rocm_dll_ver", return_value = "713"):
+ with patch.object(
+ stack_mod, "_detect_bnb_rocm_dll_ver", return_value = "713"
+ ):
with patch.object(
stack_mod, "_persist_bnb_rocm_version", return_value = True
) as mock_persist:
@@ -2112,7 +2198,9 @@ class TestInstallBnbWindowsRocm:
os.environ.pop("BNB_ROCM_VERSION", None)
os.environ.pop(stack_mod._BNB_ROCM_VERSION_SOURCE_ENV, None)
with patch.object(stack_mod, "pip_install_try", return_value = True):
- with patch.object(stack_mod, "_detect_bnb_rocm_dll_ver", return_value = "72"):
+ with patch.object(
+ stack_mod, "_detect_bnb_rocm_dll_ver", return_value = "72"
+ ):
with patch.object(
stack_mod.sysconfig, "get_path", return_value = str(site_packages)
):
@@ -2160,7 +2248,9 @@ class TestInstallBnbWindowsRocm:
encoding = "utf-8",
)
- with patch.object(stack_mod.sysconfig, "get_path", return_value = str(site_packages)):
+ with patch.object(
+ stack_mod.sysconfig, "get_path", return_value = str(site_packages)
+ ):
assert stack_mod._persist_bnb_rocm_version("713") is True
source = sitecustomize.read_text(encoding = "utf-8")
@@ -2176,7 +2266,9 @@ class TestInstallBnbWindowsRocm:
sitecustomize = site_packages / "sitecustomize.py"
sitecustomize.write_bytes(b"\xff\xfe\x00")
- with patch.object(stack_mod.sysconfig, "get_path", return_value = str(site_packages)):
+ with patch.object(
+ stack_mod.sysconfig, "get_path", return_value = str(site_packages)
+ ):
assert stack_mod._persist_bnb_rocm_version("72") is False
def test_persist_bnb_rocm_version_repairs_truncated_block(self, tmp_path):
@@ -2192,7 +2284,9 @@ class TestInstallBnbWindowsRocm:
encoding = "utf-8",
)
- with patch.object(stack_mod.sysconfig, "get_path", return_value = str(site_packages)):
+ with patch.object(
+ stack_mod.sysconfig, "get_path", return_value = str(site_packages)
+ ):
assert stack_mod._persist_bnb_rocm_version("713") is True
source = sitecustomize.read_text(encoding = "utf-8")
@@ -2215,7 +2309,9 @@ class TestInstallBnbWindowsRocm:
)
sitecustomize.write_text(block + "USER_MID = 1\n" + block, encoding = "utf-8")
- with patch.object(stack_mod.sysconfig, "get_path", return_value = str(site_packages)):
+ with patch.object(
+ stack_mod.sysconfig, "get_path", return_value = str(site_packages)
+ ):
assert stack_mod._persist_bnb_rocm_version("713") is True
source = sitecustomize.read_text(encoding = "utf-8")
@@ -2230,7 +2326,9 @@ class TestInstallBnbWindowsRocm:
site_packages = tmp_path / "site-packages"
site_packages.mkdir()
- with patch.object(stack_mod.sysconfig, "get_path", return_value = str(site_packages)):
+ with patch.object(
+ stack_mod.sysconfig, "get_path", return_value = str(site_packages)
+ ):
assert stack_mod._persist_bnb_rocm_version("72") is True
leftovers = [p.name for p in site_packages.iterdir() if "unsloth-tmp" in p.name]
@@ -2242,16 +2340,24 @@ class TestRuntimeBnbRocmSourceGuards:
"""Runtime entrypoints redetect managed defaults but keep caller overrides."""
_MAIN_PATH = PACKAGE_ROOT / "studio" / "backend" / "main.py"
- _TRAINING_WORKER_PATH = PACKAGE_ROOT / "studio" / "backend" / "core" / "training" / "worker.py"
+ _TRAINING_WORKER_PATH = (
+ PACKAGE_ROOT / "studio" / "backend" / "core" / "training" / "worker.py"
+ )
def test_main_gate_redetects_persisted_default(self):
source = self._MAIN_PATH.read_text(encoding = "utf-8")
- assert 'os.environ.get("UNSLOTH_BNB_ROCM_VERSION_SOURCE") == "sitecustomize"' in source
+ assert (
+ 'os.environ.get("UNSLOTH_BNB_ROCM_VERSION_SOURCE") == "sitecustomize"'
+ in source
+ )
assert 'os.environ["UNSLOTH_BNB_ROCM_VERSION_SOURCE"] = "detected"' in source
def test_worker_gate_redetects_persisted_default(self):
source = self._TRAINING_WORKER_PATH.read_text(encoding = "utf-8")
- assert 'os.environ.get("UNSLOTH_BNB_ROCM_VERSION_SOURCE") == "sitecustomize"' in source
+ assert (
+ 'os.environ.get("UNSLOTH_BNB_ROCM_VERSION_SOURCE") == "sitecustomize"'
+ in source
+ )
assert 'os.environ["UNSLOTH_BNB_ROCM_VERSION_SOURCE"] = "detected"' in source
def test_fallback_prefers_seeded_value_over_hardcoded_72(self):
@@ -2487,7 +2593,9 @@ class TestWorkerWindowsRocmPatches:
# entry-point function (not the trainer helper which has its own "# ── 2.").
idx_sec2 = source.find("# ── 2. Now import ML libraries")
assert idx_bnb != -1, "BNB_ROCM_VERSION not found in worker.py"
- assert idx_sec2 != -1, "'# ── 2. Now import ML libraries' marker not found in worker.py"
+ assert (
+ idx_sec2 != -1
+ ), "'# ── 2. Now import ML libraries' marker not found in worker.py"
assert idx_bnb < idx_sec2, (
"BNB_ROCM_VERSION must be set before section 2 ML imports "
f"(found at {idx_bnb}, section 2 at {idx_sec2})"
@@ -2775,12 +2883,16 @@ class TestHipSdkEnvPathResolution:
"""setup.ps1 must tell the user how to add the HIP bin dir to PATH."""
source = _SETUP_PS1_PATH.read_text(encoding = "utf-8")
# Should mention adding to PATH or SetEnvironmentVariable
- assert "PATH" in source and ("SetEnvironmentVariable" in source or "Add" in source)
+ assert "PATH" in source and (
+ "SetEnvironmentVariable" in source or "Add" in source
+ )
def test_install_provides_path_fix_hint(self):
"""install.ps1 must tell the user how to add the HIP bin dir to PATH."""
source = _INSTALL_PS1_PATH.read_text(encoding = "utf-8")
- assert "PATH" in source and ("SetEnvironmentVariable" in source or "Add" in source)
+ assert "PATH" in source and (
+ "SetEnvironmentVariable" in source or "Add" in source
+ )
# TEST: HIP SDK detected substep -- path + hipconfig version shown in terminal
@@ -2942,7 +3054,9 @@ class TestSetupShGccInstallDir:
# TEST: main.py -- BNB_ROCM_VERSION server startup + distributed stubs
_MAIN_PY_PATH = PACKAGE_ROOT / "studio" / "backend" / "main.py"
-_HARDWARE_PY_PATH = PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+_HARDWARE_PY_PATH = (
+ PACKAGE_ROOT / "studio" / "backend" / "utils" / "hardware" / "hardware.py"
+)
class TestServerStartupRocmFixes:
@@ -3184,7 +3298,9 @@ class TestApplyHostOverrides:
assert out.rocm_gfx_target is None
def test_malformed_forwarded_gfx_falls_back_to_has_rocm(self):
- out = _apply_host_overrides(cpu_host(), override_has_rocm = True, override_rocm_gfx = "junk")
+ out = _apply_host_overrides(
+ cpu_host(), override_has_rocm = True, override_rocm_gfx = "junk"
+ )
assert out.has_rocm is True
assert out.rocm_gfx_target is None
@@ -3285,14 +3401,22 @@ class TestRocmGfxForwarding:
# arch -> the host must still be treated as a GPU host and routed to the
# fork's per-gfx prebuilt, not ggml-org / a source build. Linux x64 and
# arm64 both go through the same fork branch.
- assert self._resolve_setup_sh_repo("x86_64", False, "gfx1100") == "unslothai/llama.cpp"
- assert self._resolve_setup_sh_repo("aarch64", False, "gfx1100") == "unslothai/llama.cpp"
+ assert (
+ self._resolve_setup_sh_repo("x86_64", False, "gfx1100")
+ == "unslothai/llama.cpp"
+ )
+ assert (
+ self._resolve_setup_sh_repo("aarch64", False, "gfx1100")
+ == "unslothai/llama.cpp"
+ )
def test_setup_sh_env_forwarded_gfx_resolves_to_fork(self):
# UNSLOTH_ROCM_GFX_ARCH set on a host where no probe fired (_setup_gfx
# empty, no usable NVIDIA, no ROCm tooling): setup.sh adopts the env arch
# and routes to the fork, same as the name-inference path.
- repo = self._resolve_setup_sh_repo("x86_64", False, "", rocm_gfx_arch_env = "gfx1100")
+ repo = self._resolve_setup_sh_repo(
+ "x86_64", False, "", rocm_gfx_arch_env = "gfx1100"
+ )
assert repo == "unslothai/llama.cpp"
def test_setup_sh_cpu_host_still_resolves_to_ggml(self):
@@ -3339,7 +3463,10 @@ class TestRocmGfxForwarding:
def test_setup_ps1_inferred_gfx_resolves_to_fork(self):
# Adrenalin-only Windows host: $HasROCm is false (no HIP runtime) but a
# gfx arch was inferred -> route to the fork's windows-rocm bundle.
- assert self._resolve_setup_ps1_repo(False, False, "gfx1100") == "unslothai/llama.cpp"
+ assert (
+ self._resolve_setup_ps1_repo(False, False, "gfx1100")
+ == "unslothai/llama.cpp"
+ )
def test_setup_ps1_cpu_host_still_resolves_to_ggml(self):
# No NVIDIA, no ROCm, no inferred gfx -> CPU host stays on ggml-org.
@@ -3394,7 +3521,9 @@ def test_pick_rocm_gfx_target_same_arch_multi_gpu(monkeypatch):
_INSTALL_SH_PATH = PACKAGE_ROOT / "install.sh"
-_LLAMA_CPP_PATH = PACKAGE_ROOT / "studio" / "backend" / "core" / "inference" / "llama_cpp.py"
+_LLAMA_CPP_PATH = (
+ PACKAGE_ROOT / "studio" / "backend" / "core" / "inference" / "llama_cpp.py"
+)
class TestWslSystemRocmLibDirs:
@@ -3471,7 +3600,9 @@ class TestBinaryEnvWslOrdering:
# real dir to stand in for the system ROCm lib path.
sys_rocm = tmp_path / "sysrocm"
sys_rocm.mkdir()
- with patch.object(prebuilt_mod, "_wsl_system_rocm_lib_dirs", return_value = [str(sys_rocm)]):
+ with patch.object(
+ prebuilt_mod, "_wsl_system_rocm_lib_dirs", return_value = [str(sys_rocm)]
+ ):
with patch.dict(os.environ, {}, clear = True):
env = prebuilt_mod.binary_env(binary, tmp_path, self._linux_host())
ld = env["LD_LIBRARY_PATH"].split(os.pathsep)
diff --git a/tests/studio/install/test_selection_logic.py b/tests/studio/install/test_selection_logic.py
index 4e8beeb888..48e39d3eb3 100644
--- a/tests/studio/install/test_selection_logic.py
+++ b/tests/studio/install/test_selection_logic.py
@@ -25,7 +25,9 @@ import pytest
PACKAGE_ROOT = Path(__file__).resolve().parents[3]
MODULE_PATH = PACKAGE_ROOT / "studio" / "install_llama_prebuilt.py"
RUN_MODULE_PATH = PACKAGE_ROOT / "studio" / "backend" / "run.py"
-SPEC = importlib.util.spec_from_file_location("studio_install_llama_prebuilt", MODULE_PATH)
+SPEC = importlib.util.spec_from_file_location(
+ "studio_install_llama_prebuilt", MODULE_PATH
+)
assert SPEC is not None and SPEC.loader is not None
INSTALL_LLAMA_PREBUILT = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = INSTALL_LLAMA_PREBUILT
@@ -50,7 +52,9 @@ supports_explicit_visible_device_matching = (
select_visible_gpu_rows = INSTALL_LLAMA_PREBUILT.select_visible_gpu_rows
compatible_linux_runtime_lines = INSTALL_LLAMA_PREBUILT.compatible_linux_runtime_lines
pick_windows_cuda_runtime = INSTALL_LLAMA_PREBUILT.pick_windows_cuda_runtime
-compatible_windows_runtime_lines = INSTALL_LLAMA_PREBUILT.compatible_windows_runtime_lines
+compatible_windows_runtime_lines = (
+ INSTALL_LLAMA_PREBUILT.compatible_windows_runtime_lines
+)
runtime_line_from_cuda_version = INSTALL_LLAMA_PREBUILT.runtime_line_from_cuda_version
apply_approved_hashes = INSTALL_LLAMA_PREBUILT.apply_approved_hashes
linux_cuda_choice_from_release = INSTALL_LLAMA_PREBUILT.linux_cuda_choice_from_release
@@ -62,11 +66,19 @@ _fork_manifest_release_plans = INSTALL_LLAMA_PREBUILT._fork_manifest_release_pla
resolve_published_release = INSTALL_LLAMA_PREBUILT.resolve_published_release
resolve_source_build_plan = INSTALL_LLAMA_PREBUILT.resolve_source_build_plan
validated_checksums_for_bundle = INSTALL_LLAMA_PREBUILT.validated_checksums_for_bundle
-parse_approved_release_checksums = INSTALL_LLAMA_PREBUILT.parse_approved_release_checksums
-published_release_matches_request = INSTALL_LLAMA_PREBUILT.published_release_matches_request
-exact_source_archive_logical_name = INSTALL_LLAMA_PREBUILT.exact_source_archive_logical_name
+parse_approved_release_checksums = (
+ INSTALL_LLAMA_PREBUILT.parse_approved_release_checksums
+)
+published_release_matches_request = (
+ INSTALL_LLAMA_PREBUILT.published_release_matches_request
+)
+exact_source_archive_logical_name = (
+ INSTALL_LLAMA_PREBUILT.exact_source_archive_logical_name
+)
source_archive_logical_name = INSTALL_LLAMA_PREBUILT.source_archive_logical_name
-windows_cuda_upstream_asset_names = INSTALL_LLAMA_PREBUILT.windows_cuda_upstream_asset_names
+windows_cuda_upstream_asset_names = (
+ INSTALL_LLAMA_PREBUILT.windows_cuda_upstream_asset_names
+)
env_int = INSTALL_LLAMA_PREBUILT.env_int
direct_upstream_release_plan = INSTALL_LLAMA_PREBUILT.direct_upstream_release_plan
_pinned_windows_cuda_fallback = INSTALL_LLAMA_PREBUILT._pinned_windows_cuda_fallback
@@ -77,7 +89,9 @@ _windows_cuda_attempt_covers_blackwell = (
)
resolve_release_asset_choice = INSTALL_LLAMA_PREBUILT.resolve_release_asset_choice
pinned_macos_release_tag = INSTALL_LLAMA_PREBUILT.pinned_macos_release_tag
-resolve_simple_install_release_plans = INSTALL_LLAMA_PREBUILT.resolve_simple_install_release_plans
+resolve_simple_install_release_plans = (
+ INSTALL_LLAMA_PREBUILT.resolve_simple_install_release_plans
+)
def load_studio_run_module(monkeypatch):
@@ -230,7 +244,9 @@ def make_checksums_with_source(
kind = "upstream-source",
),
}
- normalized_source_commit = source_commit.lower() if isinstance(source_commit, str) else None
+ normalized_source_commit = (
+ source_commit.lower() if isinstance(source_commit, str) else None
+ )
if normalized_source_commit:
artifacts[exact_source_archive_logical_name(normalized_source_commit)] = (
ApprovedArtifactHash(
@@ -250,7 +266,9 @@ def make_checksums_with_source(
requested_source_ref = requested_source_ref,
resolved_source_ref = resolved_source_ref,
source_commit = normalized_source_commit,
- source_commit_short = normalized_source_commit[:7] if normalized_source_commit else None,
+ source_commit_short = normalized_source_commit[:7]
+ if normalized_source_commit
+ else None,
artifacts = artifacts,
)
@@ -345,7 +363,10 @@ class TestStudioLocalhostIpv6Warning:
lambda host, port, timeout = 1.0: True,
)
- assert run_module._localhost_ipv6_mismatch_url("127.0.0.1", 8888) == "http://127.0.0.1:8888"
+ assert (
+ run_module._localhost_ipv6_mismatch_url("127.0.0.1", 8888)
+ == "http://127.0.0.1:8888"
+ )
@pytest.mark.parametrize("host", ["0.0.0.0", "::"])
def test_network_bind_suppresses_warning(self, monkeypatch, host):
@@ -410,7 +431,9 @@ class TestStudioLocalhostIpv6Warning:
monkeypatch.setattr(
run_module,
"_verify_global_reachability",
- lambda display_host, port: calls["reachability"].append((display_host, port)),
+ lambda display_host, port: calls["reachability"].append(
+ (display_host, port)
+ ),
)
return calls
@@ -433,7 +456,9 @@ class TestStudioLocalhostIpv6Warning:
def test_emit_startup_output_plain_localhost(self, monkeypatch):
run_module = load_studio_run_module(monkeypatch)
calls = self._wire_recorders(run_module, monkeypatch)
- monkeypatch.setattr(run_module, "_localhost_ipv6_mismatch_url", lambda host, port: None)
+ monkeypatch.setattr(
+ run_module, "_localhost_ipv6_mismatch_url", lambda host, port: None
+ )
run_module._emit_startup_output("127.0.0.1", 8888, "127.0.0.1")
@@ -446,7 +471,9 @@ class TestStudioLocalhostIpv6Warning:
def test_emit_startup_output_wildcard_runs_reachability(self, monkeypatch, host):
run_module = load_studio_run_module(monkeypatch)
calls = self._wire_recorders(run_module, monkeypatch)
- monkeypatch.setattr(run_module, "_localhost_ipv6_mismatch_url", lambda h, port: None)
+ monkeypatch.setattr(
+ run_module, "_localhost_ipv6_mismatch_url", lambda h, port: None
+ )
run_module._emit_startup_output(host, 8888, "203.0.113.5")
@@ -833,7 +860,9 @@ class TestPublishedReleaseResolution:
def fake_load(repo, release_tag):
if release_tag == "v2.0":
raise PrebuiltFallback("checksum asset missing")
- return make_checksums_with_source([], release_tag = "v1.0", upstream_tag = "b8999")
+ return make_checksums_with_source(
+ [], release_tag = "v1.0", upstream_tag = "b8999"
+ )
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
@@ -863,7 +892,9 @@ class TestPublishedReleaseResolution:
),
)
- assert resolve_requested_install_tag("b8508", "", "unslothai/llama.cpp") == "b8508"
+ assert (
+ resolve_requested_install_tag("b8508", "", "unslothai/llama.cpp") == "b8508"
+ )
def test_concrete_tag_without_matching_release_raises(self, monkeypatch):
release = make_release([], release_tag = "release-b9000", upstream_tag = "b9000")
@@ -877,7 +908,9 @@ class TestPublishedReleaseResolution:
resolve_requested_install_tag("b8508", "", "unslothai/llama.cpp")
def test_pinned_release_must_match_requested_upstream_tag(self, monkeypatch):
- bundle = make_release([], release_tag = "llama-prebuilt-latest", upstream_tag = "b9000")
+ bundle = make_release(
+ [], release_tag = "llama-prebuilt-latest", upstream_tag = "b9000"
+ )
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"pinned_published_release_bundle",
@@ -1053,13 +1086,15 @@ class TestSourceBuildPlanResolution:
assert plan.source_ref == "main"
assert plan.compatibility_upstream_tag == "b9000"
- def test_direct_main_request_without_published_release_uses_branch_kind(self, monkeypatch):
+ def test_direct_main_request_without_published_release_uses_branch_kind(
+ self, monkeypatch
+ ):
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"resolve_published_release",
- lambda requested_tag, published_repo, published_release_tag = "": (_ for _ in ()).throw(
- PrebuiltFallback("missing")
- ),
+ lambda requested_tag, published_repo, published_release_tag = "": (
+ _ for _ in ()
+ ).throw(PrebuiltFallback("missing")),
)
plan = resolve_source_build_plan("main", "unslothai/llama.cpp")
@@ -1134,7 +1169,9 @@ class TestValidatedChecksumsForBundle:
def test_rejects_manifest_checksum_mismatch(self, monkeypatch):
bundle = make_release([], release_tag = "r1", upstream_tag = "b8508")
bundle.manifest_sha256 = "a" * 64
- checksums = make_checksums_with_source([], release_tag = "r1", upstream_tag = "b8508")
+ checksums = make_checksums_with_source(
+ [], release_tag = "r1", upstream_tag = "b8508"
+ )
checksums.artifacts[bundle.manifest_asset_name] = ApprovedArtifactHash(
asset_name = bundle.manifest_asset_name,
sha256 = "b" * 64,
@@ -1187,9 +1224,13 @@ class TestValidatedChecksumsForBundle:
lambda repo, release_tag: checksums,
)
- assert validated_checksums_for_bundle("unslothai/llama.cpp", bundle) is checksums
+ assert (
+ validated_checksums_for_bundle("unslothai/llama.cpp", bundle) is checksums
+ )
plan = INSTALL_LLAMA_PREBUILT.source_build_plan_for_release(
- INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease(bundle = bundle, checksums = checksums)
+ INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease(
+ bundle = bundle, checksums = checksums
+ )
)
assert plan.source_url == "https://github.com/ggml-org/llama.cpp"
assert plan.source_ref_kind == "commit"
@@ -1233,7 +1274,9 @@ class TestLinuxCudaChoiceFromRelease:
art12 = make_artifact("bundle-cuda12.tar.gz", runtime_line = "cuda12")
art13 = make_artifact("bundle-cuda13.tar.gz", runtime_line = "cuda13")
release = make_release([art12, art13])
- result = linux_cuda_choice_from_release(host, release, preferred_runtime_line = "cuda12")
+ result = linux_cuda_choice_from_release(
+ host, release, preferred_runtime_line = "cuda12"
+ )
assert result is not None
assert result.primary.runtime_line == "cuda12"
@@ -1242,7 +1285,9 @@ class TestLinuxCudaChoiceFromRelease:
host = make_host(driver_cuda_version = (12, 8))
art = make_artifact("bundle-cuda12.tar.gz", runtime_line = "cuda12")
release = make_release([art])
- result = linux_cuda_choice_from_release(host, release, preferred_runtime_line = "cuda13")
+ result = linux_cuda_choice_from_release(
+ host, release, preferred_runtime_line = "cuda13"
+ )
assert result is not None
assert result.primary.runtime_line == "cuda12"
log_entries = result.selection_log
@@ -1283,7 +1328,9 @@ class TestLinuxCudaChoiceFromRelease:
def test_exact_sm_match(self, monkeypatch):
mock_linux_runtime(monkeypatch, ["cuda12"])
host = make_host(compute_caps = ["86"])
- art = make_artifact("bundle.tar.gz", supported_sms = ["75", "86", "89"], min_sm = 75, max_sm = 89)
+ art = make_artifact(
+ "bundle.tar.gz", supported_sms = ["75", "86", "89"], min_sm = 75, max_sm = 89
+ )
release = make_release([art])
result = linux_cuda_choice_from_release(host, release)
assert result is not None
@@ -1292,7 +1339,9 @@ class TestLinuxCudaChoiceFromRelease:
def test_sm_not_in_supported_sms(self, monkeypatch):
mock_linux_runtime(monkeypatch, ["cuda12"])
host = make_host(compute_caps = ["86"])
- art = make_artifact("bundle.tar.gz", supported_sms = ["75", "80", "89"], min_sm = 75, max_sm = 89)
+ art = make_artifact(
+ "bundle.tar.gz", supported_sms = ["75", "80", "89"], min_sm = 75, max_sm = 89
+ )
release = make_release([art])
result = linux_cuda_choice_from_release(host, release)
assert result is None
@@ -1300,7 +1349,9 @@ class TestLinuxCudaChoiceFromRelease:
def test_sm_outside_min_range(self, monkeypatch):
mock_linux_runtime(monkeypatch, ["cuda12"])
host = make_host(compute_caps = ["50"])
- art = make_artifact("bundle.tar.gz", supported_sms = ["50", "75", "86"], min_sm = 75, max_sm = 90)
+ art = make_artifact(
+ "bundle.tar.gz", supported_sms = ["50", "75", "86"], min_sm = 75, max_sm = 90
+ )
release = make_release([art])
result = linux_cuda_choice_from_release(host, release)
assert result is None
@@ -1369,7 +1420,9 @@ class TestLinuxCudaChoiceFromRelease:
def test_multi_gpu_not_all_covered(self, monkeypatch):
mock_linux_runtime(monkeypatch, ["cuda12"])
host = make_host(compute_caps = ["50", "89"])
- art = make_artifact("bundle.tar.gz", supported_sms = ["75", "89"], min_sm = 75, max_sm = 89)
+ art = make_artifact(
+ "bundle.tar.gz", supported_sms = ["75", "89"], min_sm = 75, max_sm = 89
+ )
release = make_release([art])
result = linux_cuda_choice_from_release(host, release)
assert result is None
@@ -1488,7 +1541,9 @@ class TestLinuxCudaChoiceFromRelease:
class TestResolveInstallAttempts:
- def test_windows_cuda_prefers_published_asset_from_selected_release(self, monkeypatch):
+ def test_windows_cuda_prefers_published_asset_from_selected_release(
+ self, monkeypatch
+ ):
host = make_host(system = "Windows", machine = "AMD64")
host.driver_cuda_version = (12, 4)
mock_windows_runtime(monkeypatch, ["cuda12"])
@@ -1532,7 +1587,9 @@ class TestResolveInstallAttempts:
INSTALL_LLAMA_PREBUILT,
"github_release_assets",
lambda repo, tag: (_ for _ in ()).throw(
- AssertionError("published Windows CUDA choice should not query upstream")
+ AssertionError(
+ "published Windows CUDA choice should not query upstream"
+ )
),
)
@@ -1554,7 +1611,9 @@ class TestResolveInstallAttempts:
host = make_host(system = "Windows", machine = "AMD64")
host.driver_cuda_version = (12, 4)
mock_windows_runtime(monkeypatch, ["cuda12"])
- release = make_release([], release_tag = "llama-prebuilt-latest", upstream_tag = "b9000")
+ release = make_release(
+ [], release_tag = "llama-prebuilt-latest", upstream_tag = "b9000"
+ )
checksums = make_checksums_with_source(
["llama-b9000-bin-win-cuda-12.4-x64.zip"],
release_tag = release.release_tag,
@@ -1609,7 +1668,9 @@ class TestResolveInstallAttempts:
assert attempts[0].expected_sha256 == "a" * 64
assert approved.release_tag == "llama-prebuilt-latest"
- def test_linux_cpu_fork_without_bundle_raises_no_upstream_fallback(self, monkeypatch):
+ def test_linux_cpu_fork_without_bundle_raises_no_upstream_fallback(
+ self, monkeypatch
+ ):
# A CPU-only Linux host on the fork no longer falls back to the ggml-org
# CPU asset: production routes CPU-only Linux to ggml-org, never the fork.
# With no fork CPU bundle in the manifest the resolver raises rather than
@@ -1619,7 +1680,9 @@ class TestResolveInstallAttempts:
has_physical_nvidia = False,
nvidia_smi = None,
)
- release = make_release([], release_tag = "llama-prebuilt-latest", upstream_tag = "b9000")
+ release = make_release(
+ [], release_tag = "llama-prebuilt-latest", upstream_tag = "b9000"
+ )
checksums = make_checksums_with_source(
[],
release_tag = release.release_tag,
@@ -1646,12 +1709,16 @@ class TestResolveInstallAttempts:
),
)
- with pytest.raises(PrebuiltFallback, match = "no compatible Linux prebuilt asset was found"):
+ with pytest.raises(
+ PrebuiltFallback, match = "no compatible Linux prebuilt asset was found"
+ ):
resolve_install_attempts("latest", host, "unslothai/llama.cpp", "")
def test_linux_cuda_does_not_fall_back_to_upstream_cpu(self, monkeypatch):
host = make_host(system = "Linux", machine = "x86_64", compute_caps = ["86"])
- release = make_release([], release_tag = "llama-prebuilt-latest", upstream_tag = "b9000")
+ release = make_release(
+ [], release_tag = "llama-prebuilt-latest", upstream_tag = "b9000"
+ )
checksums = make_checksums_with_source(
[],
release_tag = release.release_tag,
@@ -1672,7 +1739,9 @@ class TestResolveInstallAttempts:
)
mock_linux_runtime(monkeypatch, ["cuda12"])
- with pytest.raises(PrebuiltFallback, match = "no compatible Linux prebuilt asset was found"):
+ with pytest.raises(
+ PrebuiltFallback, match = "no compatible Linux prebuilt asset was found"
+ ):
resolve_install_attempts("latest", host, "unslothai/llama.cpp", "")
def test_windows_cpu_prefers_published_asset(self, monkeypatch):
@@ -1882,7 +1951,9 @@ class TestResolveInstallReleasePlans:
max_sm = 90,
)
return INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease(
- bundle = make_release([art], release_tag = release_tag, upstream_tag = upstream_tag),
+ bundle = make_release(
+ [art], release_tag = release_tag, upstream_tag = upstream_tag
+ ),
checksums = make_checksums_with_source(
[asset_name],
release_tag = release_tag,
@@ -1890,7 +1961,9 @@ class TestResolveInstallReleasePlans:
),
)
- def test_latest_collects_multiple_older_release_plans_up_to_limit(self, monkeypatch):
+ def test_latest_collects_multiple_older_release_plans_up_to_limit(
+ self, monkeypatch
+ ):
mock_linux_runtime(monkeypatch, ["cuda12"])
host = make_host(system = "Linux", machine = "x86_64", compute_caps = ["86"])
releases = [
@@ -1902,7 +1975,9 @@ class TestResolveInstallReleasePlans:
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"iter_resolved_published_releases",
- lambda requested_tag, published_repo, published_release_tag = "": iter(releases),
+ lambda requested_tag, published_repo, published_release_tag = "": iter(
+ releases
+ ),
)
requested_tag, plans = _fork_manifest_release_plans(
@@ -1917,7 +1992,9 @@ class TestResolveInstallReleasePlans:
assert [plan.release_tag for plan in plans] == ["r3", "r2"]
assert [plan.llama_tag for plan in plans] == ["b9003", "b9002"]
- def test_latest_skips_non_installable_release_and_keeps_searching(self, monkeypatch):
+ def test_latest_skips_non_installable_release_and_keeps_searching(
+ self, monkeypatch
+ ):
mock_linux_runtime(monkeypatch, ["cuda12"])
host = make_host(system = "Linux", machine = "x86_64", compute_caps = ["86"])
releases = [
@@ -1936,7 +2013,9 @@ class TestResolveInstallReleasePlans:
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"iter_resolved_published_releases",
- lambda requested_tag, published_repo, published_release_tag = "": iter(releases),
+ lambda requested_tag, published_repo, published_release_tag = "": iter(
+ releases
+ ),
)
_requested_tag, plans = _fork_manifest_release_plans(
@@ -1953,9 +2032,13 @@ class TestResolveInstallReleasePlans:
def test_malformed_release_fallback_env_uses_default(self, monkeypatch):
monkeypatch.setenv("UNSLOTH_LLAMA_MAX_PREBUILT_RELEASE_FALLBACKS", "not-an-int")
- assert env_int("UNSLOTH_LLAMA_MAX_PREBUILT_RELEASE_FALLBACKS", 3, minimum = 1) == 3
+ assert (
+ env_int("UNSLOTH_LLAMA_MAX_PREBUILT_RELEASE_FALLBACKS", 3, minimum = 1) == 3
+ )
- def test_import_with_malformed_release_fallback_env_does_not_crash(self, monkeypatch):
+ def test_import_with_malformed_release_fallback_env_does_not_crash(
+ self, monkeypatch
+ ):
monkeypatch.setenv("UNSLOTH_LLAMA_MAX_PREBUILT_RELEASE_FALLBACKS", "bad-value")
spec = importlib.util.spec_from_file_location(
"studio_install_llama_prebuilt_env_reload",
@@ -2225,14 +2308,23 @@ class TestPinnedBlackwellCudaFallback:
assert pin.runtime_sha256 and len(pin.runtime_sha256) == 64
def test_pin_offered_for_driver_13_2(self):
- assert _pinned_windows_cuda_fallback(self._win_host((13, 2), ["120"]), []) is not None
+ assert (
+ _pinned_windows_cuda_fallback(self._win_host((13, 2), ["120"]), [])
+ is not None
+ )
def test_pin_offered_for_sm121_variant(self):
# sm_121 is Blackwell-family and also needs toolkit >= 12.8.
- assert _pinned_windows_cuda_fallback(self._win_host((13, 1), ["121"]), []) is not None
+ assert (
+ _pinned_windows_cuda_fallback(self._win_host((13, 1), ["121"]), [])
+ is not None
+ )
def test_pin_uses_max_of_multi_gpu_caps(self):
- assert _pinned_windows_cuda_fallback(self._win_host((13, 1), ["86", "120"]), []) is not None
+ assert (
+ _pinned_windows_cuda_fallback(self._win_host((13, 1), ["86", "120"]), [])
+ is not None
+ )
@pytest.mark.parametrize("sm", ["89", "90", "100"])
def test_pin_not_offered_to_non_blackwell(self, sm):
@@ -2243,11 +2335,16 @@ class TestPinnedBlackwellCudaFallback:
# b9360 is native sm_120a SASS (no JIT) and ships a cuda-13.1 cudart,
# both of which run on a 13.0 r580+ driver via CUDA minor-version
# compatibility. 13.0 is the mainstream Blackwell branch, so it must fire.
- assert _pinned_windows_cuda_fallback(self._win_host((13, 0), ["120"]), []) is not None
+ assert (
+ _pinned_windows_cuda_fallback(self._win_host((13, 0), ["120"]), [])
+ is not None
+ )
def test_pin_not_offered_below_floor(self):
# 12.x predates Blackwell entirely; the pin stays dormant below 13.0.
- assert _pinned_windows_cuda_fallback(self._win_host((12, 9), ["120"]), []) is None
+ assert (
+ _pinned_windows_cuda_fallback(self._win_host((12, 9), ["120"]), []) is None
+ )
def test_pin_not_offered_without_driver(self):
assert _pinned_windows_cuda_fallback(self._win_host(None, ["120"]), []) is None
@@ -2321,7 +2418,10 @@ class TestPinnedBlackwellCudaFallback:
],
)
def test_attempt_covers_blackwell(self, minor, covers):
- assert _windows_cuda_attempt_covers_blackwell(self._win_cuda_attempt(minor)) is covers
+ assert (
+ _windows_cuda_attempt_covers_blackwell(self._win_cuda_attempt(minor))
+ is covers
+ )
def test_attempt_covers_blackwell_ignores_non_cuda_kind(self):
cpu = AssetChoice(
@@ -2359,7 +2459,9 @@ class TestPinnedBlackwellCudaFallback:
("older", "cuda12", 89, False), # 12.4 toolkit app bundle stops at Ada
],
)
- def test_attempt_covers_blackwell_app_bundle(self, profile, runtime_line, max_sm, covers):
+ def test_attempt_covers_blackwell_app_bundle(
+ self, profile, runtime_line, max_sm, covers
+ ):
# App-named bundles carry no toolkit minor; coverage is read from max_sm.
attempt = self._app_attempt(profile, runtime_line, max_sm)
assert _windows_cuda_attempt_covers_blackwell(attempt) is covers
@@ -2397,7 +2499,8 @@ class TestDirectUpstreamBlackwellPin:
return {
"tag_name": self.TAG,
"assets": [
- {"name": n, "browser_download_url": f"https://example.com/{n}"} for n in names
+ {"name": n, "browser_download_url": f"https://example.com/{n}"}
+ for n in names
],
}
@@ -2417,7 +2520,9 @@ class TestDirectUpstreamBlackwellPin:
driver_cuda_version = (13, 1),
compute_caps = ["120"],
)
- plan = direct_upstream_release_plan(self._release(), host, UPSTREAM_REPO, "latest")
+ plan = direct_upstream_release_plan(
+ self._release(), host, UPSTREAM_REPO, "latest"
+ )
order = [(a.tag, a.runtime_line or a.install_kind) for a in plan.attempts]
# cuda-12.4 (toolkit 12.4, no sm_120) is dropped entirely on Blackwell:
# behind the pin it would still be attempted if the pin download failed,
@@ -2436,7 +2541,9 @@ class TestDirectUpstreamBlackwellPin:
driver_cuda_version = (13, 3),
compute_caps = ["120"],
)
- plan = direct_upstream_release_plan(self._release(), host, UPSTREAM_REPO, "latest")
+ plan = direct_upstream_release_plan(
+ self._release(), host, UPSTREAM_REPO, "latest"
+ )
assert "b9360" not in [a.tag for a in plan.attempts]
assert plan.attempts[0].tag == self.TAG
assert plan.attempts[0].runtime_line == "cuda13"
@@ -2514,7 +2621,9 @@ class TestBlackwellCuda124Exclusion:
max_sm = 80,
)
assert (
- INSTALL_LLAMA_PREBUILT._drop_blackwell_incapable_windows_cuda(self._bw_host(), [bundle])
+ INSTALL_LLAMA_PREBUILT._drop_blackwell_incapable_windows_cuda(
+ self._bw_host(), [bundle]
+ )
== []
)
@@ -2527,7 +2636,9 @@ class TestBlackwellCuda124Exclusion:
)
attempts = [self._upstream_cuda("12.4")]
assert (
- INSTALL_LLAMA_PREBUILT._drop_blackwell_incapable_windows_cuda(host, attempts)
+ INSTALL_LLAMA_PREBUILT._drop_blackwell_incapable_windows_cuda(
+ host, attempts
+ )
== attempts
)
@@ -2641,7 +2752,9 @@ class TestPublishedWindowsCudaAttemptsDynamicMajor:
# the old hardcoded cuda12/cuda13 seed would never order it (the cuda14
# line would be skipped for want of a 14.x asset in the seed).
mock_windows_runtime(monkeypatch, ["cuda14", "cuda13", "cuda12"])
- release = self._release([("14.0", "cuda14"), ("13.3", "cuda13"), ("12.4", "cuda12")])
+ release = self._release(
+ [("14.0", "cuda14"), ("13.3", "cuda13"), ("12.4", "cuda12")]
+ )
host = make_host(
system = "Windows",
machine = "AMD64",
@@ -3127,14 +3240,18 @@ class TestResolveUpstreamAssetChoice:
def test_linux_x86_64_cpu(self, monkeypatch):
name = f"llama-{self.TAG}-bin-ubuntu-x64.tar.gz"
self._mock_github_assets(monkeypatch, {name: f"https://x/{name}"})
- host = make_host(has_usable_nvidia = False, nvidia_smi = None, has_physical_nvidia = False)
+ host = make_host(
+ has_usable_nvidia = False, nvidia_smi = None, has_physical_nvidia = False
+ )
result = resolve_upstream_asset_choice(host, self.TAG)
assert result.install_kind == "linux-cpu"
assert result.name == name
def test_linux_cpu_missing(self, monkeypatch):
self._mock_github_assets(monkeypatch, {})
- host = make_host(has_usable_nvidia = False, nvidia_smi = None, has_physical_nvidia = False)
+ host = make_host(
+ has_usable_nvidia = False, nvidia_smi = None, has_physical_nvidia = False
+ )
with pytest.raises(PrebuiltFallback, match = "Linux CPU"):
resolve_upstream_asset_choice(host, self.TAG)
@@ -3221,7 +3338,9 @@ class TestResolveUpstreamAssetChoice:
has_physical_nvidia = False,
has_usable_nvidia = False,
)
- with pytest.raises(PrebuiltFallback, match = "no prebuilt policy exists for Linux aarch64"):
+ with pytest.raises(
+ PrebuiltFallback, match = "no prebuilt policy exists for Linux aarch64"
+ ):
resolve_upstream_asset_choice(host, self.TAG)
def test_windows_usable_nvidia_delegates(self, monkeypatch):
@@ -3347,7 +3466,9 @@ class TestResolveSimpleMacosPin:
for tag in self.TAGS:
yield _release(tag)
- monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "iter_release_payloads_by_time", fake_iter)
+ monkeypatch.setattr(
+ INSTALL_LLAMA_PREBUILT, "iter_release_payloads_by_time", fake_iter
+ )
return calls
def test_pre26_host_pins_b9415_without_walkback(self, monkeypatch):
@@ -3399,9 +3520,13 @@ class TestLinuxArm64ForkFallsBackToSource:
called["args"] = (host.machine, repo)
return "b9457", ["plan"]
- monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "_fork_manifest_release_plans", _full)
+ monkeypatch.setattr(
+ INSTALL_LLAMA_PREBUILT, "_fork_manifest_release_plans", _full
+ )
host = make_host(system = "Linux", machine = "aarch64")
- tag, plans = resolve_simple_install_release_plans("latest", host, "unslothai/llama.cpp", "")
+ tag, plans = resolve_simple_install_release_plans(
+ "latest", host, "unslothai/llama.cpp", ""
+ )
assert called.get("args") == ("aarch64", "unslothai/llama.cpp")
assert plans == ["plan"]
@@ -3415,9 +3540,13 @@ class TestLinuxArm64ForkFallsBackToSource:
called["args"] = (host.machine, repo)
return "b9457", ["plan"]
- monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "_fork_manifest_release_plans", _full)
+ monkeypatch.setattr(
+ INSTALL_LLAMA_PREBUILT, "_fork_manifest_release_plans", _full
+ )
host = make_host(system = "Linux", machine = "x86_64")
- tag, plans = resolve_simple_install_release_plans("latest", host, "unslothai/llama.cpp", "")
+ tag, plans = resolve_simple_install_release_plans(
+ "latest", host, "unslothai/llama.cpp", ""
+ )
assert called.get("args") == ("x86_64", "unslothai/llama.cpp")
assert plans == ["plan"]
@@ -3439,7 +3568,9 @@ class TestLinuxArm64ForkFallsBackToSource:
has_usable_nvidia = False,
)
with pytest.raises(PrebuiltFallback) as exc:
- resolve_simple_install_release_plans("latest", host, "ggml-org/llama.cpp", "")
+ resolve_simple_install_release_plans(
+ "latest", host, "ggml-org/llama.cpp", ""
+ )
assert "linux-x64 prebuilts" not in str(exc.value)
@@ -3525,7 +3656,9 @@ class TestCpuFallback:
has_physical_nvidia = False,
has_usable_nvidia = False,
)
- plan = direct_upstream_release_plan(release, cpu_host, "ggml-org/llama.cpp", "latest")
+ plan = direct_upstream_release_plan(
+ release, cpu_host, "ggml-org/llama.cpp", "latest"
+ )
assert plan.attempts[0].install_kind == "linux-arm64"
assert plan.attempts[0].name == f"llama-{tag}-bin-ubuntu-arm64.tar.gz"
@@ -3771,7 +3904,9 @@ class TestCudaDriverToolkitMismatchMessage:
nvcc.chmod(0o755)
return nvcc
- def test_setup_sh_major_mismatch_uses_newest_compatible_detected_toolkit(self, tmp_path):
+ def test_setup_sh_major_mismatch_uses_newest_compatible_detected_toolkit(
+ self, tmp_path
+ ):
blocked_nvcc = self._fake_nvcc(tmp_path, "13.3")
older_nvcc = self._fake_nvcc(tmp_path, "12.6")
compatible_nvcc = self._fake_nvcc(tmp_path, "12.8")
@@ -3955,7 +4090,9 @@ class TestCudaDriverToolkitMismatchMessage:
assert "GPU_BACKEND=cuda" in output
assert "ALLOWED=true" in output
- def test_setup_sh_compatible_finder_rejects_newer_major_only_candidate(self, tmp_path):
+ def test_setup_sh_compatible_finder_rejects_newer_major_only_candidate(
+ self, tmp_path
+ ):
# Only alternative is still newer-major than the driver: finder must fail, not pick it.
blocked_nvcc = self._fake_nvcc(tmp_path, "13.3")
other_newer_nvcc = self._fake_nvcc(tmp_path, "13.1")
diff --git a/tests/studio/load_freeze/llama_server_shim.py b/tests/studio/load_freeze/llama_server_shim.py
index 4030fcd6c4..2c0b8ff1b1 100644
--- a/tests/studio/load_freeze/llama_server_shim.py
+++ b/tests/studio/load_freeze/llama_server_shim.py
@@ -123,7 +123,9 @@ class _Handler(BaseHTTPRequestHandler):
self._send_raw(srv.config.detok_status, srv.config.detok_body)
return
tids = body.get("tokens") or []
- content = "".join(srv.config.detok_map.get(int(t), f"") for t in tids)
+ content = "".join(
+ srv.config.detok_map.get(int(t), f"") for t in tids
+ )
self._send_json(srv.config.detok_status, {"content": content})
return
if path == "/completion":
@@ -226,7 +228,9 @@ class FakeLlamaServer:
def start(self) -> "FakeLlamaServer":
# port=0 lets ThreadingHTTPServer pick a free port atomically (no
# find-then-bind race); read back via server_address[1].
- self._server = FakeLlamaServer._Server((self.host, self._requested_port), _Handler)
+ self._server = FakeLlamaServer._Server(
+ (self.host, self._requested_port), _Handler
+ )
self._server.config = self.config
bound_port = self._server.server_address[1]
self._thread = threading.Thread(
diff --git a/tests/studio/load_freeze/test_load_orchestrator.py b/tests/studio/load_freeze/test_load_orchestrator.py
index d091e56e70..a0ef080bc4 100644
--- a/tests/studio/load_freeze/test_load_orchestrator.py
+++ b/tests/studio/load_freeze/test_load_orchestrator.py
@@ -110,7 +110,9 @@ class _UvicornServerThread:
self.host = host
self.port = port
- cfg = uvicorn.Config(app, host = host, port = port, log_level = "warning", access_log = False)
+ cfg = uvicorn.Config(
+ app, host = host, port = port, log_level = "warning", access_log = False
+ )
self._server = uvicorn.Server(cfg)
self._server.install_signal_handlers = lambda: None # type: ignore[assignment]
self._thread: threading.Thread | None = None
@@ -214,7 +216,9 @@ def test_buggy_route_blocks_event_loop():
app = _build_app(backend, wrap_in_thread = False)
port = _free_port()
with _UvicornServerThread(app, port = port) as uv:
- max_lat, probe_t, _ = _drive_concurrent_probe_and_health(f"http://127.0.0.1:{uv.port}")
+ max_lat, probe_t, _ = _drive_concurrent_probe_and_health(
+ f"http://127.0.0.1:{uv.port}"
+ )
assert probe_t >= 0.5
assert max_lat >= 0.4, f"expected >=0.4s stall, got {max_lat:.3f}s"
@@ -437,7 +441,9 @@ def test_50_concurrent_probes_complete_without_deadlock():
with ThreadPoolExecutor(max_workers = 50) as pool:
futs = [
pool.submit(
- lambda: httpx.get(f"http://127.0.0.1:{uv.port}/probe", timeout = 30.0)
+ lambda: httpx.get(
+ f"http://127.0.0.1:{uv.port}/probe", timeout = 30.0
+ )
)
for _ in range(50)
]
diff --git a/tests/studio/playwright_chat_ime_i18n.py b/tests/studio/playwright_chat_ime_i18n.py
index f27266193d..65d80770b8 100644
--- a/tests/studio/playwright_chat_ime_i18n.py
+++ b/tests/studio/playwright_chat_ime_i18n.py
@@ -240,7 +240,8 @@ with sync_playwright() as p:
dir_attr = composer.evaluate("(el) => el.getAttribute('dir')")
if dir_attr != "auto":
soft_fail(
- f'composer is missing dir="auto" (got {dir_attr!r}); RTL ' "languages will render LTR."
+ f'composer is missing dir="auto" (got {dir_attr!r}); RTL '
+ "languages will render LTR."
)
else:
info('composer dir="auto" present')
@@ -251,7 +252,9 @@ with sync_playwright() as p:
_thread_src = (
_repo_root / "studio/frontend/src/components/assistant-ui/thread.tsx"
).read_text()
- _shared_src = (_repo_root / "studio/frontend/src/features/chat/shared-composer.tsx").read_text()
+ _shared_src = (
+ _repo_root / "studio/frontend/src/features/chat/shared-composer.tsx"
+ ).read_text()
_edit_idx = _thread_src.find("aui-edit-composer-input")
if _edit_idx == -1 or 'dir="auto"' not in _thread_src[_edit_idx : _edit_idx + 600]:
soft_fail('edit composer source is missing dir="auto"')
@@ -260,7 +263,8 @@ with sync_playwright() as p:
_compare_idx = _shared_src.find("Send to both models")
if (
_compare_idx == -1
- or 'dir="auto"' not in _shared_src[max(_compare_idx - 400, 0) : _compare_idx + 400]
+ or 'dir="auto"'
+ not in _shared_src[max(_compare_idx - 400, 0) : _compare_idx + 400]
):
soft_fail('compare composer source is missing dir="auto"')
else:
@@ -467,7 +471,9 @@ with sync_playwright() as p:
# IME keydown (isComposing=true / keyCode 229) must not slip preedit text
# through submit. The onKeyDown gate re-pins composingRef so handleSubmit
# refuses at form.requestSubmit() time, not at the (enabled) button.
- step("BUG REPRO: keydown re-pin after watchdog cleared composing (issue #5546 follow-up)")
+ step(
+ "BUG REPRO: keydown re-pin after watchdog cleared composing (issue #5546 follow-up)"
+ )
clear()
composer.click()
composer.evaluate(
@@ -513,7 +519,9 @@ with sync_playwright() as p:
"Form submitted after an IME keydown -- preedit text leaked "
"through the watchdog gap (#5546 follow-up regression)."
)
- info(f"Form submit refused after IME keydown; textarea retained {submit_probe.get('after')!r}")
+ info(
+ f"Form submit refused after IME keydown; textarea retained {submit_probe.get('after')!r}"
+ )
shoot("06c-keydown-repin")
info("keydown re-pin gate PASS")
clear()
diff --git a/tests/studio/playwright_chat_ui.py b/tests/studio/playwright_chat_ui.py
index 4739d519d9..0ab9194fa5 100644
--- a/tests/studio/playwright_chat_ui.py
+++ b/tests/studio/playwright_chat_ui.py
@@ -141,7 +141,10 @@ def expected_default_model():
for node in tree.body:
if not isinstance(node, ast.Assign):
continue
- if not any(isinstance(t, ast.Name) and t.id == "DEFAULT_MODELS_GGUF" for t in node.targets):
+ if not any(
+ isinstance(t, ast.Name) and t.id == "DEFAULT_MODELS_GGUF"
+ for t in node.targets
+ ):
continue
try:
models = ast.literal_eval(node.value)
@@ -318,7 +321,9 @@ with sync_playwright() as p:
form_err: Exception | None = None
for _form_attempt in range(3):
try:
- page.goto(f"{BASE}/change-password", wait_until = "domcontentloaded", timeout = 60_000)
+ page.goto(
+ f"{BASE}/change-password", wait_until = "domcontentloaded", timeout = 60_000
+ )
try:
page.wait_for_load_state("networkidle", timeout = 30_000)
except Exception:
@@ -377,7 +382,9 @@ with sync_playwright() as p:
flush = True,
)
if page_errors:
- print(f"[ui] first pageerror: {page_errors[0][:200]!r}", flush = True)
+ print(
+ f"[ui] first pageerror: {page_errors[0][:200]!r}", flush = True
+ )
try:
shoot(f"01-change-password-attempt-{_form_attempt + 1}-fail")
except Exception:
@@ -451,7 +458,9 @@ with sync_playwright() as p:
flush = True,
)
if page_errors:
- print(f"[ui] first pageerror: {page_errors[0][:200]!r}", flush = True)
+ print(
+ f"[ui] first pageerror: {page_errors[0][:200]!r}", flush = True
+ )
try:
shoot(f"03-composer-wait-attempt-{_attempt + 1}-fail")
except Exception:
@@ -548,7 +557,9 @@ with sync_playwright() as p:
try:
sel_text = (selector_btn.text_content(timeout = 2_000) or "").strip()
except Exception as _sel_err:
- info(f"WARN: model-selector probe skipped: {type(_sel_err).__name__}: {_sel_err}")
+ info(
+ f"WARN: model-selector probe skipped: {type(_sel_err).__name__}: {_sel_err}"
+ )
if sel_text:
info(f"model selector button text: {sel_text!r}")
shoot("03b-default-model-button")
@@ -584,7 +595,10 @@ with sync_playwright() as p:
if load_resp.get("error"):
fail(f"/api/inference/load wedged: {load_resp['error']!r}")
if load_resp["status"] != 200:
- fail(f"/api/inference/load returned {load_resp['status']}: " f"{load_resp.get('body')!r}")
+ fail(
+ f"/api/inference/load returned {load_resp['status']}: "
+ f"{load_resp.get('body')!r}"
+ )
info(f"loaded model: {(load_resp['body'] or {}).get('display_name')}")
# Studio caches the per-context model state in zustand; reload
@@ -831,7 +845,8 @@ with sync_playwright() as p:
# Look for either "Disable X" or "Enable X" -- whichever
# is currently rendered.
toggle = page.locator(
- f'button[aria-label="Disable {feature}"], ' f'button[aria-label="Enable {feature}"]'
+ f'button[aria-label="Disable {feature}"], '
+ f'button[aria-label="Enable {feature}"]'
).first
if toggle.count() == 0:
info(f"toggle '{feature}' not present on this layout")
@@ -847,7 +862,8 @@ with sync_playwright() as p:
page.wait_for_timeout(200)
after = (
page.locator(
- f'button[aria-label="Disable {feature}"], ' f'button[aria-label="Enable {feature}"]'
+ f'button[aria-label="Disable {feature}"], '
+ f'button[aria-label="Enable {feature}"]'
).first.get_attribute("aria-label")
or ""
)
@@ -858,7 +874,8 @@ with sync_playwright() as p:
# Flip back so test state is unchanged.
try:
page.locator(
- f'button[aria-label="Disable {feature}"], ' f'button[aria-label="Enable {feature}"]'
+ f'button[aria-label="Disable {feature}"], '
+ f'button[aria-label="Enable {feature}"]'
).first.click()
except Exception:
pass
@@ -951,7 +968,8 @@ with sync_playwright() as p:
except Exception as exc:
if attempt == 1:
soft_fail(
- f"theme cycle {cycle + 1}: account-menu click failed " f"({exc!r})"
+ f"theme cycle {cycle + 1}: account-menu click failed "
+ f"({exc!r})"
)
continue
try:
@@ -1002,7 +1020,8 @@ with sync_playwright() as p:
if click_err is not None:
page.keyboard.press("Escape")
soft_fail(
- f"theme cycle {cycle + 1}: theme menuitem click failed " f"({click_err!r})"
+ f"theme cycle {cycle + 1}: theme menuitem click failed "
+ f"({click_err!r})"
)
break
# Settle. The ".dark" class on is the ground
@@ -1059,7 +1078,9 @@ with sync_playwright() as p:
# progressively more permissive locators so the test stays
# green on both platforms.
candidates = [
- page.get_by_role("button", name = re.compile(rf"^\s*{label}\s*$", re.I)).first,
+ page.get_by_role(
+ "button", name = re.compile(rf"^\s*{label}\s*$", re.I)
+ ).first,
page.locator(f'button:has-text("{label}")').first,
page.locator(f'a:has-text("{label}")').first,
page.locator(f'[data-sidebar="menu-button"]:has-text("{label}")').first,
@@ -1095,15 +1116,21 @@ with sync_playwright() as p:
click_nav("New Chat", r"/chat")
shoot("11-new-chat")
# Compare moved into the composer + menu (Tools and attachments).
- plus_btn = page.get_by_role("button", name = re.compile(r"Tools and attachments", re.I)).first
+ plus_btn = page.get_by_role(
+ "button", name = re.compile(r"Tools and attachments", re.I)
+ ).first
if plus_btn.count() > 0:
plus_btn.click(force = True)
page.wait_for_timeout(400)
- compare_item = page.get_by_role("menuitem", name = re.compile(r"Compare chat", re.I)).first
+ compare_item = page.get_by_role(
+ "menuitem", name = re.compile(r"Compare chat", re.I)
+ ).first
if compare_item.count() == 0:
# The plus menu was decluttered: Compare chat now lives in the
# "More" submenu; hover (then click as fallback) to open it.
- more_trigger = page.get_by_role("menuitem", name = re.compile(r"^More$", re.I)).first
+ more_trigger = page.get_by_role(
+ "menuitem", name = re.compile(r"^More$", re.I)
+ ).first
if more_trigger.count() > 0:
more_trigger.hover()
page.wait_for_timeout(400)
@@ -1150,7 +1177,9 @@ with sync_playwright() as p:
step("Developer (API) tab via account menu")
acct.click()
page.wait_for_timeout(400)
- dev = page.get_by_role("menuitem", name = re.compile(r"developer|api", re.I)).first
+ dev = page.get_by_role(
+ "menuitem", name = re.compile(r"developer|api", re.I)
+ ).first
if dev.count() > 0:
dev.click()
page.wait_for_timeout(800)
@@ -1167,7 +1196,9 @@ with sync_playwright() as p:
re.compile(r"api keys|developer", re.I),
).first
if keys_section.count() > 0:
- info(f"OK API tab text: {(keys_section.text_content() or '').strip()[:80]!r}")
+ info(
+ f"OK API tab text: {(keys_section.text_content() or '').strip()[:80]!r}"
+ )
# Close dialog with Escape.
page.keyboard.press("Escape")
page.wait_for_timeout(300)
@@ -1185,7 +1216,9 @@ with sync_playwright() as p:
page.wait_for_timeout(1500)
# Recipe cards are rendered as or button elements; count
# all clickable headings under main + screenshot.
- headings = page.locator("main h2, main h3, [data-recipe], a[href*='/data-recipes/']")
+ headings = page.locator(
+ "main h2, main h3, [data-recipe], a[href*='/data-recipes/']"
+ )
n_cards = headings.count()
info(f"Recipes route headings/cards: {n_cards}")
shoot("15b-recipes-cards")
@@ -1274,7 +1307,10 @@ with sync_playwright() as p:
info(f"recent-thread click {i} failed: {_click_err!s}")
continue
if not clicked_recent:
- soft_fail(f"no Recents entry was clickable within 30s deadline " f"(n_threads={n_threads})")
+ soft_fail(
+ f"no Recents entry was clickable within 30s deadline "
+ f"(n_threads={n_threads})"
+ )
# Back to chat.
page.goto(f"{BASE}/chat")
composer = page.locator('textarea[aria-label="Message input"]')
diff --git a/tests/studio/playwright_extra_ui.py b/tests/studio/playwright_extra_ui.py
index 26c3c244ca..6434f64bd3 100644
--- a/tests/studio/playwright_extra_ui.py
+++ b/tests/studio/playwright_extra_ui.py
@@ -170,7 +170,9 @@ with sync_playwright() as p:
form_err: Exception | None = None
for _form_attempt in range(3):
try:
- page.goto(f"{BASE}/change-password", wait_until = "domcontentloaded", timeout = 60_000)
+ page.goto(
+ f"{BASE}/change-password", wait_until = "domcontentloaded", timeout = 60_000
+ )
try:
page.wait_for_load_state("networkidle", timeout = 30_000)
except Exception:
@@ -327,14 +329,20 @@ with sync_playwright() as p:
step("Compare tab: send to two panes")
# Compare moved into the composer + menu (Tools and attachments).
compare_opened = False
- plus_btn = page.get_by_role("button", name = re.compile(r"Tools and attachments", re.I)).first
+ plus_btn = page.get_by_role(
+ "button", name = re.compile(r"Tools and attachments", re.I)
+ ).first
if plus_btn.count() > 0:
plus_btn.click(force = True)
page.wait_for_timeout(400)
- compare_item = page.get_by_role("menuitem", name = re.compile(r"Compare chat", re.I)).first
+ compare_item = page.get_by_role(
+ "menuitem", name = re.compile(r"Compare chat", re.I)
+ ).first
if compare_item.count() == 0:
# Compare chat moved into the "More" submenu; hover, then click fallback.
- more_trigger = page.get_by_role("menuitem", name = re.compile(r"^More$", re.I)).first
+ more_trigger = page.get_by_role(
+ "menuitem", name = re.compile(r"^More$", re.I)
+ ).first
if more_trigger.count() > 0:
more_trigger.hover()
page.wait_for_timeout(400)
@@ -425,7 +433,9 @@ with sync_playwright() as p:
arg = ok_count_before + 4,
timeout = 60_000,
)
- info("OK Compare: 4 total new assistant bubbles after second prompt")
+ info(
+ "OK Compare: 4 total new assistant bubbles after second prompt"
+ )
except Exception as exc:
runtime_warn(
f"Compare: 4 bubbles didn't appear (panes likely "
@@ -446,7 +456,9 @@ with sync_playwright() as p:
page.wait_for_timeout(1500)
shoot("05-recipes-list")
# Template cards render as