Compare commits
79 commits
main
...
auto-insta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6f1075812 | ||
|
|
657b3251f8 | ||
|
|
a370521879 | ||
|
|
b712f9f557 | ||
|
|
667467a315 | ||
|
|
55075f691e | ||
|
|
0e929cddac | ||
|
|
ecaf3dde2a | ||
|
|
fc9d82f6bf | ||
|
|
4d2afc62a0 | ||
|
|
43c669e6ec | ||
|
|
11b69c1051 | ||
|
|
39c7a4d290 | ||
|
|
ff1088af40 | ||
|
|
6d3849b821 | ||
|
|
8e26a368e1 | ||
|
|
e31d4c6aea | ||
|
|
5688072af6 | ||
|
|
d3ac7447eb | ||
|
|
2fdfe24fb1 | ||
|
|
e2215c9d11 | ||
|
|
b11f13a710 | ||
|
|
66ae2d416c | ||
|
|
b0a35ddeab | ||
|
|
405add94a1 | ||
|
|
64dc11faa2 | ||
|
|
07e2fccf38 | ||
|
|
d482382a92 |
||
|
|
342bbc2333 | ||
|
|
e1fe3be939 | ||
|
|
d459f60458 | ||
|
|
2c4bf9ae35 | ||
|
|
a87a08610e | ||
|
|
41abe89041 | ||
|
|
d638d1bd6f | ||
|
|
2e3b2bdc27 | ||
|
|
9cc539c1b4 | ||
|
|
973c7d80c2 | ||
|
|
c27f9b99e9 | ||
|
|
0c24d61708 | ||
|
|
e9a2b5c010 | ||
|
|
e8355451ea | ||
|
|
a7b4ae19ee | ||
|
|
9066946615 | ||
|
|
b2580ae32b | ||
|
|
7ae9580ce6 | ||
|
|
9ad3b761ee | ||
|
|
8f721d28d6 | ||
|
|
396aa05ead | ||
|
|
2297f73cad | ||
|
|
a728f7c308 | ||
|
|
884152daee | ||
|
|
7437af2e44 | ||
|
|
67678d2c29 | ||
|
|
e568000a92 | ||
|
|
6700dd60f0 | ||
|
|
abf578327c | ||
|
|
a4ae80cd6a | ||
|
|
ec47b2984d | ||
|
|
d9d1a63397 | ||
|
|
0bb6379aad | ||
|
|
1c608e8ff7 | ||
|
|
f9adf6834d | ||
|
|
1fd5853741 | ||
|
|
93a70fbe4e | ||
|
|
8b77451e75 | ||
|
|
481f0618ff | ||
|
|
ddf6f6d1f9 | ||
|
|
dddc9eac92 | ||
|
|
da81c94510 | ||
|
|
4c139503c1 | ||
|
|
9d1e3c38bc | ||
|
|
8bc26f4e1d | ||
|
|
044e67f5aa | ||
|
|
ba36c12240 | ||
|
|
0a5652281f | ||
|
|
bf63f79414 | ||
|
|
2973bea3d0 | ||
|
|
ddbbfe52cf |
462 changed files with 13541 additions and 5163 deletions
744
pyproject.toml
744
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 = ["."]
|
||||
|
|
|
|||
|
|
@ -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"<script[^>]*src\s*=\s*['\"][^'\"]*/{html_pkg}", content):
|
||||
if is_html and re.search(
|
||||
rf"<script[^>]*src\s*=\s*['\"][^'\"]*/{html_pkg}", content
|
||||
):
|
||||
return "html_script"
|
||||
if is_html and re.search(rf"<link[^>]*href\s*=\s*['\"][^'\"]*/{html_pkg}", content):
|
||||
return "html_link"
|
||||
# TypeScript triple-slash
|
||||
if is_ts and re.search(rf"///\s*<reference\s+types\s*=\s*['\"]{esc}{sub}['\"]", content):
|
||||
if is_ts and re.search(
|
||||
rf"///\s*<reference\s+types\s*=\s*['\"]{esc}{sub}['\"]", content
|
||||
):
|
||||
return "tsc_triple_slash"
|
||||
# new URL("pkg/...", import.meta.url)
|
||||
if is_script and re.search(rf"\bnew\s+URL\(\s*['\"]{esc}{sub}['\"]", content):
|
||||
|
|
@ -479,12 +487,18 @@ def _next_real_bin(words: list[str], idx: int) -> str | None:
|
|||
if first in {"npx", "pnpx", "bunx"} and idx + 1 < len(words):
|
||||
idx += 1
|
||||
continue
|
||||
if first in {"pnpm", "yarn"} and idx + 2 < len(words) and words[idx + 1] in {"exec", "dlx"}:
|
||||
if (
|
||||
first in {"pnpm", "yarn"}
|
||||
and idx + 2 < len(words)
|
||||
and words[idx + 1] in {"exec", "dlx"}
|
||||
):
|
||||
idx += 2
|
||||
continue
|
||||
|
||||
# 3. Wrapper bin (cross-env, dotenv): skip its flags and env prefixes.
|
||||
bin_token = first.removeprefix("./node_modules/.bin/").removeprefix("node_modules/.bin/")
|
||||
bin_token = first.removeprefix("./node_modules/.bin/").removeprefix(
|
||||
"node_modules/.bin/"
|
||||
)
|
||||
if bin_token in _SCRIPT_WRAPPERS and bin_token not in seen_wrappers:
|
||||
seen_wrappers.add(bin_token)
|
||||
idx += 1
|
||||
|
|
@ -510,7 +524,9 @@ def _next_real_bin(words: list[str], idx: int) -> str | None:
|
|||
return None
|
||||
|
||||
|
||||
def scripts_bin_refs(head_pkg: dict, bin_to_pkg: dict[str, str]) -> dict[str, list[str]]:
|
||||
def scripts_bin_refs(
|
||||
head_pkg: dict, bin_to_pkg: dict[str, str]
|
||||
) -> dict[str, list[str]]:
|
||||
"""Return `{package_name: ['scripts.X: cmd', ...]}` for every package
|
||||
referenced via its bin name in package.json scripts.
|
||||
|
||||
|
|
@ -566,7 +582,11 @@ def tsconfig_compiler_types_refs() -> set[str]:
|
|||
if not isinstance(t, str):
|
||||
continue
|
||||
# `vite/client` resolves to the `vite` package.
|
||||
pkg = t.split("/", 1)[0] if not t.startswith("@") else "/".join(t.split("/", 2)[:2])
|
||||
pkg = (
|
||||
t.split("/", 1)[0]
|
||||
if not t.startswith("@")
|
||||
else "/".join(t.split("/", 2)[:2])
|
||||
)
|
||||
out.add(pkg)
|
||||
return out
|
||||
|
||||
|
|
@ -704,7 +724,9 @@ _file_lines_cache: dict[str, list[str]] = {}
|
|||
def _read_file(path: str) -> list[str]:
|
||||
if path not in _file_lines_cache:
|
||||
try:
|
||||
_file_lines_cache[path] = Path(path).read_text(errors = "replace").splitlines()
|
||||
_file_lines_cache[path] = (
|
||||
Path(path).read_text(errors = "replace").splitlines()
|
||||
)
|
||||
except (OSError, UnicodeDecodeError):
|
||||
_file_lines_cache[path] = []
|
||||
return _file_lines_cache[path]
|
||||
|
|
@ -819,14 +841,18 @@ def find_types_runtime_usage(pkg: str, tsc_types: set[str]) -> list[Hit]:
|
|||
|
||||
|
||||
def main() -> int:
|
||||
p = argparse.ArgumentParser(description = __doc__, formatter_class = argparse.RawTextHelpFormatter)
|
||||
p = argparse.ArgumentParser(
|
||||
description = __doc__, formatter_class = argparse.RawTextHelpFormatter
|
||||
)
|
||||
p.add_argument(
|
||||
"--base",
|
||||
default = "origin/main",
|
||||
help = "git ref to diff against (default: origin/main). "
|
||||
"Examples: HEAD~1, main, a-tag, a-sha.",
|
||||
)
|
||||
p.add_argument("--base-pkg", help = "optional override: read base package.json from this path")
|
||||
p.add_argument(
|
||||
"--base-pkg", help = "optional override: read base package.json from this path"
|
||||
)
|
||||
p.add_argument(
|
||||
"--base-lock",
|
||||
help = "optional override: read base package-lock.json from this path. "
|
||||
|
|
@ -918,7 +944,9 @@ def main() -> int:
|
|||
print(f" - {w}")
|
||||
print()
|
||||
if missing_imports:
|
||||
print(f"Imports without a matching package.json dep ({len(missing_imports)}):")
|
||||
print(
|
||||
f"Imports without a matching package.json dep ({len(missing_imports)}):"
|
||||
)
|
||||
for file, ln, spec in missing_imports[:20]:
|
||||
print(f" - {file}:{ln} imports '{spec}'")
|
||||
print()
|
||||
|
|
@ -956,7 +984,9 @@ def main() -> int:
|
|||
return 1
|
||||
return 0
|
||||
|
||||
print(f"Checking {len(removed)} removed package(s) from studio/frontend/package.json")
|
||||
print(
|
||||
f"Checking {len(removed)} removed package(s) from studio/frontend/package.json"
|
||||
)
|
||||
print(f"Base: {args.base} Head: working tree")
|
||||
print()
|
||||
|
||||
|
|
@ -980,7 +1010,9 @@ def main() -> int:
|
|||
top = f"node_modules/{name}"
|
||||
top_path = top if top in reachable_paths else None
|
||||
nested = sorted(
|
||||
p for p in reachable_paths if p != top and p.endswith(f"/node_modules/{name}")
|
||||
p
|
||||
for p in reachable_paths
|
||||
if p != top and p.endswith(f"/node_modules/{name}")
|
||||
)
|
||||
return top_path, nested
|
||||
|
||||
|
|
@ -1026,7 +1058,9 @@ def main() -> int:
|
|||
_print_hygiene()
|
||||
|
||||
if failures:
|
||||
print(f"FAIL: {len(failures)} removed package(s) still referenced and not resolvable")
|
||||
print(
|
||||
f"FAIL: {len(failures)} removed package(s) still referenced and not resolvable"
|
||||
)
|
||||
for name, _ in failures:
|
||||
print(f" - {name}")
|
||||
return 1
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@ HIGH = "HIGH"
|
|||
class Finding:
|
||||
__slots__ = ("severity", "name", "version", "kind", "detail")
|
||||
|
||||
def __init__(self, severity: str, name: str, version: str, kind: str, detail: str) -> None:
|
||||
def __init__(
|
||||
self, severity: str, name: str, version: str, kind: str, detail: str
|
||||
) -> None:
|
||||
self.severity = severity
|
||||
self.name = name
|
||||
self.version = version
|
||||
|
|
@ -161,7 +163,9 @@ def diff_new_install_scripts(base_lock: dict, head_lock: dict) -> list[Finding]:
|
|||
if key in base:
|
||||
continue # pre-existing install-script dep; not in scope
|
||||
name = head[key]
|
||||
version = key[len(name) + 1 :] if key.startswith(name + "@") else "<unversioned>"
|
||||
version = (
|
||||
key[len(name) + 1 :] if key.startswith(name + "@") else "<unversioned>"
|
||||
)
|
||||
scripts = _fetch_registry_scripts(name, version)
|
||||
if scripts:
|
||||
detail = "; ".join(f"{h}={cmd!r}" for h, cmd in scripts.items())
|
||||
|
|
|
|||
|
|
@ -123,7 +123,9 @@ def remove_redundant_passes(text: str) -> tuple[str, bool]:
|
|||
lines = text.splitlines(keepends=True)
|
||||
changed = False
|
||||
|
||||
for node in sorted(redundant, key=lambda item: (item.lineno, item.col_offset), reverse=True):
|
||||
for node in sorted(
|
||||
redundant, key=lambda item: (item.lineno, item.col_offset), reverse=True
|
||||
):
|
||||
start = node.lineno - 1
|
||||
end = (node.end_lineno or node.lineno) - 1
|
||||
if start >= len(lines):
|
||||
|
|
@ -181,7 +183,11 @@ def remove_blank_after_short_import(text: str) -> tuple[str, bool]:
|
|||
out: list[list[ast.stmt]] = []
|
||||
for attr in ("body", "orelse", "finalbody"):
|
||||
val = getattr(node, attr, None)
|
||||
if isinstance(val, list) and val and all(isinstance(s, ast.stmt) for s in val):
|
||||
if (
|
||||
isinstance(val, list)
|
||||
and val
|
||||
and all(isinstance(s, ast.stmt) for s in val)
|
||||
):
|
||||
out.append(val)
|
||||
return out
|
||||
|
||||
|
|
@ -199,7 +205,9 @@ def remove_blank_after_short_import(text: str) -> tuple[str, bool]:
|
|||
j += 1
|
||||
if j + 1 < len(suite): # an import block followed by another statement
|
||||
last_imp, nxt = suite[j], suite[j + 1]
|
||||
gap = range((last_imp.end_lineno or last_imp.lineno) + 1, nxt.lineno)
|
||||
gap = range(
|
||||
(last_imp.end_lineno or last_imp.lineno) + 1, nxt.lineno
|
||||
)
|
||||
nums = [n for n in gap if 1 <= n <= len(lines)]
|
||||
if nums and all(lines[n - 1].strip() == "" for n in nums):
|
||||
drop.update(nums)
|
||||
|
|
@ -211,7 +219,13 @@ def remove_blank_after_short_import(text: str) -> tuple[str, bool]:
|
|||
return "".join(kept), True
|
||||
|
||||
|
||||
_STRING_TRIVIA = (tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT, tokenize.INDENT, tokenize.DEDENT)
|
||||
_STRING_TRIVIA = (
|
||||
tokenize.NL,
|
||||
tokenize.NEWLINE,
|
||||
tokenize.COMMENT,
|
||||
tokenize.INDENT,
|
||||
tokenize.DEDENT,
|
||||
)
|
||||
|
||||
|
||||
_DEF_MIN_PARAMS_FOR_MULTILINE = 3 # signatures with < this many params stay one line
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ from pathlib import Path
|
|||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
print("ERROR: PyYAML is required. Install with 'pip install pyyaml'", file = sys.stderr)
|
||||
print(
|
||||
"ERROR: PyYAML is required. Install with 'pip install pyyaml'", file = sys.stderr
|
||||
)
|
||||
sys.exit(2)
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
|
|
@ -133,7 +135,9 @@ def main() -> int:
|
|||
)
|
||||
|
||||
if findings:
|
||||
print("Workflow trigger lint failed with the following issues:", file = sys.stderr)
|
||||
print(
|
||||
"Workflow trigger lint failed with the following issues:", file = sys.stderr
|
||||
)
|
||||
for f in findings:
|
||||
print(f" - {f}", file = sys.stderr)
|
||||
return 1
|
||||
|
|
|
|||
|
|
@ -459,7 +459,9 @@ def audit_npm_lockfile(path: Path) -> list[Finding]:
|
|||
path = str(path),
|
||||
package = key,
|
||||
kind = "blocked-known-malicious",
|
||||
detail = (f"{pkg_name}@{version} is on the BLOCKED_NPM_VERSIONS list"),
|
||||
detail = (
|
||||
f"{pkg_name}@{version} is on the BLOCKED_NPM_VERSIONS list"
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -663,7 +665,9 @@ def main(argv: list[str] | None = None) -> int:
|
|||
"--cargo-lockfile",
|
||||
action = "append",
|
||||
default = None,
|
||||
help = ("Path to a Cargo.lock (repeatable). Default: studio/src-tauri/Cargo.lock."),
|
||||
help = (
|
||||
"Path to a Cargo.lock (repeatable). Default: studio/src-tauri/Cargo.lock."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--strict",
|
||||
|
|
|
|||
|
|
@ -155,7 +155,9 @@ def convert_cell_to_python(source: str, *, allow_shell: bool = True) -> str:
|
|||
cmd_lines.append(lines[i].strip())
|
||||
full_cmd = "\n".join(cmd_lines)
|
||||
|
||||
result.extend(_emit_shell_command(indent, full_cmd, allow_shell = allow_shell))
|
||||
result.extend(
|
||||
_emit_shell_command(indent, full_cmd, allow_shell = allow_shell)
|
||||
)
|
||||
|
||||
# %cd path -> os.chdir(path)
|
||||
elif stripped.startswith("%cd "):
|
||||
|
|
@ -278,7 +280,9 @@ def convert_notebook_to_script(
|
|||
source_name = source
|
||||
|
||||
output_filename = filename.replace(".ipynb", ".py")
|
||||
output_filename = output_filename.replace("(", "").replace(")", "").replace("-", "_")
|
||||
output_filename = (
|
||||
output_filename.replace("(", "").replace(")", "").replace("-", "_")
|
||||
)
|
||||
|
||||
if output_dir:
|
||||
output_path = os.path.join(output_dir, output_filename)
|
||||
|
|
@ -297,7 +301,9 @@ def convert_notebook_to_script(
|
|||
def main():
|
||||
import argparse
|
||||
|
||||
class Formatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter):
|
||||
class Formatter(
|
||||
argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter
|
||||
):
|
||||
pass
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
|
|
@ -311,8 +317,12 @@ Examples:
|
|||
python notebook_to_python.py https://github.com/unslothai/notebooks/blob/main/nb/Oute_TTS_(1B).ipynb
|
||||
""",
|
||||
)
|
||||
parser.add_argument("notebooks", nargs = "+", help = "Notebook files or URLs to convert.")
|
||||
parser.add_argument("-o", "--output", dest = "output_dir", default = ".", help = "Output directory.")
|
||||
parser.add_argument(
|
||||
"notebooks", nargs = "+", help = "Notebook files or URLs to convert."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o", "--output", dest = "output_dir", default = ".", help = "Output directory."
|
||||
)
|
||||
# Default True for backwards compat; pass --no-allow-shell for untrusted notebooks.
|
||||
parser.add_argument(
|
||||
"--allow-shell",
|
||||
|
|
|
|||
|
|
@ -87,7 +87,9 @@ COLAB_ORACLE_FILES: dict[str, str] = {
|
|||
"apt-list-gpu.txt": "colab_apt_list.gpu.txt",
|
||||
"os-info-gpu.txt": "colab_os_info.gpu.txt",
|
||||
}
|
||||
COLAB_ORACLE_BASE_URL = "https://raw.githubusercontent.com/googlecolab/backend-info/main/"
|
||||
COLAB_ORACLE_BASE_URL = (
|
||||
"https://raw.githubusercontent.com/googlecolab/backend-info/main/"
|
||||
)
|
||||
|
||||
# ----- Compat tables. PRs add rows as new releases land. ----- #
|
||||
|
||||
|
|
@ -187,7 +189,9 @@ def install_cells(nb: dict[str, Any]) -> list[tuple[int, str]]:
|
|||
if first and first[0].strip().startswith("%%capture"):
|
||||
out.append((i, src))
|
||||
continue
|
||||
if re.search(r"^[ \t]*!\s*(uv\s+)?pip\s+(install|uninstall)\b", src, re.MULTILINE):
|
||||
if re.search(
|
||||
r"^[ \t]*!\s*(uv\s+)?pip\s+(install|uninstall)\b", src, re.MULTILINE
|
||||
):
|
||||
out.append((i, src))
|
||||
return out
|
||||
|
||||
|
|
@ -318,7 +322,9 @@ def parse_pip_line(line: str, line_no: int = 0) -> PipInvocation | None:
|
|||
if t in ("install", "uninstall"):
|
||||
continue
|
||||
packages.append(t)
|
||||
return PipInvocation(tool = tool, flags = flags, packages = packages, raw = line, line_no = line_no)
|
||||
return PipInvocation(
|
||||
tool = tool, flags = flags, packages = packages, raw = line, line_no = line_no
|
||||
)
|
||||
|
||||
|
||||
def _glue_line_continuations(text: str) -> list[tuple[int, str]]:
|
||||
|
|
@ -403,7 +409,9 @@ def pypi_metadata(name: str, version: str) -> dict[str, Any] | None:
|
|||
return data
|
||||
|
||||
|
||||
def transitive_constraint(name: str, version: str, target: str) -> tuple[str | None, list[str]]:
|
||||
def transitive_constraint(
|
||||
name: str, version: str, target: str
|
||||
) -> tuple[str | None, list[str]]:
|
||||
"""Return (raw_specifier_string_or_None, list_of_(op,version) tuples)
|
||||
for the constraint that `name==version` places on `target`.
|
||||
"""
|
||||
|
|
@ -477,7 +485,10 @@ def resolved_set(install_cell: str, colab: dict[str, str]) -> dict[str, str]:
|
|||
out[sp.name] = ver
|
||||
pinned.add(sp.name)
|
||||
elif op == "<=" and sp.name not in pinned:
|
||||
if sp.name not in upper_bounds or cmp_versions(ver, upper_bounds[sp.name]) < 0:
|
||||
if (
|
||||
sp.name not in upper_bounds
|
||||
or cmp_versions(ver, upper_bounds[sp.name]) < 0
|
||||
):
|
||||
upper_bounds[sp.name] = ver
|
||||
# Apply upper bounds where Colab's preinstall violates them.
|
||||
for name, ub in upper_bounds.items():
|
||||
|
|
@ -492,7 +503,9 @@ def resolved_set(install_cell: str, colab: dict[str, str]) -> dict[str, str]:
|
|||
# ----- Rules ----- #
|
||||
|
||||
|
||||
def rule_inst_001_git_plus(install_cell: str, file: str, cell_idx: int) -> list[Finding]:
|
||||
def rule_inst_001_git_plus(
|
||||
install_cell: str, file: str, cell_idx: int
|
||||
) -> list[Finding]:
|
||||
findings: list[Finding] = []
|
||||
for inv in iter_pip_invocations(install_cell):
|
||||
if any("git+" in p for p in inv.packages) or "git+" in inv.raw:
|
||||
|
|
@ -680,7 +693,9 @@ def rule_inst_005_transformers_tokenizers(
|
|||
_RE_DOUBLE_BANG = re.compile(r"^[ \t]*!{2,}\s*pip\b", re.MULTILINE)
|
||||
|
||||
|
||||
def rule_inst_006_double_bang(install_cell: str, file: str, cell_idx: int) -> list[Finding]:
|
||||
def rule_inst_006_double_bang(
|
||||
install_cell: str, file: str, cell_idx: int
|
||||
) -> list[Finding]:
|
||||
findings: list[Finding] = []
|
||||
for m in _RE_DOUBLE_BANG.finditer(install_cell):
|
||||
line_no = install_cell.count("\n", 0, m.start()) + 1
|
||||
|
|
@ -771,7 +786,9 @@ POLICY_CLAUSES_DEFAULT = [
|
|||
]
|
||||
|
||||
|
||||
def extract_policy_clauses(update_script: pathlib.Path) -> list[tuple[str, re.Pattern[str], Any]]:
|
||||
def extract_policy_clauses(
|
||||
update_script: pathlib.Path,
|
||||
) -> list[tuple[str, re.Pattern[str], Any]]:
|
||||
"""Best-effort scan of update_all_notebooks.py for canonical phrases;
|
||||
falls back to POLICY_CLAUSES_DEFAULT (which we use directly today). The
|
||||
permissive regexes avoid false positives on template rewords."""
|
||||
|
|
@ -831,7 +848,11 @@ def cmd_drift(args: argparse.Namespace) -> int:
|
|||
print(f"FAIL: {update_script} not found", file = sys.stderr)
|
||||
return 2
|
||||
# Stash any pre-existing dirty state, run the updater, diff, restore.
|
||||
head = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd = nbdir).decode().strip()
|
||||
head = (
|
||||
subprocess.check_output(["git", "rev-parse", "HEAD"], cwd = nbdir)
|
||||
.decode()
|
||||
.strip()
|
||||
)
|
||||
subprocess.run(
|
||||
["git", "-C", str(nbdir), "stash", "--include-untracked"],
|
||||
check = False,
|
||||
|
|
@ -932,7 +953,9 @@ def cmd_convert(args: argparse.Namespace) -> int:
|
|||
hint = proc.stderr[-200:].strip(),
|
||||
)
|
||||
)
|
||||
print(f"converted {len(notebooks) - len(failed)}/{len(notebooks)} notebooks to {out}")
|
||||
print(
|
||||
f"converted {len(notebooks) - len(failed)}/{len(notebooks)} notebooks to {out}"
|
||||
)
|
||||
_emit(failed)
|
||||
return 0 if not failed else 1
|
||||
|
||||
|
|
@ -942,7 +965,11 @@ def cmd_convert(args: argparse.Namespace) -> int:
|
|||
|
||||
def cmd_lint(args: argparse.Namespace) -> int:
|
||||
nbdir = pathlib.Path(args.notebooks_dir).resolve()
|
||||
colab_path = pathlib.Path(args.colab_pin).resolve() if args.colab_pin else COLAB_FALLBACK_FILE
|
||||
colab_path = (
|
||||
pathlib.Path(args.colab_pin).resolve()
|
||||
if args.colab_pin
|
||||
else COLAB_FALLBACK_FILE
|
||||
)
|
||||
colab = parse_pip_freeze(colab_path)
|
||||
if not colab:
|
||||
print(
|
||||
|
|
@ -982,9 +1009,13 @@ def cmd_lint(args: argparse.Namespace) -> int:
|
|||
first_cell = cells[0][0] if cells else None
|
||||
findings += rule_inst_003_peft_torchao(merged, oracle, rel, first_cell)
|
||||
findings += rule_inst_004_torchcodec_torch(merged, oracle, rel, first_cell)
|
||||
findings += rule_inst_005_transformers_tokenizers(merged, oracle, rel, first_cell)
|
||||
findings += rule_inst_005_transformers_tokenizers(
|
||||
merged, oracle, rel, first_cell
|
||||
)
|
||||
if not args.no_pypi:
|
||||
findings += rule_inst_002_no_deps_transitive(merged, oracle, rel, first_cell)
|
||||
findings += rule_inst_002_no_deps_transitive(
|
||||
merged, oracle, rel, first_cell
|
||||
)
|
||||
findings += scan_user_cells(nb, rel)
|
||||
_emit(findings)
|
||||
return 0 if not any(f.severity == "error" for f in findings) else 1
|
||||
|
|
@ -1159,7 +1190,9 @@ def cmd_colab_diff(args: argparse.Namespace) -> int:
|
|||
print(f"::warning::colab-diff: could not fetch {url}: {e}")
|
||||
continue
|
||||
if not snap_path.exists():
|
||||
print(f"::warning::colab-diff: no committed snapshot at {snap_path}; skipping")
|
||||
print(
|
||||
f"::warning::colab-diff: no committed snapshot at {snap_path}; skipping"
|
||||
)
|
||||
continue
|
||||
snapshot_text = snap_path.read_text(encoding = "utf-8", errors = "replace")
|
||||
parser = _COLAB_ORACLE_PARSERS[upstream_name]
|
||||
|
|
|
|||
|
|
@ -768,7 +768,8 @@ def download_tarball(
|
|||
written += len(chunk)
|
||||
if written > max_bytes:
|
||||
return dest, (
|
||||
f"download exceeded cap {max_bytes} bytes " f"after {written} bytes"
|
||||
f"download exceeded cap {max_bytes} bytes "
|
||||
f"after {written} bytes"
|
||||
)
|
||||
h.update(chunk)
|
||||
out.write(chunk)
|
||||
|
|
@ -865,7 +866,11 @@ def safe_extract(
|
|||
# each gets its own cap (both are bounded).
|
||||
header = src.read(16)
|
||||
is_binary = _looks_binary(name, header)
|
||||
file_cap = HARD_MAX_BINARY_FILE_BYTES if is_binary else HARD_MAX_TEXT_FILE_BYTES
|
||||
file_cap = (
|
||||
HARD_MAX_BINARY_FILE_BYTES
|
||||
if is_binary
|
||||
else HARD_MAX_TEXT_FILE_BYTES
|
||||
)
|
||||
if declared > file_cap:
|
||||
return (
|
||||
f"member {name!r} declared size {declared} > "
|
||||
|
|
@ -988,7 +993,9 @@ def scan_package_json(pkg: PackageEntry, rel: str, text: str) -> list[Finding]:
|
|||
if isinstance(opt, dict):
|
||||
for k, v in opt.items():
|
||||
if isinstance(v, str) and (
|
||||
v.startswith("github:") or v.startswith("git+") or v.startswith("git://")
|
||||
v.startswith("github:")
|
||||
or v.startswith("git+")
|
||||
or v.startswith("git://")
|
||||
):
|
||||
findings.append(
|
||||
Finding(
|
||||
|
|
@ -1104,7 +1111,9 @@ def scan_text_blob(pkg: PackageEntry, rel: str, text: str) -> list[Finding]:
|
|||
filename = rel,
|
||||
pattern = "js-fetch-eval",
|
||||
evidence = _evidence(text, _JS_FETCH_EVAL),
|
||||
detail = ("Function/eval against base64-decoded payload (obfuscated dropper shape)"),
|
||||
detail = (
|
||||
"Function/eval against base64-decoded payload (obfuscated dropper shape)"
|
||||
),
|
||||
)
|
||||
)
|
||||
if _JS_ENV_TOKEN.search(text):
|
||||
|
|
@ -1345,7 +1354,8 @@ def main(argv: list[str] | None = None) -> int:
|
|||
if hard_errors or blocking:
|
||||
if blocking:
|
||||
print(
|
||||
f"\n[scan-npm] FAIL: {len(blocking)} finding(s) " f"at or above {threshold}",
|
||||
f"\n[scan-npm] FAIL: {len(blocking)} finding(s) "
|
||||
f"at or above {threshold}",
|
||||
file = sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
|
|
|||
|
|
@ -293,7 +293,9 @@ RE_CRYPTO_THEFT = re.compile(
|
|||
RE_PTH_IMPORT = re.compile(r"^\s*import\s+", re.MULTILINE)
|
||||
|
||||
# openssl CLI invocations via subprocess (encrypted exfiltration)
|
||||
RE_OPENSSL_CLI = re.compile(r"\bopenssl\s+(enc|rand|rsautl|pkeyutl|genrsa|dgst|s_client)\b")
|
||||
RE_OPENSSL_CLI = re.compile(
|
||||
r"\bopenssl\s+(enc|rand|rsautl|pkeyutl|genrsa|dgst|s_client)\b"
|
||||
)
|
||||
|
||||
# Write to /tmp then execute (staged dropper)
|
||||
RE_TEMP_EXEC = re.compile(
|
||||
|
|
@ -1224,13 +1226,15 @@ def iter_archive_files(archive_path: str):
|
|||
# historically dereferenced them on extract.
|
||||
if member.issym() or member.islnk():
|
||||
print(
|
||||
f" [WARN] {path.name}: refused link member " f"{member.name!r}",
|
||||
f" [WARN] {path.name}: refused link member "
|
||||
f"{member.name!r}",
|
||||
file = sys.stderr,
|
||||
)
|
||||
continue
|
||||
if member.isdev() or member.isfifo():
|
||||
print(
|
||||
f" [WARN] {path.name}: refused special member " f"{member.name!r}",
|
||||
f" [WARN] {path.name}: refused special member "
|
||||
f"{member.name!r}",
|
||||
file = sys.stderr,
|
||||
)
|
||||
continue
|
||||
|
|
@ -1432,7 +1436,9 @@ def download_packages(
|
|||
env = env,
|
||||
)
|
||||
if proc.returncode != 0:
|
||||
msg = f"pip download (with deps) failed: " f"{proc.stderr.strip()[:500]}"
|
||||
msg = (
|
||||
f"pip download (with deps) failed: " f"{proc.stderr.strip()[:500]}"
|
||||
)
|
||||
print(f" [ERROR] {msg}", file = sys.stderr)
|
||||
download_errors.append(msg)
|
||||
except subprocess.TimeoutExpired:
|
||||
|
|
@ -1473,7 +1479,10 @@ def download_packages(
|
|||
env = env,
|
||||
)
|
||||
if proc.returncode != 0:
|
||||
msg = f"pip download failed for {spec}: " f"{proc.stderr.strip()[:500]}"
|
||||
msg = (
|
||||
f"pip download failed for {spec}: "
|
||||
f"{proc.stderr.strip()[:500]}"
|
||||
)
|
||||
print(f" [ERROR] {msg}", file = sys.stderr)
|
||||
download_errors.append(msg)
|
||||
continue
|
||||
|
|
@ -1499,7 +1508,9 @@ def _extract_pkg_name(spec: str) -> str:
|
|||
"""Extract the package name from a pip spec string."""
|
||||
m = _RE_NAME.match(spec)
|
||||
return (
|
||||
m.group(1) if m else spec.split("==")[0].split(">=")[0].split("<=")[0].split("[")[0].strip()
|
||||
m.group(1)
|
||||
if m
|
||||
else spec.split("==")[0].split(">=")[0].split("<=")[0].split("[")[0].strip()
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -1840,7 +1851,9 @@ def _run_fix(critical_pkgs: set[str], entries: list[dict], max_search: int) -> N
|
|||
if git_entries:
|
||||
for e in git_entries:
|
||||
src = e["source_file"] or "CLI"
|
||||
print(f" [SKIP] {pkg_name} is a git URL dep in {src}, cannot auto-update")
|
||||
print(
|
||||
f" [SKIP] {pkg_name} is a git URL dep in {src}, cannot auto-update"
|
||||
)
|
||||
changes_summary.append(f" SKIP {pkg_name} (git URL)")
|
||||
continue
|
||||
|
||||
|
|
@ -1862,7 +1875,9 @@ def _run_fix(critical_pkgs: set[str], entries: list[dict], max_search: int) -> N
|
|||
shutil.rmtree(dl_dir, ignore_errors = True)
|
||||
|
||||
if not current_ver:
|
||||
print(f" [WARN] Cannot determine current version of {pkg_name}, skipping fix")
|
||||
print(
|
||||
f" [WARN] Cannot determine current version of {pkg_name}, skipping fix"
|
||||
)
|
||||
changes_summary.append(f" SKIP {pkg_name} (version unknown)")
|
||||
continue
|
||||
|
||||
|
|
@ -1879,7 +1894,9 @@ def _run_fix(critical_pkgs: set[str], entries: list[dict], max_search: int) -> N
|
|||
continue
|
||||
|
||||
print(f" [OK] {pkg_name}: {current_ver} -> {safe_ver}")
|
||||
changes_summary.append(f" FIX {pkg_name}=={current_ver} -> {pkg_name}=={safe_ver}")
|
||||
changes_summary.append(
|
||||
f" FIX {pkg_name}=={current_ver} -> {pkg_name}=={safe_ver}"
|
||||
)
|
||||
|
||||
# Update all occurrences in requirements files
|
||||
file_updates: dict[str, dict[int, str]] = {}
|
||||
|
|
@ -1926,7 +1943,9 @@ def _find_requirements_files(root: str) -> list[str]:
|
|||
dirnames[:] = [
|
||||
d
|
||||
for d in dirnames
|
||||
if not d.startswith(".") and d not in skip_dirs and not d.endswith(".egg-info")
|
||||
if not d.startswith(".")
|
||||
and d not in skip_dirs
|
||||
and not d.endswith(".egg-info")
|
||||
]
|
||||
dirname = os.path.basename(dirpath)
|
||||
for fname in sorted(filenames):
|
||||
|
|
@ -1998,7 +2017,9 @@ def main() -> int:
|
|||
print(f" {f}")
|
||||
req_files.extend(found)
|
||||
else:
|
||||
print(f" [WARN] No requirements files found in {scan_dir}/", file = sys.stderr)
|
||||
print(
|
||||
f" [WARN] No requirements files found in {scan_dir}/", file = sys.stderr
|
||||
)
|
||||
|
||||
# Build unified entry list: list of dicts with source tracking
|
||||
entries: list[dict] = []
|
||||
|
|
|
|||
|
|
@ -42,7 +42,9 @@ def _atomic_write_text(
|
|||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
BUILD_INFO_PATH = REPO_ROOT / "studio" / "backend" / "utils" / "_studio_release_build.py"
|
||||
BUILD_INFO_PATH = (
|
||||
REPO_ROOT / "studio" / "backend" / "utils" / "_studio_release_build.py"
|
||||
)
|
||||
BUILD_INFO_SUFFIX = "studio/backend/utils/_studio_release_build.py"
|
||||
VERSION_RE = re.compile(r"^v\d+\.\d+\.\d+(?:-[0-9A-Za-z.][0-9A-Za-z.-]*)?$")
|
||||
GIT_DESCRIBE_SUFFIX_RE = re.compile(r"-\d+-g[0-9A-Fa-f]+(?:-dirty)?$")
|
||||
|
|
|
|||
|
|
@ -74,7 +74,9 @@ def desired_key(name: str, versions: list[str]) -> str:
|
|||
return f"{name}@{' || '.join(versions)}"
|
||||
|
||||
|
||||
def compute_renames(policy: dict, lock_versions: dict[str, list[str]]) -> dict[str, str]:
|
||||
def compute_renames(
|
||||
policy: dict, lock_versions: dict[str, list[str]]
|
||||
) -> dict[str, str]:
|
||||
renames: dict[str, str] = {}
|
||||
for key in policy:
|
||||
name, rng = split_spec(key)
|
||||
|
|
@ -93,7 +95,9 @@ def main(argv: list[str] | None = None) -> int:
|
|||
ap = argparse.ArgumentParser(description = __doc__)
|
||||
mode = ap.add_mutually_exclusive_group(required = True)
|
||||
mode.add_argument("--check", action = "store_true", help = "exit 1 if pins are stale")
|
||||
mode.add_argument("--fix", action = "store_true", help = "rewrite package.json in place")
|
||||
mode.add_argument(
|
||||
"--fix", action = "store_true", help = "rewrite package.json in place"
|
||||
)
|
||||
ap.add_argument(
|
||||
"--dir",
|
||||
type = Path,
|
||||
|
|
@ -105,20 +109,26 @@ def main(argv: list[str] | None = None) -> int:
|
|||
pkg_path = args.dir / "package.json"
|
||||
lock_path = args.dir / "package-lock.json"
|
||||
if not pkg_path.exists() or not lock_path.exists():
|
||||
print(f"sync-allow-scripts: nothing to do ({args.dir} has no package.json + lockfile)")
|
||||
print(
|
||||
f"sync-allow-scripts: nothing to do ({args.dir} has no package.json + lockfile)"
|
||||
)
|
||||
return 0
|
||||
|
||||
pkg = json.loads(pkg_path.read_text(encoding = "utf-8"))
|
||||
policy = pkg.get("allowScripts")
|
||||
if not isinstance(policy, dict) or not policy:
|
||||
print("sync-allow-scripts: no allowScripts policy in package.json, nothing to do")
|
||||
print(
|
||||
"sync-allow-scripts: no allowScripts policy in package.json, nothing to do"
|
||||
)
|
||||
return 0
|
||||
|
||||
lock = json.loads(lock_path.read_text(encoding = "utf-8"))
|
||||
renames = compute_renames(policy, script_versions_from_lock(lock))
|
||||
|
||||
if not renames:
|
||||
print(f"sync-allow-scripts: {len(policy)} allowScripts entries in sync with the lockfile")
|
||||
print(
|
||||
f"sync-allow-scripts: {len(policy)} allowScripts entries in sync with the lockfile"
|
||||
)
|
||||
return 0
|
||||
|
||||
for old, new in renames.items():
|
||||
|
|
@ -132,7 +142,9 @@ def main(argv: list[str] | None = None) -> int:
|
|||
return 1
|
||||
|
||||
pkg["allowScripts"] = {renames.get(k, k): v for k, v in policy.items()}
|
||||
pkg_path.write_text(json.dumps(pkg, indent = 2, ensure_ascii = False) + "\n", encoding = "utf-8")
|
||||
pkg_path.write_text(
|
||||
json.dumps(pkg, indent = 2, ensure_ascii = False) + "\n", encoding = "utf-8"
|
||||
)
|
||||
print(
|
||||
f"sync-allow-scripts: re-pinned {len(renames)} entr{'y' if len(renames) == 1 else 'ies'} in {pkg_path}"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -131,7 +131,8 @@ def _walk_yaml_diff(
|
|||
"""Print a path-keyed summary of the first structural / scalar diff."""
|
||||
if type(b) is not type(a):
|
||||
print(
|
||||
f" type-diff at {prefix or '/'}: " f"{type(b).__name__} -> {type(a).__name__}",
|
||||
f" type-diff at {prefix or '/'}: "
|
||||
f"{type(b).__name__} -> {type(a).__name__}",
|
||||
)
|
||||
return
|
||||
if isinstance(b, dict):
|
||||
|
|
|
|||
|
|
@ -161,7 +161,9 @@ class _Builder(ast.NodeVisitor):
|
|||
|
||||
def _visit_stmt(self, node: ast.AST, scope: Scope) -> None:
|
||||
if isinstance(node, (ast.Import, ast.ImportFrom)):
|
||||
star = isinstance(node, ast.ImportFrom) and any(a.name == "*" for a in node.names)
|
||||
star = isinstance(node, ast.ImportFrom) and any(
|
||||
a.name == "*" for a in node.names
|
||||
)
|
||||
if star:
|
||||
scope.star_import = True
|
||||
for alias in node.names:
|
||||
|
|
@ -349,7 +351,9 @@ class _Builder(ast.NodeVisitor):
|
|||
self._bind_args(node.args, child)
|
||||
self._visit_expr(node.body, child)
|
||||
return
|
||||
if isinstance(node, (ast.ListComp, ast.SetComp, ast.GeneratorExp, ast.DictComp)):
|
||||
if isinstance(
|
||||
node, (ast.ListComp, ast.SetComp, ast.GeneratorExp, ast.DictComp)
|
||||
):
|
||||
child = Scope("comp", f"{scope.qualname}.<comp>", scope)
|
||||
for i, gen in enumerate(node.generators):
|
||||
# first iterable evaluates in the enclosing scope
|
||||
|
|
@ -606,7 +610,9 @@ def compare(before_src: str, after_src: str, path: str) -> list[tuple[str, str]]
|
|||
for scope, names in b["ambiguous"].items():
|
||||
new = names - a["ambiguous"].get(scope, set())
|
||||
for n in sorted(new):
|
||||
findings.append(("WARN", f"{path}: AMBIGUOUS-BIND '{n}' import+non-import in {scope}"))
|
||||
findings.append(
|
||||
("WARN", f"{path}: AMBIGUOUS-BIND '{n}' import+non-import in {scope}")
|
||||
)
|
||||
|
||||
# 6. TARGET-MISSING (informational): a scope stopped resolving to an import
|
||||
# target. Real bugs are covered above; remaining cases are relocated code.
|
||||
|
|
@ -618,7 +624,9 @@ def compare(before_src: str, after_src: str, path: str) -> list[tuple[str, str]]
|
|||
if t in added_module_targets
|
||||
else " [target not re-added here -> likely relocated/deleted]"
|
||||
)
|
||||
findings.append(("INFO", f"{path}: TARGET-MISSING {t} in scope {scope}{relocated}"))
|
||||
findings.append(
|
||||
("INFO", f"{path}: TARGET-MISSING {t} in scope {scope}{relocated}")
|
||||
)
|
||||
return findings
|
||||
|
||||
|
||||
|
|
@ -763,7 +771,9 @@ def audit_files(paths: list[str]) -> int:
|
|||
ok = n_err == 0 and n_fp == 0
|
||||
print(
|
||||
"\nAUDIT:",
|
||||
"ROBUST (no crashes, no false positives vs pyflakes)" if ok else "NEEDS WORK (see above)",
|
||||
"ROBUST (no crashes, no false positives vs pyflakes)"
|
||||
if ok
|
||||
else "NEEDS WORK (see above)",
|
||||
)
|
||||
return 0 if ok else 1
|
||||
|
||||
|
|
@ -799,12 +809,18 @@ def main() -> int:
|
|||
blockers = [f for f in findings if f[0] == "BLOCKER"]
|
||||
warns = [f for f in findings if f[0] == "WARN"]
|
||||
infos = [f for f in findings if f[0] == "INFO"]
|
||||
status = "CLEAN" if not blockers and not warns else ("BLOCKERS" if blockers else "WARNINGS")
|
||||
status = (
|
||||
"CLEAN"
|
||||
if not blockers and not warns
|
||||
else ("BLOCKERS" if blockers else "WARNINGS")
|
||||
)
|
||||
print(f"\n=== {path}: {status} ===")
|
||||
for sev, m in blockers + warns + infos:
|
||||
print(f" [{sev}] {m}")
|
||||
any_blocker = any_blocker or bool(blockers)
|
||||
print("\nOVERALL:", "FAIL (blockers found)" if any_blocker else "PASS (no blockers)")
|
||||
print(
|
||||
"\nOVERALL:", "FAIL (blockers found)" if any_blocker else "PASS (no blockers)"
|
||||
)
|
||||
return 1 if any_blocker else 0
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -108,7 +108,9 @@ def create_refresh_token(subject: str, *, desktop: bool = False) -> str:
|
|||
return token
|
||||
|
||||
|
||||
def refresh_access_token(refresh_token: str) -> Tuple[Optional[str], Optional[str], bool]:
|
||||
def refresh_access_token(
|
||||
refresh_token: str,
|
||||
) -> Tuple[Optional[str], Optional[str], bool]:
|
||||
"""
|
||||
Validate a refresh token and issue a new access token.
|
||||
|
||||
|
|
@ -135,7 +137,9 @@ def reload_secret() -> None:
|
|||
load_jwt_secret()
|
||||
|
||||
|
||||
async def get_current_subject(credentials: HTTPAuthorizationCredentials = Depends(security)) -> str:
|
||||
async def get_current_subject(
|
||||
credentials: HTTPAuthorizationCredentials = Depends(security),
|
||||
) -> str:
|
||||
"""Validate JWT and require the password-change flow to be completed."""
|
||||
return await _get_current_subject(
|
||||
credentials,
|
||||
|
|
|
|||
|
|
@ -138,9 +138,13 @@ def get_connection() -> sqlite3.Connection:
|
|||
);
|
||||
"""
|
||||
)
|
||||
api_key_columns = {row["name"] for row in conn.execute("PRAGMA table_info(api_keys)")}
|
||||
api_key_columns = {
|
||||
row["name"] for row in conn.execute("PRAGMA table_info(api_keys)")
|
||||
}
|
||||
if "is_internal" not in api_key_columns:
|
||||
conn.execute("ALTER TABLE api_keys ADD COLUMN is_internal INTEGER NOT NULL DEFAULT 0")
|
||||
conn.execute(
|
||||
"ALTER TABLE api_keys ADD COLUMN is_internal INTEGER NOT NULL DEFAULT 0"
|
||||
)
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS app_secrets (
|
||||
|
|
@ -154,9 +158,13 @@ def get_connection() -> sqlite3.Connection:
|
|||
conn.execute(
|
||||
"ALTER TABLE auth_user ADD COLUMN must_change_password INTEGER NOT NULL DEFAULT 0"
|
||||
)
|
||||
refresh_columns = {row["name"] for row in conn.execute("PRAGMA table_info(refresh_tokens)")}
|
||||
refresh_columns = {
|
||||
row["name"] for row in conn.execute("PRAGMA table_info(refresh_tokens)")
|
||||
}
|
||||
if "is_desktop" not in refresh_columns:
|
||||
conn.execute("ALTER TABLE refresh_tokens ADD COLUMN is_desktop INTEGER NOT NULL DEFAULT 0")
|
||||
conn.execute(
|
||||
"ALTER TABLE refresh_tokens ADD COLUMN is_desktop INTEGER NOT NULL DEFAULT 0"
|
||||
)
|
||||
conn.commit()
|
||||
return conn
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,9 @@ def _asset_name() -> Optional[Tuple[str, bool]]:
|
|||
def _cache_path() -> Optional[Path]:
|
||||
"""studio_bin_root()/cloudflared(.exe), or None if the studio home is unresolvable."""
|
||||
try:
|
||||
from utils.paths.storage_roots import studio_bin_root # lazy: backend-only import
|
||||
from utils.paths.storage_roots import (
|
||||
studio_bin_root,
|
||||
) # lazy: backend-only import
|
||||
except Exception:
|
||||
return None
|
||||
name = "cloudflared.exe" if sys.platform == "win32" else "cloudflared"
|
||||
|
|
@ -262,7 +264,9 @@ class CloudflareTunnel:
|
|||
if self.url is None:
|
||||
self.error = "cloudflared exited before emitting a tunnel URL"
|
||||
elif not self.ready:
|
||||
self.error = "cloudflared exited before the tunnel connection registered"
|
||||
self.error = (
|
||||
"cloudflared exited before the tunnel connection registered"
|
||||
)
|
||||
self._url_event.set()
|
||||
self._ready_event.set()
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,12 @@ def get_colab_url(port: int = 8888) -> str:
|
|||
try:
|
||||
url = eval_js(f"google.colab.kernel.proxyPort({port})", timeout_sec = 10)
|
||||
# Valid proxy URL is https:// and embeds the port.
|
||||
if url and isinstance(url, str) and url.startswith("https://") and str(port) in url:
|
||||
if (
|
||||
url
|
||||
and isinstance(url, str)
|
||||
and url.startswith("https://")
|
||||
and str(port) in url
|
||||
):
|
||||
return url.rstrip("/")
|
||||
except Exception as e:
|
||||
logger.info(f"Note: Could not get Colab URL (attempt {attempt + 1}/3: {e})")
|
||||
|
|
@ -107,7 +112,9 @@ def _is_studio_healthy(port: int, timeout: float = 2.0) -> bool:
|
|||
"""Return True if a Studio backend is already answering health checks on *port*."""
|
||||
import urllib.request
|
||||
try:
|
||||
with urllib.request.urlopen(f"http://localhost:{port}/api/health", timeout = timeout):
|
||||
with urllib.request.urlopen(
|
||||
f"http://localhost:{port}/api/health", timeout = timeout
|
||||
):
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
|
@ -179,7 +186,9 @@ def start(port: int = 8888):
|
|||
# Fast path: Studio already running (cell re-run). Re-launching would collide on
|
||||
# the port, so just re-show the link and iframe.
|
||||
if _is_studio_healthy(port):
|
||||
logger.info(f" Studio is already running on port {port} — reusing existing server.")
|
||||
logger.info(
|
||||
f" Studio is already running on port {port} — reusing existing server."
|
||||
)
|
||||
_show_and_embed(port)
|
||||
try:
|
||||
for _ in range(10000):
|
||||
|
|
@ -202,7 +211,9 @@ def start(port: int = 8888):
|
|||
|
||||
logger.info(" Starting server...")
|
||||
try:
|
||||
app = run_server(host = "0.0.0.0", port = port, frontend_path = frontend_path, silent = True)
|
||||
app = run_server(
|
||||
host = "0.0.0.0", port = port, frontend_path = frontend_path, silent = True
|
||||
)
|
||||
except SystemExit as exc:
|
||||
logger.error(f"❌ Unsloth Studio failed to start: {exc}")
|
||||
return
|
||||
|
|
@ -223,7 +234,9 @@ def start(port: int = 8888):
|
|||
server_ready = False
|
||||
for _ in range(40):
|
||||
try:
|
||||
with urllib.request.urlopen(f"http://localhost:{actual_port}/api/health", timeout = 1):
|
||||
with urllib.request.urlopen(
|
||||
f"http://localhost:{actual_port}/api/health", timeout = 1
|
||||
):
|
||||
server_ready = True
|
||||
break
|
||||
except Exception:
|
||||
|
|
|
|||
|
|
@ -36,7 +36,9 @@ def _resolve_recipe_artifact_path(artifact_path: str) -> Path:
|
|||
if not resolved.exists():
|
||||
raise RecipeDatasetPublishError("Execution artifacts are no longer available.")
|
||||
if not resolved.is_dir():
|
||||
raise RecipeDatasetPublishError("Execution artifact path is not a dataset folder.")
|
||||
raise RecipeDatasetPublishError(
|
||||
"Execution artifact path is not a dataset folder."
|
||||
)
|
||||
|
||||
return resolved
|
||||
|
||||
|
|
|
|||
|
|
@ -108,7 +108,9 @@ class Subscription:
|
|||
event_id = self._next_id
|
||||
body = json.dumps(event, separators = (",", ":"), ensure_ascii = False)
|
||||
event_type = event.get("type") or "message"
|
||||
return (f"id: {event_id}\n" f"event: {event_type}\n" f"data: {body}\n\n").encode("utf-8")
|
||||
return (
|
||||
f"id: {event_id}\n" f"event: {event_type}\n" f"data: {body}\n\n"
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
class JobManager:
|
||||
|
|
@ -155,7 +157,9 @@ class JobManager:
|
|||
job_id = uuid.uuid4().hex
|
||||
self._job = Job(job_id = job_id, status = "pending", started_at = time.time())
|
||||
self._job.progress_columns_total = llm_column_count
|
||||
self._job.source_progress_estimated_total = _github_source_estimated_total(recipe)
|
||||
self._job.source_progress_estimated_total = _github_source_estimated_total(
|
||||
recipe
|
||||
)
|
||||
self._job.internal_api_key_id = internal_api_key_id
|
||||
self._events.clear()
|
||||
self._seq = 0
|
||||
|
|
@ -182,7 +186,9 @@ class JobManager:
|
|||
self._pump_thread = threading.Thread(target = self._pump_loop, daemon = True)
|
||||
self._pump_thread.start()
|
||||
|
||||
self._emit({"type": EVENT_JOB_ENQUEUED, "ts": time.time(), "job_id": job_id})
|
||||
self._emit(
|
||||
{"type": EVENT_JOB_ENQUEUED, "ts": time.time(), "job_id": job_id}
|
||||
)
|
||||
return job_id
|
||||
|
||||
def cancel(self, job_id: str) -> bool:
|
||||
|
|
@ -193,7 +199,9 @@ class JobManager:
|
|||
if self._proc is None or not self._proc.is_alive():
|
||||
return True
|
||||
self._job.status = "cancelling"
|
||||
self._emit({"type": EVENT_JOB_CANCELLING, "ts": time.time(), "job_id": job_id})
|
||||
self._emit(
|
||||
{"type": EVENT_JOB_CANCELLING, "ts": time.time(), "job_id": job_id}
|
||||
)
|
||||
try:
|
||||
self._proc.terminate()
|
||||
except (AttributeError, OSError):
|
||||
|
|
@ -310,12 +318,16 @@ class JobManager:
|
|||
if not parquet_dir.exists():
|
||||
return {"error": f"dataset path missing: {parquet_dir}"}
|
||||
|
||||
return self._load_dataset_page(parquet_dir = parquet_dir, limit = limit, offset = offset)
|
||||
return self._load_dataset_page(
|
||||
parquet_dir = parquet_dir, limit = limit, offset = offset
|
||||
)
|
||||
except Exception as exc:
|
||||
return {"error": f"dataset load failed: {exc}"}
|
||||
|
||||
@staticmethod
|
||||
def _load_dataset_page(*, parquet_dir: Path, limit: int, offset: int) -> dict[str, Any]:
|
||||
def _load_dataset_page(
|
||||
*, parquet_dir: Path, limit: int, offset: int
|
||||
) -> dict[str, Any]:
|
||||
dataset_page = JobManager._load_dataset_page_with_duckdb(
|
||||
parquet_dir = parquet_dir,
|
||||
limit = limit,
|
||||
|
|
@ -478,7 +490,9 @@ class JobManager:
|
|||
self._job.error = self._job.error or "process exited"
|
||||
self._job.finished_at = time.time()
|
||||
event_type = (
|
||||
EVENT_JOB_CANCELLED if self._job.status == "cancelled" else EVENT_JOB_ERROR
|
||||
EVENT_JOB_CANCELLED
|
||||
if self._job.status == "cancelled"
|
||||
else EVENT_JOB_ERROR
|
||||
)
|
||||
self._emit(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -119,7 +119,8 @@ def parse_log_message(msg: str) -> ParsedUpdate | None:
|
|||
page_items = page_items,
|
||||
rate_remaining = int(m.group("remaining")),
|
||||
message = (
|
||||
f"Scraping GitHub source: {repo} " f"{resource} page {page} (+{page_items})"
|
||||
f"Scraping GitHub source: {repo} "
|
||||
f"{resource} page {page} (+{page_items})"
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -133,7 +134,9 @@ def parse_log_message(msg: str) -> ParsedUpdate | None:
|
|||
source = "github",
|
||||
status = "rate_limited",
|
||||
retry_after_sec = seconds,
|
||||
message = ("Waiting for GitHub rate limit. Studio will resume automatically."),
|
||||
message = (
|
||||
"Waiting for GitHub rate limit. Studio will resume automatically."
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -161,7 +164,9 @@ def parse_log_message(msg: str) -> ParsedUpdate | None:
|
|||
source = "github",
|
||||
status = "rate_limited",
|
||||
retry_after_sec = seconds,
|
||||
message = ("Waiting for GitHub rate limit. Studio will resume automatically."),
|
||||
message = (
|
||||
"Waiting for GitHub rate limit. Studio will resume automatically."
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -379,13 +384,15 @@ def _apply_source_progress(job: Job, progress: SourceProgress) -> None:
|
|||
count_key = f"{progress.repo}:{progress.resource}"
|
||||
if page_key not in job._source_seen_pages:
|
||||
job._source_seen_pages.add(page_key)
|
||||
job._source_counts[count_key] = int(job._source_counts.get(count_key, 0)) + int(
|
||||
page_items or 0
|
||||
)
|
||||
job._source_counts[count_key] = int(
|
||||
job._source_counts.get(count_key, 0)
|
||||
) + int(page_items or 0)
|
||||
|
||||
fetched_items = sum(job._source_counts.values())
|
||||
if fetched_items <= 0:
|
||||
fetched_items = progress.fetched_items or (previous.fetched_items if previous else None)
|
||||
fetched_items = progress.fetched_items or (
|
||||
previous.fetched_items if previous else None
|
||||
)
|
||||
|
||||
estimated_total = (
|
||||
progress.estimated_total
|
||||
|
|
@ -405,10 +412,14 @@ def _apply_source_progress(job: Job, progress: SourceProgress) -> None:
|
|||
repo = progress.repo or (previous.repo if previous else None),
|
||||
resource = progress.resource or (previous.resource if previous else None),
|
||||
page = (
|
||||
progress.page if progress.page is not None else (previous.page if previous else None)
|
||||
progress.page
|
||||
if progress.page is not None
|
||||
else (previous.page if previous else None)
|
||||
),
|
||||
page_items = (
|
||||
page_items if page_items is not None else (previous.page_items if previous else None)
|
||||
page_items
|
||||
if page_items is not None
|
||||
else (previous.page_items if previous else None)
|
||||
),
|
||||
fetched_items = fetched_items,
|
||||
estimated_total = estimated_total,
|
||||
|
|
@ -439,7 +450,9 @@ def _compute_overall_progress(job: Job, column_progress: Progress) -> Progress:
|
|||
if len(job._column_done) == 0:
|
||||
done = current_done
|
||||
else:
|
||||
sum_done = sum(max(0, min(value, total_rows)) for value in job._column_done.values())
|
||||
sum_done = sum(
|
||||
max(0, min(value, total_rows)) for value in job._column_done.values()
|
||||
)
|
||||
done = int(sum_done / total_columns)
|
||||
|
||||
prev_done = int(job.progress.done or 0)
|
||||
|
|
|
|||
|
|
@ -60,7 +60,9 @@ def _slugify_run_name(value: str) -> str:
|
|||
return slug[:80].strip("-")
|
||||
|
||||
|
||||
def _build_dataset_name(*, run_name: str | None, job_id: str, artifact_root: Path) -> str:
|
||||
def _build_dataset_name(
|
||||
*, run_name: str | None, job_id: str, artifact_root: Path
|
||||
) -> str:
|
||||
fallback = f"recipe_{job_id}"
|
||||
slug = _slugify_run_name(run_name or "")
|
||||
base_name = f"recipe_{slug}" if slug else fallback
|
||||
|
|
@ -72,7 +74,9 @@ def _build_dataset_name(*, run_name: str | None, job_id: str, artifact_root: Pat
|
|||
return candidate
|
||||
|
||||
|
||||
def run_job_process(*, event_queue, recipe: dict[str, Any], run: dict[str, Any]) -> None:
|
||||
def run_job_process(
|
||||
*, event_queue, recipe: dict[str, Any], run: dict[str, Any]
|
||||
) -> None:
|
||||
"""Subprocess entrypoint. Sends events to `event_queue`."""
|
||||
import os
|
||||
|
||||
|
|
@ -160,10 +164,14 @@ def run_job_process(*, event_queue, recipe: dict[str, Any], run: dict[str, Any])
|
|||
}
|
||||
)
|
||||
else:
|
||||
results = designer.create(builder, num_records = rows, dataset_name = dataset_name)
|
||||
results = designer.create(
|
||||
builder, num_records = rows, dataset_name = dataset_name
|
||||
)
|
||||
analysis = to_jsonable(results.load_analysis().model_dump(mode = "json"))
|
||||
if merge_batches:
|
||||
_merge_batches_to_single_parquet(results.artifact_storage.base_dataset_path)
|
||||
_merge_batches_to_single_parquet(
|
||||
results.artifact_storage.base_dataset_path
|
||||
)
|
||||
artifact_path = str(results.artifact_storage.base_dataset_path)
|
||||
event_queue.put(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -133,7 +133,11 @@ def _parse_oxc_spec(*, column: dict[str, Any]) -> OxcLocalCallableValidatorSpec
|
|||
|
||||
target_columns_raw = column.get("target_columns")
|
||||
target_columns = (
|
||||
[value.strip() for value in target_columns_raw if isinstance(value, str) and value.strip()]
|
||||
[
|
||||
value.strip()
|
||||
for value in target_columns_raw
|
||||
if isinstance(value, str) and value.strip()
|
||||
]
|
||||
if isinstance(target_columns_raw, list)
|
||||
else []
|
||||
)
|
||||
|
|
@ -173,7 +177,9 @@ def _parse_oxc_validation_marker(fn_name: str) -> tuple[str, str, str]:
|
|||
return "javascript", "syntax", "auto"
|
||||
code_lang = parts[0] if parts[0] in _OXC_LANG_TO_NODE_LANG else "javascript"
|
||||
mode = parts[1] if parts[1] in _OXC_VALIDATION_MODES else "syntax"
|
||||
code_shape = parts[2] if len(parts) >= 3 and parts[2] in _OXC_CODE_SHAPES else "auto"
|
||||
code_shape = (
|
||||
parts[2] if len(parts) >= 3 and parts[2] in _OXC_CODE_SHAPES else "auto"
|
||||
)
|
||||
return code_lang, mode, code_shape
|
||||
|
||||
|
||||
|
|
@ -194,7 +200,10 @@ def _build_oxc_validation_function(lang: str, validation_mode: str, code_shape:
|
|||
code_values = (
|
||||
["" for _ in range(row_count)]
|
||||
if not code_column
|
||||
else ["" if value is None else str(value) for value in df[code_column].tolist()]
|
||||
else [
|
||||
"" if value is None else str(value)
|
||||
for value in df[code_column].tolist()
|
||||
]
|
||||
)
|
||||
|
||||
results = _run_oxc_batch(
|
||||
|
|
@ -210,9 +219,7 @@ def _build_oxc_validation_function(lang: str, validation_mode: str, code_shape:
|
|||
)
|
||||
return pd.DataFrame(results)
|
||||
|
||||
_validator.__name__ = (
|
||||
f"{OXC_VALIDATION_FN_MARKER}_{node_lang}_{mode.replace('+', '_')}_{normalized_code_shape}"
|
||||
)
|
||||
_validator.__name__ = f"{OXC_VALIDATION_FN_MARKER}_{node_lang}_{mode.replace('+', '_')}_{normalized_code_shape}"
|
||||
return _validator
|
||||
|
||||
|
||||
|
|
@ -292,13 +299,21 @@ def _run_oxc_batch(
|
|||
warning_count_raw = item.get("warning_count")
|
||||
out.append(
|
||||
{
|
||||
"is_valid": bool(is_valid_raw) if isinstance(is_valid_raw, bool) else False,
|
||||
"error_count": int(error_count_raw) if isinstance(error_count_raw, int) else 0,
|
||||
"is_valid": bool(is_valid_raw)
|
||||
if isinstance(is_valid_raw, bool)
|
||||
else False,
|
||||
"error_count": int(error_count_raw)
|
||||
if isinstance(error_count_raw, int)
|
||||
else 0,
|
||||
"error_message": str(message_raw or ""),
|
||||
"severity": str(severity_raw) if isinstance(severity_raw, str) else None,
|
||||
"severity": str(severity_raw)
|
||||
if isinstance(severity_raw, str)
|
||||
else None,
|
||||
"code": str(code_raw) if isinstance(code_raw, str) else None,
|
||||
"labels": labels_raw if isinstance(labels_raw, list) else [],
|
||||
"codeframe": str(codeframe_raw) if isinstance(codeframe_raw, str) else None,
|
||||
"codeframe": str(codeframe_raw)
|
||||
if isinstance(codeframe_raw, str)
|
||||
else None,
|
||||
"warning_count": int(warning_count_raw)
|
||||
if isinstance(warning_count_raw, int)
|
||||
else 0,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ def _encode_bytes_to_base64(value: bytes | bytearray) -> str:
|
|||
return base64.b64encode(bytes(value)).decode("utf-8")
|
||||
|
||||
|
||||
def _load_image_file_to_base64(path_value: str, *, base_path: str | None = None) -> str | None:
|
||||
def _load_image_file_to_base64(
|
||||
path_value: str, *, base_path: str | None = None
|
||||
) -> str | None:
|
||||
try:
|
||||
path = Path(path_value)
|
||||
candidates: list[Path] = []
|
||||
|
|
@ -117,7 +119,9 @@ def _apply_data_designer_image_context_patch() -> None:
|
|||
|
||||
original_auto_resolve = ImageContext._auto_resolve_context_value
|
||||
|
||||
def _patched_auto_resolve(self: Any, context_value: Any, base_path: str | None) -> Any:
|
||||
def _patched_auto_resolve(
|
||||
self: Any, context_value: Any, base_path: str | None
|
||||
) -> Any:
|
||||
normalized = _normalize_image_context_value(context_value, base_path = base_path)
|
||||
return original_auto_resolve(self, normalized, base_path)
|
||||
|
||||
|
|
@ -159,7 +163,9 @@ def _recipe_has_llm_columns(recipe: dict[str, Any]) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def _validate_recipe_runtime_support(recipe: dict[str, Any], model_providers: list[Any]) -> None:
|
||||
def _validate_recipe_runtime_support(
|
||||
recipe: dict[str, Any], model_providers: list[Any]
|
||||
) -> None:
|
||||
if _recipe_has_llm_columns(recipe) and not model_providers:
|
||||
raise ValueError("Add a Provider connection block before running this recipe.")
|
||||
|
||||
|
|
@ -249,7 +255,9 @@ def build_config_builder(recipe: dict[str, Any]):
|
|||
if key not in {"model_providers", "mcp_providers"}
|
||||
}
|
||||
recipe_core = _strip_frontend_model_config_metadata(recipe_core)
|
||||
recipe_core, oxc_local_callable_specs = split_oxc_local_callable_validators(recipe_core)
|
||||
recipe_core, oxc_local_callable_specs = split_oxc_local_callable_validators(
|
||||
recipe_core
|
||||
)
|
||||
builder = DataDesignerConfigBuilder.from_config({"data_designer": recipe_core})
|
||||
register_oxc_local_callable_validators(
|
||||
builder = builder,
|
||||
|
|
@ -320,10 +328,14 @@ def preview_recipe(
|
|||
dataset = [to_jsonable(row) for row in raw_rows]
|
||||
|
||||
artifacts = (
|
||||
None if results.processor_artifacts is None else to_jsonable(results.processor_artifacts)
|
||||
None
|
||||
if results.processor_artifacts is None
|
||||
else to_jsonable(results.processor_artifacts)
|
||||
)
|
||||
analysis = (
|
||||
None if results.analysis is None else to_jsonable(results.analysis.model_dump(mode = "json"))
|
||||
None
|
||||
if results.analysis is None
|
||||
else to_jsonable(results.analysis.model_dump(mode = "json"))
|
||||
)
|
||||
|
||||
return dataset, artifacts, analysis
|
||||
|
|
|
|||
|
|
@ -58,7 +58,9 @@ def _apply_wsl_sudo_patch():
|
|||
import unsloth_zoo.llama_cpp as llama_cpp_module
|
||||
|
||||
def _wsl_do_we_need_sudo(system_type = "debian"):
|
||||
logger.info("WSL detected — skipping sudo check (build deps pre-installed by setup.sh)")
|
||||
logger.info(
|
||||
"WSL detected — skipping sudo check (build deps pre-installed by setup.sh)"
|
||||
)
|
||||
return False
|
||||
|
||||
llama_cpp_module.do_we_need_sudo = _wsl_do_we_need_sudo
|
||||
|
|
@ -331,7 +333,9 @@ class ExportBackend:
|
|||
output_path: Optional[str] = None
|
||||
try:
|
||||
if _IS_MLX:
|
||||
mlx_save_method = "merged_4bit" if format_type == "4-bit (FP4)" else "merged_16bit"
|
||||
mlx_save_method = (
|
||||
"merged_4bit" if format_type == "4-bit (FP4)" else "merged_16bit"
|
||||
)
|
||||
else:
|
||||
if format_type == "4-bit (FP4)":
|
||||
save_method = "merged_4bit_forced"
|
||||
|
|
@ -394,7 +398,9 @@ class ExportBackend:
|
|||
private = private,
|
||||
)
|
||||
else:
|
||||
hub_save_method = save_method if save_method is not None else "merged_16bit"
|
||||
hub_save_method = (
|
||||
save_method if save_method is not None else "merged_16bit"
|
||||
)
|
||||
self.current_model.push_to_hub_merged(
|
||||
repo_id,
|
||||
self.current_tokenizer,
|
||||
|
|
@ -498,7 +504,9 @@ class ExportBackend:
|
|||
else:
|
||||
# Base model name from request or model config
|
||||
base_model = (
|
||||
base_model_id or self.current_model.config._name_or_path or "unknown"
|
||||
base_model_id
|
||||
or self.current_model.config._name_or_path
|
||||
or "unknown"
|
||||
)
|
||||
|
||||
hf_api = HfApi(token = hf_token)
|
||||
|
|
@ -518,7 +526,9 @@ class ExportBackend:
|
|||
extra = "unsloth",
|
||||
)
|
||||
card = ModelCard(content)
|
||||
card.push_to_hub(repo_id, token = hf_token, commit_message = "Unsloth Model Card")
|
||||
card.push_to_hub(
|
||||
repo_id, token = hf_token, commit_message = "Unsloth Model Card"
|
||||
)
|
||||
|
||||
if save_directory:
|
||||
hf_api.upload_folder(
|
||||
|
|
@ -581,7 +591,9 @@ class ExportBackend:
|
|||
LLAMA_CPP_DEFAULT_DIR,
|
||||
_resolve_local_convert_script, # noqa: F401
|
||||
)
|
||||
os.environ.setdefault("UNSLOTH_LLAMA_CPP_SCRIPTS_DIR", LLAMA_CPP_DEFAULT_DIR)
|
||||
os.environ.setdefault(
|
||||
"UNSLOTH_LLAMA_CPP_SCRIPTS_DIR", LLAMA_CPP_DEFAULT_DIR
|
||||
)
|
||||
except ImportError:
|
||||
if not _LLAMA_CPP_SCRIPTS_WARNING_EMITTED:
|
||||
logger.warning(
|
||||
|
|
@ -609,12 +621,16 @@ class ExportBackend:
|
|||
cwd = os.getcwd()
|
||||
pre_existing_ggufs = set(glob.glob(os.path.join(cwd, "*.gguf")))
|
||||
|
||||
pre_existing_subs = {d.name for d in Path(abs_save_dir).iterdir() if d.is_dir()}
|
||||
pre_existing_subs = {
|
||||
d.name for d in Path(abs_save_dir).iterdir() if d.is_dir()
|
||||
}
|
||||
|
||||
# Avoid clobbering an existing user-owned model/ directory.
|
||||
import uuid
|
||||
|
||||
_model_tmp = os.path.join(abs_save_dir, f"_tmp_model_{uuid.uuid4().hex[:8]}")
|
||||
_model_tmp = os.path.join(
|
||||
abs_save_dir, f"_tmp_model_{uuid.uuid4().hex[:8]}"
|
||||
)
|
||||
model_tmp_to_cleanup = _model_tmp
|
||||
self.current_model.save_pretrained_gguf(
|
||||
_model_tmp,
|
||||
|
|
@ -623,11 +639,15 @@ class ExportBackend:
|
|||
)
|
||||
|
||||
# Relocate the .gguf that convert_to_gguf wrote to cwd (repo root).
|
||||
new_ggufs = set(glob.glob(os.path.join(cwd, "*.gguf"))) - pre_existing_ggufs
|
||||
new_ggufs = (
|
||||
set(glob.glob(os.path.join(cwd, "*.gguf"))) - pre_existing_ggufs
|
||||
)
|
||||
for src in sorted(new_ggufs):
|
||||
dest = os.path.join(abs_save_dir, os.path.basename(src))
|
||||
shutil.move(src, dest)
|
||||
logger.info(f"Relocated GGUF: {os.path.basename(src)} → {abs_save_dir}/")
|
||||
logger.info(
|
||||
f"Relocated GGUF: {os.path.basename(src)} → {abs_save_dir}/"
|
||||
)
|
||||
|
||||
# Flatten GGUF files from subdirs created during this export.
|
||||
for sub in list(Path(abs_save_dir).iterdir()):
|
||||
|
|
@ -647,7 +667,10 @@ class ExportBackend:
|
|||
if self.current_checkpoint:
|
||||
ckpt = Path(self.current_checkpoint)
|
||||
gguf_dir = ckpt.parent / f"{ckpt.name}_gguf"
|
||||
if gguf_dir.is_dir() and gguf_dir.resolve() != Path(abs_save_dir).resolve():
|
||||
if (
|
||||
gguf_dir.is_dir()
|
||||
and gguf_dir.resolve() != Path(abs_save_dir).resolve()
|
||||
):
|
||||
for src in gguf_dir.glob("*.gguf"):
|
||||
dest = os.path.join(abs_save_dir, src.name)
|
||||
shutil.move(str(src), dest)
|
||||
|
|
@ -655,7 +678,9 @@ class ExportBackend:
|
|||
# Also relocate Ollama Modelfile if present
|
||||
modelfile = gguf_dir / "Modelfile"
|
||||
if modelfile.is_file():
|
||||
shutil.move(str(modelfile), os.path.join(abs_save_dir, "Modelfile"))
|
||||
shutil.move(
|
||||
str(modelfile), os.path.join(abs_save_dir, "Modelfile")
|
||||
)
|
||||
logger.info(f"Relocated Modelfile → {abs_save_dir}/")
|
||||
shutil.rmtree(str(gguf_dir), ignore_errors = True)
|
||||
logger.info(f"Cleaned up intermediate GGUF dir: {gguf_dir}")
|
||||
|
|
@ -763,8 +788,12 @@ class ExportBackend:
|
|||
repo_type = "model",
|
||||
)
|
||||
else:
|
||||
self.current_model.push_to_hub(repo_id, token = hf_token, private = private)
|
||||
self.current_tokenizer.push_to_hub(repo_id, token = hf_token, private = private)
|
||||
self.current_model.push_to_hub(
|
||||
repo_id, token = hf_token, private = private
|
||||
)
|
||||
self.current_tokenizer.push_to_hub(
|
||||
repo_id, token = hf_token, private = private
|
||||
)
|
||||
logger.info(f"Adapter pushed successfully to {repo_id}")
|
||||
|
||||
return True, "LoRA adapter exported successfully", output_path
|
||||
|
|
|
|||
|
|
@ -276,7 +276,9 @@ class ExportOrchestrator:
|
|||
expected_type,
|
||||
)
|
||||
|
||||
raise RuntimeError(f"Timeout waiting for '{expected_type}' response after {timeout}s")
|
||||
raise RuntimeError(
|
||||
f"Timeout waiting for '{expected_type}' response after {timeout}s"
|
||||
)
|
||||
|
||||
def _drain_queue(self) -> list:
|
||||
"""Drain all pending responses."""
|
||||
|
|
@ -326,7 +328,9 @@ class ExportOrchestrator:
|
|||
elif self._proc is not None:
|
||||
self._shutdown_subprocess(timeout = 2)
|
||||
|
||||
logger.info("Spawning fresh export subprocess for '%s'", checkpoint_path)
|
||||
logger.info(
|
||||
"Spawning fresh export subprocess for '%s'", checkpoint_path
|
||||
)
|
||||
self._spawn_subprocess(sub_config)
|
||||
|
||||
try:
|
||||
|
|
@ -438,7 +442,9 @@ class ExportOrchestrator:
|
|||
},
|
||||
)
|
||||
|
||||
def _run_export(self, export_type: str, params: dict) -> Tuple[bool, str, Optional[str]]:
|
||||
def _run_export(
|
||||
self, export_type: str, params: dict
|
||||
) -> Tuple[bool, str, Optional[str]]:
|
||||
"""Send an export command and wait for the result.
|
||||
|
||||
Returns ``(success, message, output_path)``. ``output_path`` is the on-disk
|
||||
|
|
@ -500,7 +506,9 @@ class ExportOrchestrator:
|
|||
finally:
|
||||
self._export_active = False
|
||||
|
||||
def scan_checkpoints(self, outputs_dir: str = str(outputs_root())) -> List[Tuple[str, list]]:
|
||||
def scan_checkpoints(
|
||||
self, outputs_dir: str = str(outputs_root())
|
||||
) -> List[Tuple[str, list]]:
|
||||
"""Scan for checkpoints — runs locally, no ML imports."""
|
||||
from utils.models.checkpoints import scan_checkpoints
|
||||
return scan_checkpoints(outputs_dir = outputs_dir)
|
||||
|
|
|
|||
|
|
@ -428,7 +428,9 @@ def run_export_process(*, cmd_queue: Any, resp_queue: Any, config: dict) -> None
|
|||
|
||||
import transformers
|
||||
|
||||
logger.info("Export subprocess loaded transformers %s", transformers.__version__)
|
||||
logger.info(
|
||||
"Export subprocess loaded transformers %s", transformers.__version__
|
||||
)
|
||||
|
||||
except Exception as exc:
|
||||
_send_response(
|
||||
|
|
@ -529,7 +531,9 @@ def run_export_process(*, cmd_queue: Any, resp_queue: Any, config: dict) -> None
|
|||
)
|
||||
|
||||
except Exception as exc:
|
||||
logger.error("Error handling command '%s': %s", cmd_type, exc, exc_info = True)
|
||||
logger.error(
|
||||
"Error handling command '%s': %s", cmd_type, exc, exc_info = True
|
||||
)
|
||||
_send_response(
|
||||
resp_queue,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -36,7 +36,11 @@ def anthropic_tool_use_id(upstream_id = None) -> str:
|
|||
"""Return an Anthropic-style tool_use id (prefix 'toolu_'). Reuses an
|
||||
upstream id only if it already starts with 'toolu_'; otherwise mints a fresh
|
||||
'toolu_<24 hex>'."""
|
||||
if upstream_id and isinstance(upstream_id, str) and upstream_id.startswith("toolu_"):
|
||||
if (
|
||||
upstream_id
|
||||
and isinstance(upstream_id, str)
|
||||
and upstream_id.startswith("toolu_")
|
||||
):
|
||||
return upstream_id
|
||||
return f"toolu_{uuid.uuid4().hex[:24]}"
|
||||
|
||||
|
|
@ -149,7 +153,9 @@ def anthropic_messages_to_openai(
|
|||
tc = b.get("content", "")
|
||||
if isinstance(tc, list):
|
||||
tc = " ".join(
|
||||
p["text"] for p in tc if isinstance(p, dict) and p.get("type") == "text"
|
||||
p["text"]
|
||||
for p in tc
|
||||
if isinstance(p, dict) and p.get("type") == "text"
|
||||
)
|
||||
tool_results.append(
|
||||
{
|
||||
|
|
@ -432,7 +438,9 @@ class AnthropicStreamEmitter:
|
|||
events.append(self._close_block())
|
||||
# Reuse the id published in content_block_start; fall back to mapping
|
||||
# the raw id only if no tool_start preceded this end.
|
||||
tool_use_id = self._open_tool_use_id or anthropic_tool_use_id(event.get("tool_call_id", ""))
|
||||
tool_use_id = self._open_tool_use_id or anthropic_tool_use_id(
|
||||
event.get("tool_call_id", "")
|
||||
)
|
||||
self._open_tool_call_id = None
|
||||
self._open_tool_use_id = None
|
||||
self._open_tool_args_sent = False
|
||||
|
|
|
|||
|
|
@ -77,7 +77,9 @@ class AudioCodecManager:
|
|||
return
|
||||
from snac import SNAC
|
||||
|
||||
self._snac_model = SNAC.from_pretrained("hubertsiuzdak/snac_24khz").to(device).eval()
|
||||
self._snac_model = (
|
||||
SNAC.from_pretrained("hubertsiuzdak/snac_24khz").to(device).eval()
|
||||
)
|
||||
logger.info("Loaded SNAC codec (24kHz)")
|
||||
|
||||
def _load_bicodec(
|
||||
|
|
@ -92,7 +94,9 @@ class AudioCodecManager:
|
|||
|
||||
# Clone SparkAudio/Spark-TTS for the sparktts package (HF model repos
|
||||
# don't contain it)
|
||||
spark_code_dir = os.path.join(os.path.dirname(model_repo_path or "."), "Spark-TTS")
|
||||
spark_code_dir = os.path.join(
|
||||
os.path.dirname(model_repo_path or "."), "Spark-TTS"
|
||||
)
|
||||
sparktts_pkg = os.path.join(spark_code_dir, "sparktts")
|
||||
if not os.path.isdir(sparktts_pkg):
|
||||
logger.info(f"Cloning SparkAudio/Spark-TTS to {spark_code_dir}...")
|
||||
|
|
@ -175,7 +179,9 @@ class AudioCodecManager:
|
|||
|
||||
# ── Decoders ─────────────────────────────────────────────────
|
||||
|
||||
def decode_snac(self, generated_ids: torch.Tensor, device: str) -> Tuple[bytes, int]:
|
||||
def decode_snac(
|
||||
self, generated_ids: torch.Tensor, device: str
|
||||
) -> Tuple[bytes, int]:
|
||||
"""Decode SNAC tokens (Orpheus) into WAV bytes.
|
||||
|
||||
Finds the START_OF_SPEECH (128257) marker, extracts codes after it,
|
||||
|
|
@ -188,7 +194,9 @@ class AudioCodecManager:
|
|||
cropped = generated_ids[:, token_indices[1][-1] + 1 :]
|
||||
else:
|
||||
# Fall back to the entire output if the marker is missing
|
||||
logger.warning("No START_OF_SPEECH token (128257) found — using full generated output")
|
||||
logger.warning(
|
||||
"No START_OF_SPEECH token (128257) found — using full generated output"
|
||||
)
|
||||
cropped = generated_ids
|
||||
row = cropped[0]
|
||||
|
||||
|
|
@ -214,7 +222,8 @@ class AudioCodecManager:
|
|||
layer_3.append(codes[7 * i + 6] - 24576)
|
||||
|
||||
snac_codes = [
|
||||
torch.tensor(layer).unsqueeze(0).to(device) for layer in [layer_1, layer_2, layer_3]
|
||||
torch.tensor(layer).unsqueeze(0).to(device)
|
||||
for layer in [layer_1, layer_2, layer_3]
|
||||
]
|
||||
|
||||
with torch.no_grad():
|
||||
|
|
@ -241,12 +250,16 @@ class AudioCodecManager:
|
|||
f"BiCodec decode: {len(global_matches)} global tokens, {len(semantic_matches)} semantic tokens"
|
||||
)
|
||||
if len(global_matches) < 10:
|
||||
logger.info(f"BiCodec generated text (first 500 chars): {generated_text[:500]}")
|
||||
logger.info(
|
||||
f"BiCodec generated text (first 500 chars): {generated_text[:500]}"
|
||||
)
|
||||
|
||||
if not semantic_matches:
|
||||
raise ValueError("No bicodec_semantic tokens found in generated output")
|
||||
|
||||
semantic_ids = torch.tensor([int(t) for t in semantic_matches]).long().unsqueeze(0)
|
||||
semantic_ids = (
|
||||
torch.tensor([int(t) for t in semantic_matches]).long().unsqueeze(0)
|
||||
)
|
||||
|
||||
# Speaker encoder expects exactly 32 global tokens (token_num=32);
|
||||
# pad with zeros or truncate.
|
||||
|
|
|
|||
|
|
@ -55,4 +55,6 @@ def apply_chat_template_for_generation(
|
|||
break
|
||||
if last_exc is not None:
|
||||
raise last_exc
|
||||
raise RuntimeError("apply_chat_template_for_generation: no attempt produced a result")
|
||||
raise RuntimeError(
|
||||
"apply_chat_template_for_generation: no attempt produced a result"
|
||||
)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -260,7 +260,9 @@ class InferenceBackend:
|
|||
if config.is_audio:
|
||||
audio_type = config.audio_type
|
||||
adapter_info = " (LoRA adapter)" if config.is_lora else ""
|
||||
logger.info(f"Loading audio ({audio_type}) model{adapter_info}: {model_name}")
|
||||
logger.info(
|
||||
f"Loading audio ({audio_type}) model{adapter_info}: {model_name}"
|
||||
)
|
||||
log_gpu_memory(f"Before loading {model_name}")
|
||||
|
||||
if audio_type == "csm":
|
||||
|
|
@ -294,7 +296,9 @@ class InferenceBackend:
|
|||
from huggingface_hub import snapshot_download
|
||||
|
||||
local_dir = base_path.split("/")[-1]
|
||||
repo_path = snapshot_download(base_path, local_dir = local_dir)
|
||||
repo_path = snapshot_download(
|
||||
base_path, local_dir = local_dir
|
||||
)
|
||||
abs_repo_path = os.path.abspath(repo_path)
|
||||
|
||||
logger.info(
|
||||
|
|
@ -405,7 +409,9 @@ class InferenceBackend:
|
|||
)
|
||||
|
||||
# Reject CPU/disk offload for audio models too
|
||||
raise_if_offloaded(self.models[model_name]["model"], device_map, "Inference")
|
||||
raise_if_offloaded(
|
||||
self.models[model_name]["model"], device_map, "Inference"
|
||||
)
|
||||
self.models[model_name]["context_length"] = runtime_context_length(
|
||||
self.models[model_name].get("model"),
|
||||
max_seq_length,
|
||||
|
|
@ -418,7 +424,9 @@ class InferenceBackend:
|
|||
return True
|
||||
|
||||
model_type = "vision" if config.is_vision else "text"
|
||||
adapter_info = " (LoRA adapter)" if self.models[model_name]["is_lora"] else ""
|
||||
adapter_info = (
|
||||
" (LoRA adapter)" if self.models[model_name]["is_lora"] else ""
|
||||
)
|
||||
logger.info(f"Loading {model_type} model{adapter_info}: {model_name}")
|
||||
log_gpu_memory(f"Before loading {model_name}")
|
||||
|
||||
|
|
@ -442,10 +450,13 @@ class InferenceBackend:
|
|||
from transformers import ProcessorMixin
|
||||
|
||||
if not (
|
||||
isinstance(processor, ProcessorMixin) or hasattr(processor, "image_processor")
|
||||
isinstance(processor, ProcessorMixin)
|
||||
or hasattr(processor, "image_processor")
|
||||
):
|
||||
# LoRA adapters: use base model. Local merged exports: read base from export_metadata.json.
|
||||
processor_source = config.base_model if config.is_lora else config.identifier
|
||||
processor_source = (
|
||||
config.base_model if config.is_lora else config.identifier
|
||||
)
|
||||
if not config.is_lora and config.is_local:
|
||||
_meta_path = Path(config.path) / "export_metadata.json"
|
||||
try:
|
||||
|
|
@ -466,7 +477,9 @@ class InferenceBackend:
|
|||
token = hf_token if hf_token and hf_token.strip() else None,
|
||||
trust_remote_code = trust_remote_code,
|
||||
)
|
||||
logger.info(f"Loaded {type(processor).__name__} from {processor_source}")
|
||||
logger.info(
|
||||
f"Loaded {type(processor).__name__} from {processor_source}"
|
||||
)
|
||||
|
||||
self.models[model_name]["model"] = model
|
||||
self.models[model_name]["tokenizer"] = processor
|
||||
|
|
@ -489,7 +502,9 @@ class InferenceBackend:
|
|||
self.models[model_name]["model"] = model
|
||||
self.models[model_name]["tokenizer"] = tokenizer
|
||||
|
||||
raise_if_offloaded(self.models[model_name]["model"], device_map, "Inference")
|
||||
raise_if_offloaded(
|
||||
self.models[model_name]["model"], device_map, "Inference"
|
||||
)
|
||||
self.models[model_name]["context_length"] = runtime_context_length(
|
||||
self.models[model_name].get("model"),
|
||||
max_seq_length,
|
||||
|
|
@ -537,7 +552,11 @@ class InferenceBackend:
|
|||
import sys as _sys
|
||||
from utils.cache_cleanup import clear_unsloth_compiled_cache
|
||||
|
||||
_preserve = ["Unsloth*Trainer.py"] if _sys.platform in ("win32", "darwin") else None
|
||||
_preserve = (
|
||||
["Unsloth*Trainer.py"]
|
||||
if _sys.platform in ("win32", "darwin")
|
||||
else None
|
||||
)
|
||||
clear_unsloth_compiled_cache(preserve_patterns = _preserve)
|
||||
|
||||
logger.info(f"Model '{model_name}' successfully unloaded.")
|
||||
|
|
@ -604,9 +623,13 @@ class InferenceBackend:
|
|||
base_model_name = lora_config.base_model
|
||||
|
||||
# 1. Load the base model if not already in memory
|
||||
if base_model_name not in self.models or not self.models[base_model_name].get("model"):
|
||||
if base_model_name not in self.models or not self.models[
|
||||
base_model_name
|
||||
].get("model"):
|
||||
logger.info(f"Base model '{base_model_name}' not loaded, loading now.")
|
||||
base_config = ModelConfig.from_ui_selection(base_model_name, None, is_lora = False)
|
||||
base_config = ModelConfig.from_ui_selection(
|
||||
base_model_name, None, is_lora = False
|
||||
)
|
||||
if not self.load_model(
|
||||
base_config,
|
||||
max_seq_length,
|
||||
|
|
@ -642,7 +665,9 @@ class InferenceBackend:
|
|||
logger.error(traceback.format_exc())
|
||||
return False, None, None
|
||||
|
||||
def load_adapter(self, base_model_name: str, adapter_path: str, adapter_name: str) -> bool:
|
||||
def load_adapter(
|
||||
self, base_model_name: str, adapter_path: str, adapter_name: str
|
||||
) -> bool:
|
||||
"""Load an adapter onto the model only if not already attached."""
|
||||
model = self.models[base_model_name].get("model")
|
||||
|
||||
|
|
@ -713,12 +738,16 @@ class InferenceBackend:
|
|||
)
|
||||
model.base_model.disable_adapter_layers()
|
||||
else:
|
||||
logger.info(f"Compare mode: model '{base}' is not a PeftModel, already base")
|
||||
logger.info(
|
||||
f"Compare mode: model '{base}' is not a PeftModel, already base"
|
||||
)
|
||||
|
||||
elif use_adapter is True:
|
||||
# Re-enable LoRA layers -> adapter output.
|
||||
if isinstance(model, (PeftModel, PeftModelForCausalLM)):
|
||||
logger.info(f"Compare mode: enabling adapters on '{base}' for LoRA generation")
|
||||
logger.info(
|
||||
f"Compare mode: enabling adapters on '{base}' for LoRA generation"
|
||||
)
|
||||
model.base_model.enable_adapter_layers()
|
||||
else:
|
||||
logger.warning("use_adapter=true but model is not a PeftModel")
|
||||
|
|
@ -726,11 +755,15 @@ class InferenceBackend:
|
|||
elif isinstance(use_adapter, str):
|
||||
# Enable adapters and set the named one active.
|
||||
if isinstance(model, (PeftModel, PeftModelForCausalLM)):
|
||||
logger.info(f"Compare mode: enabling adapter '{use_adapter}' on '{base}'")
|
||||
logger.info(
|
||||
f"Compare mode: enabling adapter '{use_adapter}' on '{base}'"
|
||||
)
|
||||
model.base_model.enable_adapter_layers()
|
||||
self.set_active_adapter(base, use_adapter)
|
||||
else:
|
||||
logger.warning(f"use_adapter='{use_adapter}' but model is not a PeftModel")
|
||||
logger.warning(
|
||||
f"use_adapter='{use_adapter}' but model is not a PeftModel"
|
||||
)
|
||||
|
||||
def generate_with_adapter_control(
|
||||
self,
|
||||
|
|
@ -902,7 +935,8 @@ class InferenceBackend:
|
|||
|
||||
processor = model_info.get("processor")
|
||||
has_image_processing = processor is not None and (
|
||||
isinstance(processor, ProcessorMixin) or hasattr(processor, "image_processor")
|
||||
isinstance(processor, ProcessorMixin)
|
||||
or hasattr(processor, "image_processor")
|
||||
)
|
||||
if has_image_processing:
|
||||
yield from self._generate_vision_response(
|
||||
|
|
@ -954,7 +988,9 @@ class InferenceBackend:
|
|||
|
||||
# Step 2: format with tokenizer.apply_chat_template().
|
||||
if system_prompt:
|
||||
template_messages = [{"role": "system", "content": system_prompt}] + messages
|
||||
template_messages = [
|
||||
{"role": "system", "content": system_prompt}
|
||||
] + messages
|
||||
else:
|
||||
template_messages = messages
|
||||
try:
|
||||
|
|
@ -1069,7 +1105,9 @@ class InferenceBackend:
|
|||
else:
|
||||
# Text-only path for a vision model
|
||||
formatted_prompt = self.format_chat_prompt(messages, system_prompt)
|
||||
inputs = raw_tokenizer(formatted_prompt, return_tensors = "pt").to(model.device)
|
||||
inputs = raw_tokenizer(formatted_prompt, return_tensors = "pt").to(
|
||||
model.device
|
||||
)
|
||||
|
||||
# Stream with TextIteratorStreamer + background thread
|
||||
try:
|
||||
|
|
@ -1356,7 +1394,9 @@ class InferenceBackend:
|
|||
timeout = 0.2,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"HarmonyTextStreamer init failed, falling back: {e}")
|
||||
logger.warning(
|
||||
f"HarmonyTextStreamer init failed, falling back: {e}"
|
||||
)
|
||||
streamer = TextIteratorStreamer(
|
||||
tokenizer,
|
||||
skip_prompt = True,
|
||||
|
|
@ -1452,7 +1492,9 @@ class InferenceBackend:
|
|||
cancel_event.set()
|
||||
thread.join(timeout = 10)
|
||||
if thread.is_alive():
|
||||
logger.warning("Generation thread did not exit after cancel/join timeout")
|
||||
logger.warning(
|
||||
"Generation thread did not exit after cancel/join timeout"
|
||||
)
|
||||
|
||||
if err.get("msg"):
|
||||
yield f"Error: {err['msg']}"
|
||||
|
|
@ -1527,12 +1569,21 @@ class InferenceBackend:
|
|||
raise RuntimeError(f"Unknown audio_type: {audio_type}")
|
||||
|
||||
def _generate_snac(
|
||||
self, model, tokenizer, text, temperature, top_p, max_new_tokens, repetition_penalty
|
||||
self,
|
||||
model,
|
||||
tokenizer,
|
||||
text,
|
||||
temperature,
|
||||
top_p,
|
||||
max_new_tokens,
|
||||
repetition_penalty,
|
||||
):
|
||||
"""Generate audio using SNAC codec (Orpheus)."""
|
||||
device = model.device
|
||||
start_token = torch.tensor([[128259]], device = device) # START_OF_HUMAN
|
||||
end_tokens = torch.tensor([[128009, 128260]], device = device) # EOT, END_OF_HUMAN
|
||||
end_tokens = torch.tensor(
|
||||
[[128009, 128260]], device = device
|
||||
) # EOT, END_OF_HUMAN
|
||||
text_ids = tokenizer(text, return_tensors = "pt").input_ids.to(device)
|
||||
input_ids = torch.cat([start_token, text_ids, end_tokens], dim = 1)
|
||||
attention_mask = torch.ones_like(input_ids)
|
||||
|
|
@ -1556,12 +1607,20 @@ class InferenceBackend:
|
|||
inputs = processor(
|
||||
f"[{speaker_id}]{text}", add_special_tokens = True, return_tensors = "pt"
|
||||
).to(model.device)
|
||||
audio_values = model.generate(**inputs, max_new_tokens = max_new_tokens, output_audio = True)
|
||||
audio_values = model.generate(
|
||||
**inputs, max_new_tokens = max_new_tokens, output_audio = True
|
||||
)
|
||||
return self._audio_codec_manager.decode_csm(audio_values)
|
||||
|
||||
def _generate_bicodec(self, model, tokenizer, text, temperature, top_k, max_new_tokens):
|
||||
def _generate_bicodec(
|
||||
self, model, tokenizer, text, temperature, top_k, max_new_tokens
|
||||
):
|
||||
"""Generate audio using BiCodec (Spark-TTS)."""
|
||||
prompt = "<|task_tts|><|start_content|>" + text + "<|end_content|><|start_global_token|>"
|
||||
prompt = (
|
||||
"<|task_tts|><|start_content|>"
|
||||
+ text
|
||||
+ "<|end_content|><|start_global_token|>"
|
||||
)
|
||||
inputs = tokenizer([prompt], return_tensors = "pt").to(model.device)
|
||||
generated = model.generate(
|
||||
**inputs,
|
||||
|
|
@ -1632,7 +1691,9 @@ class InferenceBackend:
|
|||
def __init__(self, penalty: float):
|
||||
self.penalty_last_n = 64
|
||||
if not isinstance(penalty, float) or penalty <= 0:
|
||||
raise ValueError(f"`penalty` has to be a positive float, but is {penalty}")
|
||||
raise ValueError(
|
||||
f"`penalty` has to be a positive float, but is {penalty}"
|
||||
)
|
||||
self.penalty = penalty
|
||||
|
||||
@torch.no_grad()
|
||||
|
|
@ -1657,8 +1718,12 @@ class InferenceBackend:
|
|||
)
|
||||
return scores
|
||||
|
||||
generation_utils.RepetitionPenaltyLogitsProcessor = RepetitionPenaltyLogitsProcessorPatch
|
||||
logger.info("Patched RepetitionPenaltyLogitsProcessor with 64-token window for OuteTTS")
|
||||
generation_utils.RepetitionPenaltyLogitsProcessor = (
|
||||
RepetitionPenaltyLogitsProcessorPatch
|
||||
)
|
||||
logger.info(
|
||||
"Patched RepetitionPenaltyLogitsProcessor with 64-token window for OuteTTS"
|
||||
)
|
||||
|
||||
def _apply_chat_template_for_generation(
|
||||
self,
|
||||
|
|
@ -1700,7 +1765,9 @@ class InferenceBackend:
|
|||
logger.error("Tokenizer not loaded for active model")
|
||||
return ""
|
||||
|
||||
chat_template_info = self.models[self.active_model_name].get("chat_template_info", {})
|
||||
chat_template_info = self.models[self.active_model_name].get(
|
||||
"chat_template_info", {}
|
||||
)
|
||||
tokenizer = self.models[self.active_model_name]["tokenizer"]
|
||||
tokenizer = getattr(tokenizer, "tokenizer", tokenizer)
|
||||
|
||||
|
|
@ -1717,7 +1784,9 @@ class InferenceBackend:
|
|||
|
||||
if role in ["system", "user", "assistant"] and content.strip():
|
||||
if role == last_role:
|
||||
logger.debug(f"Skipping consecutive {role} message to maintain alternation")
|
||||
logger.debug(
|
||||
f"Skipping consecutive {role} message to maintain alternation"
|
||||
)
|
||||
continue
|
||||
|
||||
if role == "user":
|
||||
|
|
@ -1733,7 +1802,9 @@ class InferenceBackend:
|
|||
continue
|
||||
|
||||
if chat_messages and chat_messages[-1]["role"] == "assistant":
|
||||
logger.debug("Removing final assistant message to ensure proper alternation")
|
||||
logger.debug(
|
||||
"Removing final assistant message to ensure proper alternation"
|
||||
)
|
||||
chat_messages.pop()
|
||||
|
||||
logger.info(f"Sending {len(chat_messages)} messages to tokenizer:")
|
||||
|
|
@ -1748,7 +1819,10 @@ class InferenceBackend:
|
|||
return formatted_prompt
|
||||
except Exception as e:
|
||||
error_msg = str(e).lower()
|
||||
if "chat_template is not set" in error_msg or "no template argument" in error_msg:
|
||||
if (
|
||||
"chat_template is not set" in error_msg
|
||||
or "no template argument" in error_msg
|
||||
):
|
||||
logger.info(
|
||||
f"Base model detected - no built-in chat template available, using fallback formatting"
|
||||
)
|
||||
|
|
@ -1759,7 +1833,9 @@ class InferenceBackend:
|
|||
)
|
||||
|
||||
if chat_template_info.get("has_template", False):
|
||||
logger.info("Falling back to manual template formatting based on detected patterns")
|
||||
logger.info(
|
||||
"Falling back to manual template formatting based on detected patterns"
|
||||
)
|
||||
template_type = chat_template_info.get("format_type", "generic")
|
||||
manual_prompt = self._format_chat_manual(
|
||||
chat_messages,
|
||||
|
|
@ -1772,7 +1848,9 @@ class InferenceBackend:
|
|||
logger.info("Using generic chat formatting for base model")
|
||||
return self._format_generic_template(chat_messages, {})
|
||||
|
||||
def _format_chat_manual(self, messages: list, template_type: str, special_tokens: dict) -> str:
|
||||
def _format_chat_manual(
|
||||
self, messages: list, template_type: str, special_tokens: dict
|
||||
) -> str:
|
||||
"""Manual chat-formatting fallback when the tokenizer template fails.
|
||||
|
||||
Args:
|
||||
|
|
@ -1802,7 +1880,9 @@ class InferenceBackend:
|
|||
for msg in messages:
|
||||
role = msg["role"]
|
||||
content = msg["content"]
|
||||
formatted += f"<|start_header_id|>{role}<|end_header_id|>\n\n{content}<|eot_id|>"
|
||||
formatted += (
|
||||
f"<|start_header_id|>{role}<|end_header_id|>\n\n{content}<|eot_id|>"
|
||||
)
|
||||
|
||||
formatted += "<|start_header_id|>assistant<|end_header_id|>\n\n"
|
||||
return formatted
|
||||
|
|
@ -1831,7 +1911,10 @@ class InferenceBackend:
|
|||
|
||||
formatted += f"[INST] {user_content} [/INST]"
|
||||
|
||||
if i + 1 < len(conversation) and conversation[i + 1]["role"] == "assistant":
|
||||
if (
|
||||
i + 1 < len(conversation)
|
||||
and conversation[i + 1]["role"] == "assistant"
|
||||
):
|
||||
formatted += f" {conversation[i + 1]['content']}</s>"
|
||||
i += 2
|
||||
else:
|
||||
|
|
@ -1965,7 +2048,9 @@ class InferenceBackend:
|
|||
return text.strip()
|
||||
|
||||
def _load_chat_template_info(self, model_name: str):
|
||||
if model_name not in self.models or not self.models[model_name].get("tokenizer"):
|
||||
if model_name not in self.models or not self.models[model_name].get(
|
||||
"tokenizer"
|
||||
):
|
||||
return
|
||||
|
||||
tokenizer = self.models[model_name]["tokenizer"]
|
||||
|
|
@ -1983,7 +2068,9 @@ class InferenceBackend:
|
|||
# Exact match first
|
||||
model_name_lower = model_name.lower()
|
||||
if model_name_lower in MODEL_TO_TEMPLATE_MAPPER:
|
||||
chat_template_info["template_name"] = MODEL_TO_TEMPLATE_MAPPER[model_name_lower]
|
||||
chat_template_info["template_name"] = MODEL_TO_TEMPLATE_MAPPER[
|
||||
model_name_lower
|
||||
]
|
||||
logger.info(
|
||||
f"Detected template '{chat_template_info['template_name']}' for {model_name} from mapper"
|
||||
)
|
||||
|
|
@ -1991,13 +2078,17 @@ class InferenceBackend:
|
|||
# Partial match (for variants like model_name-bnb-4bit)
|
||||
for key in MODEL_TO_TEMPLATE_MAPPER:
|
||||
if key in model_name_lower or model_name_lower in key:
|
||||
chat_template_info["template_name"] = MODEL_TO_TEMPLATE_MAPPER[key]
|
||||
chat_template_info["template_name"] = MODEL_TO_TEMPLATE_MAPPER[
|
||||
key
|
||||
]
|
||||
logger.info(
|
||||
f"Detected template '{chat_template_info['template_name']}' for {model_name} (partial match)"
|
||||
)
|
||||
break
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not detect template from mapper for {model_name}: {e}")
|
||||
logger.warning(
|
||||
f"Could not detect template from mapper for {model_name}: {e}"
|
||||
)
|
||||
|
||||
try:
|
||||
if hasattr(tokenizer, "chat_template") and tokenizer.chat_template:
|
||||
|
|
@ -2006,7 +2097,10 @@ class InferenceBackend:
|
|||
|
||||
template_str = tokenizer.chat_template.lower()
|
||||
|
||||
if "start_header_id" in template_str and "end_header_id" in template_str:
|
||||
if (
|
||||
"start_header_id" in template_str
|
||||
and "end_header_id" in template_str
|
||||
):
|
||||
chat_template_info["format_type"] = "llama3"
|
||||
elif "[inst]" in template_str and "[/inst]" in template_str:
|
||||
chat_template_info["format_type"] = "mistral"
|
||||
|
|
@ -2033,7 +2127,9 @@ class InferenceBackend:
|
|||
chat_template_info["special_tokens"] = special_tokens
|
||||
|
||||
else:
|
||||
logger.info(f"No chat template found for {model_name}, will use generic formatting")
|
||||
logger.info(
|
||||
f"No chat template found for {model_name}, will use generic formatting"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading chat template info for {model_name}: {e}")
|
||||
|
|
@ -2045,7 +2141,9 @@ class InferenceBackend:
|
|||
f"Chat template loaded for {model_name}: {chat_template_info['format_type']} format"
|
||||
)
|
||||
else:
|
||||
logger.info(f"No built-in chat template for {model_name}, will use generic formatting")
|
||||
logger.info(
|
||||
f"No built-in chat template for {model_name}, will use generic formatting"
|
||||
)
|
||||
|
||||
def get_current_model(self) -> Optional[str]:
|
||||
"""Currently active model name."""
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -124,7 +124,9 @@ def is_managed_flag(flag: str) -> bool:
|
|||
# from inherited extras so they can't last-wins-override an Apply that
|
||||
# re-sets the same field.
|
||||
_CONTEXT_FLAGS: frozenset[str] = frozenset({"-c", "--ctx-size"})
|
||||
_CACHE_FLAGS: frozenset[str] = frozenset({"-ctk", "--cache-type-k", "-ctv", "--cache-type-v"})
|
||||
_CACHE_FLAGS: frozenset[str] = frozenset(
|
||||
{"-ctk", "--cache-type-k", "-ctv", "--cache-type-v"}
|
||||
)
|
||||
_SPEC_FLAGS: frozenset[str] = frozenset(
|
||||
{
|
||||
"--spec-default",
|
||||
|
|
@ -170,11 +172,17 @@ _TENSOR_SPLIT_FLAGS: frozenset[str] = frozenset({"-ts", "--tensor-split"})
|
|||
_SPLIT_SHADOWING_FLAGS: frozenset[str] = _SPLIT_MODE_FLAGS | _TENSOR_SPLIT_FLAGS
|
||||
|
||||
_SHADOWING_FLAGS: frozenset[str] = (
|
||||
_CONTEXT_FLAGS | _CACHE_FLAGS | _SPEC_FLAGS | _TEMPLATE_FLAGS | _SPLIT_SHADOWING_FLAGS
|
||||
_CONTEXT_FLAGS
|
||||
| _CACHE_FLAGS
|
||||
| _SPEC_FLAGS
|
||||
| _TEMPLATE_FLAGS
|
||||
| _SPLIT_SHADOWING_FLAGS
|
||||
)
|
||||
|
||||
# Shadowing flags that take no value -- strip the flag only, not the next token.
|
||||
_BOOLEAN_SHADOWING_FLAGS: frozenset[str] = frozenset({"--spec-default", "--jinja", "--no-jinja"})
|
||||
_BOOLEAN_SHADOWING_FLAGS: frozenset[str] = frozenset(
|
||||
{"--spec-default", "--jinja", "--no-jinja"}
|
||||
)
|
||||
|
||||
|
||||
def parse_ctx_override(args: Optional[Iterable[str]]) -> Optional[int]:
|
||||
|
|
@ -201,16 +209,22 @@ def parse_ctx_override(args: Optional[Iterable[str]]) -> Optional[int]:
|
|||
i += 1
|
||||
else:
|
||||
if i + 1 >= n or _flag_name(tokens[i + 1]) is not None:
|
||||
raise ValueError(f"llama-server flag '{flag}' requires an integer value")
|
||||
raise ValueError(
|
||||
f"llama-server flag '{flag}' requires an integer value"
|
||||
)
|
||||
raw_value = tokens[i + 1]
|
||||
i += 2
|
||||
|
||||
try:
|
||||
value = int(str(raw_value).strip())
|
||||
except ValueError as exc:
|
||||
raise ValueError(f"llama-server flag '{flag}' requires an integer value") from exc
|
||||
raise ValueError(
|
||||
f"llama-server flag '{flag}' requires an integer value"
|
||||
) from exc
|
||||
if value < 0:
|
||||
raise ValueError(f"llama-server flag '{flag}' requires a non-negative integer value")
|
||||
raise ValueError(
|
||||
f"llama-server flag '{flag}' requires a non-negative integer value"
|
||||
)
|
||||
override = value
|
||||
|
||||
return override
|
||||
|
|
@ -226,7 +240,9 @@ def resolve_requested_ctx(args: Optional[Iterable[str]], fallback_n_ctx: int) ->
|
|||
return override if override is not None else fallback_n_ctx
|
||||
|
||||
|
||||
def _last_flag_value(args: Optional[Iterable[str]], flags: frozenset[str]) -> Optional[str]:
|
||||
def _last_flag_value(
|
||||
args: Optional[Iterable[str]], flags: frozenset[str]
|
||||
) -> Optional[str]:
|
||||
"""Return the last-wins string value among ``flags`` in extras, or None.
|
||||
|
||||
Handles both ``--flag=value`` and ``--flag value`` forms and raises if a
|
||||
|
|
@ -295,7 +311,9 @@ def parse_split_mode_override(args: Optional[Iterable[str]]) -> Optional[str]:
|
|||
return _last_flag_value(args, _SPLIT_MODE_FLAGS)
|
||||
|
||||
|
||||
def resolve_tensor_parallel(args: Optional[Iterable[str]], fallback_tensor_parallel: bool) -> bool:
|
||||
def resolve_tensor_parallel(
|
||||
args: Optional[Iterable[str]], fallback_tensor_parallel: bool
|
||||
) -> bool:
|
||||
"""Return the tensor-parallel state load_model should treat as requested.
|
||||
|
||||
A user-supplied ``--split-mode`` in extras last-wins-overrides the
|
||||
|
|
|
|||
|
|
@ -217,7 +217,9 @@ def _client(
|
|||
auth = OAuth(mcp_url = url, token_storage = _oauth_store())
|
||||
|
||||
transport_cls = (
|
||||
SSETransport if infer_transport_type_from_url(url) == "sse" else StreamableHttpTransport
|
||||
SSETransport
|
||||
if infer_transport_type_from_url(url) == "sse"
|
||||
else StreamableHttpTransport
|
||||
)
|
||||
return Client(transport_cls(url = url, headers = headers or None, auth = auth))
|
||||
|
||||
|
|
@ -252,7 +254,9 @@ _probe_cooloff_until: dict[str, float] = {}
|
|||
# endpoint/auth used to probe it (url, headers, oauth) or whether it's used at
|
||||
# all (is_enabled). A rename does not. The update route's eviction and
|
||||
# get_enabled_mcp_tools' mid-probe guard both key off this so they can't drift.
|
||||
TOOL_CACHE_INVALIDATING_FIELDS = frozenset({"url", "headers_json", "use_oauth", "is_enabled"})
|
||||
TOOL_CACHE_INVALIDATING_FIELDS = frozenset(
|
||||
{"url", "headers_json", "use_oauth", "is_enabled"}
|
||||
)
|
||||
|
||||
|
||||
def get_cached_tools(server_id: str) -> Optional[list[dict]]:
|
||||
|
|
@ -265,7 +269,11 @@ def cache_tools(server_id: str, tools: list[dict]) -> None:
|
|||
|
||||
|
||||
def record_probe_failure(server_id: str, use_oauth: bool = False) -> None:
|
||||
cooloff = OAUTH_FAILED_PROBE_COOLOFF_SECONDS if use_oauth else FAILED_PROBE_COOLOFF_SECONDS
|
||||
cooloff = (
|
||||
OAUTH_FAILED_PROBE_COOLOFF_SECONDS
|
||||
if use_oauth
|
||||
else FAILED_PROBE_COOLOFF_SECONDS
|
||||
)
|
||||
_probe_cooloff_until[server_id] = time.monotonic() + cooloff
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -60,14 +60,19 @@ def _enabled_from_spec(label: str, spec: dict) -> tuple[Optional[bool], Optional
|
|||
return not disabled, None
|
||||
|
||||
|
||||
def _parse_entry(name: str, spec: object) -> tuple[Optional[ParsedMcpEntry], Optional[str]]:
|
||||
def _parse_entry(
|
||||
name: str, spec: object
|
||||
) -> tuple[Optional[ParsedMcpEntry], Optional[str]]:
|
||||
label = str(name).strip()
|
||||
if not label:
|
||||
return None, "Server entry has an empty name."
|
||||
if not isinstance(spec, dict):
|
||||
return None, f"{label}: entry must be an object."
|
||||
if _has_variable_reference(spec):
|
||||
return None, f"{label}: VS Code variable references are not supported by import."
|
||||
return (
|
||||
None,
|
||||
f"{label}: VS Code variable references are not supported by import.",
|
||||
)
|
||||
|
||||
is_enabled, error = _enabled_from_spec(label, spec)
|
||||
if error:
|
||||
|
|
@ -91,8 +96,13 @@ def _parse_entry(name: str, spec: object) -> tuple[Optional[ParsedMcpEntry], Opt
|
|||
if sandbox_enabled is not None and not isinstance(sandbox_enabled, bool):
|
||||
return None, f"{label}: 'sandboxEnabled' must be true or false."
|
||||
if sandbox_enabled:
|
||||
return None, f"{label}: sandboxed stdio servers cannot be preserved by import."
|
||||
unsupported = [field for field in _UNSUPPORTED_STDIO_FIELDS if spec.get(field) is not None]
|
||||
return (
|
||||
None,
|
||||
f"{label}: sandboxed stdio servers cannot be preserved by import.",
|
||||
)
|
||||
unsupported = [
|
||||
field for field in _UNSUPPORTED_STDIO_FIELDS if spec.get(field) is not None
|
||||
]
|
||||
if unsupported:
|
||||
return None, f"{label}: import cannot preserve {', '.join(unsupported)}."
|
||||
if spec.get("oauth") is not None:
|
||||
|
|
@ -104,7 +114,10 @@ def _parse_entry(name: str, spec: object) -> tuple[Optional[ParsedMcpEntry], Opt
|
|||
if env is not None and not isinstance(env, dict):
|
||||
return None, f"{label}: 'env' must be an object."
|
||||
if _has_null_value(env):
|
||||
return None, f"{label}: null environment values are not supported by import."
|
||||
return (
|
||||
None,
|
||||
f"{label}: null environment values are not supported by import.",
|
||||
)
|
||||
url = join_stdio_command([command, *(str(a) for a in args)])
|
||||
headers = _coerce_str_dict(env) if env else None
|
||||
return ParsedMcpEntry(label, url, headers, True, is_enabled = is_enabled), None
|
||||
|
|
@ -120,12 +133,21 @@ def _parse_entry(name: str, spec: object) -> tuple[Optional[ParsedMcpEntry], Opt
|
|||
field for field in _UNSUPPORTED_TIMEOUT_FIELDS if spec.get(field) is not None
|
||||
]
|
||||
if unsupported_timeout:
|
||||
return None, f"{label}: import cannot preserve {', '.join(unsupported_timeout)}."
|
||||
return (
|
||||
None,
|
||||
f"{label}: import cannot preserve {', '.join(unsupported_timeout)}.",
|
||||
)
|
||||
url_infers_sse = url.rstrip("/").endswith("/sse")
|
||||
if entry_type == "sse" and not url_infers_sse:
|
||||
return None, f"{label}: explicit SSE transport cannot be preserved for this URL."
|
||||
return (
|
||||
None,
|
||||
f"{label}: explicit SSE transport cannot be preserved for this URL.",
|
||||
)
|
||||
if entry_type in _HTTP_REMOTE_TYPES and url_infers_sse:
|
||||
return None, f"{label}: explicit HTTP transport cannot be preserved for this URL."
|
||||
return (
|
||||
None,
|
||||
f"{label}: explicit HTTP transport cannot be preserved for this URL.",
|
||||
)
|
||||
oauth_raw = spec.get("oauth")
|
||||
if oauth_raw is not None and not isinstance(oauth_raw, dict):
|
||||
return None, f"{label}: 'oauth' must be an object."
|
||||
|
|
|
|||
|
|
@ -296,7 +296,9 @@ class MLXInferenceBackend:
|
|||
elif isinstance(content, list):
|
||||
# Prepend image if not already present
|
||||
has_image = any(
|
||||
p.get("type") == "image" for p in content if isinstance(p, dict)
|
||||
p.get("type") == "image"
|
||||
for p in content
|
||||
if isinstance(p, dict)
|
||||
)
|
||||
if not has_image:
|
||||
content.insert(0, {"type": "image"})
|
||||
|
|
@ -366,7 +368,9 @@ class MLXInferenceBackend:
|
|||
preserve_thinking = preserve_thinking,
|
||||
)
|
||||
if prompt is None:
|
||||
raise RuntimeError("apply_chat_template returned None — tokenizer may be incompatible")
|
||||
raise RuntimeError(
|
||||
"apply_chat_template returned None — tokenizer may be incompatible"
|
||||
)
|
||||
|
||||
sampler = make_sampler(
|
||||
temp = temperature,
|
||||
|
|
@ -513,7 +517,9 @@ class MLXInferenceBackend:
|
|||
**vlm_kwargs,
|
||||
):
|
||||
final_response = response
|
||||
token_text = response.text if hasattr(response, "text") else str(response)
|
||||
token_text = (
|
||||
response.text if hasattr(response, "text") else str(response)
|
||||
)
|
||||
cumulative += token_text
|
||||
yield cumulative
|
||||
if cancel_event and cancel_event.is_set():
|
||||
|
|
|
|||
|
|
@ -89,7 +89,9 @@ class InferenceOrchestrator:
|
|||
atexit.register(self._cleanup)
|
||||
logger.info("InferenceOrchestrator initialized (subprocess mode)")
|
||||
|
||||
threading.Thread(target = self._fetch_top_models, daemon = True, name = "top-models").start()
|
||||
threading.Thread(
|
||||
target = self._fetch_top_models, daemon = True, name = "top-models"
|
||||
).start()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Default models (top GGUFs fetched dynamically from HF)
|
||||
|
|
@ -129,12 +131,14 @@ class InferenceOrchestrator:
|
|||
if resp.status_code == 200:
|
||||
models = resp.json()
|
||||
# Top 40 GGUFs (deep pool for frontend infinite scroll)
|
||||
gguf_ids = [m["id"] for m in models if m.get("id", "").upper().endswith("-GGUF")][
|
||||
:40
|
||||
]
|
||||
gguf_ids = [
|
||||
m["id"] for m in models if m.get("id", "").upper().endswith("-GGUF")
|
||||
][:40]
|
||||
# Top 40 non-GGUF hub models
|
||||
hub_ids = [
|
||||
m["id"] for m in models if not m.get("id", "").upper().endswith("-GGUF")
|
||||
m["id"]
|
||||
for m in models
|
||||
if not m.get("id", "").upper().endswith("-GGUF")
|
||||
][:40]
|
||||
if gguf_ids:
|
||||
self._top_gguf_cache = gguf_ids
|
||||
|
|
@ -272,7 +276,8 @@ class InferenceOrchestrator:
|
|||
"Try a smaller model, lower context length, or close other GPU-heavy apps."
|
||||
)
|
||||
return (
|
||||
f"{message}{suffix} " f"Details: pid={pid}, signal={sig_name}, exitcode={exitcode}."
|
||||
f"{message}{suffix} "
|
||||
f"Details: pid={pid}, signal={sig_name}, exitcode={exitcode}."
|
||||
)
|
||||
|
||||
return f"{message} Details: pid={pid}, exitcode={exitcode}."
|
||||
|
|
@ -355,7 +360,8 @@ class InferenceOrchestrator:
|
|||
)
|
||||
|
||||
raise RuntimeError(
|
||||
f"Timeout waiting for '{expected_type}' response " f"(no activity for {timeout}s)"
|
||||
f"Timeout waiting for '{expected_type}' response "
|
||||
f"(no activity for {timeout}s)"
|
||||
)
|
||||
|
||||
def _drain_queue(self) -> list:
|
||||
|
|
@ -733,9 +739,13 @@ class InferenceOrchestrator:
|
|||
# without re-entering the subprocess.
|
||||
_tpl_info = model_info.get("chat_template_info")
|
||||
if isinstance(_tpl_info, dict):
|
||||
self.models[self.active_model_name]["chat_template_info"] = _tpl_info
|
||||
self.models[self.active_model_name]["chat_template_info"] = (
|
||||
_tpl_info
|
||||
)
|
||||
self.loading_models.discard(model_name)
|
||||
logger.info("Model '%s' loaded successfully in subprocess", model_name)
|
||||
logger.info(
|
||||
"Model '%s' loaded successfully in subprocess", model_name
|
||||
)
|
||||
return True
|
||||
else:
|
||||
error = resp.get("error", "Failed to load model")
|
||||
|
|
@ -1166,7 +1176,9 @@ class InferenceOrchestrator:
|
|||
|
||||
if resp is None:
|
||||
if not self._ensure_subprocess_alive():
|
||||
raise RuntimeError(self._subprocess_crash_message("audio generation"))
|
||||
raise RuntimeError(
|
||||
self._subprocess_crash_message("audio generation")
|
||||
)
|
||||
continue
|
||||
|
||||
rtype = resp.get("type", "")
|
||||
|
|
@ -1258,7 +1270,9 @@ class InferenceOrchestrator:
|
|||
|
||||
# numpy array -> list for mp.Queue serialization
|
||||
audio_data = (
|
||||
audio_array.tolist() if hasattr(audio_array, "tolist") else list(audio_array)
|
||||
audio_array.tolist()
|
||||
if hasattr(audio_array, "tolist")
|
||||
else list(audio_array)
|
||||
)
|
||||
|
||||
cmd = {
|
||||
|
|
@ -1288,7 +1302,10 @@ class InferenceOrchestrator:
|
|||
|
||||
if resp is None:
|
||||
if not self._ensure_subprocess_alive():
|
||||
yield ("Error: " + self._subprocess_crash_message("audio input generation"))
|
||||
yield (
|
||||
"Error: "
|
||||
+ self._subprocess_crash_message("audio input generation")
|
||||
)
|
||||
return
|
||||
continue
|
||||
|
||||
|
|
|
|||
|
|
@ -103,7 +103,9 @@ def _lookup(provider: str, model: str) -> Optional[dict[str, float]]:
|
|||
return None
|
||||
|
||||
|
||||
def calculate_cost(provider: str, model: str, usage: dict[str, Any]) -> dict[str, float]:
|
||||
def calculate_cost(
|
||||
provider: str, model: str, usage: dict[str, Any]
|
||||
) -> dict[str, float]:
|
||||
"""Return a per-turn USD cost breakdown (per-bucket + total).
|
||||
|
||||
Unknown model -> ``priced`` False and USD fields 0.0 (token counts still report).
|
||||
|
|
@ -131,7 +133,8 @@ def calculate_cost(provider: str, model: str, usage: dict[str, Any]) -> dict[str
|
|||
# Clamp >=0 so corrupted payloads can't produce a negative bill.
|
||||
cache_creation = max(0, int(usage.get("cache_creation_input_tokens") or 0))
|
||||
cache_read_native_present = (
|
||||
"cache_read_input_tokens" in usage and usage.get("cache_read_input_tokens") is not None
|
||||
"cache_read_input_tokens" in usage
|
||||
and usage.get("cache_read_input_tokens") is not None
|
||||
)
|
||||
cache_read = max(0, int(usage.get("cache_read_input_tokens") or 0))
|
||||
# Fall back to mirrored prompt_tokens_details only when native
|
||||
|
|
@ -214,10 +217,14 @@ def calculate_cost(provider: str, model: str, usage: dict[str, Any]) -> dict[str
|
|||
if cc_5m + cc_1h == 0 and cache_creation > 0:
|
||||
# No breakdown -- assume default 5m pool.
|
||||
cc_5m = cache_creation
|
||||
out["cache_write_usd"] = (cc_5m / 1_000_000.0) * base * ANTHROPIC_CACHE_5M_WRITE_MULT + (
|
||||
out["cache_write_usd"] = (
|
||||
cc_5m / 1_000_000.0
|
||||
) * base * ANTHROPIC_CACHE_5M_WRITE_MULT + (
|
||||
cc_1h / 1_000_000.0
|
||||
) * base * ANTHROPIC_CACHE_1H_WRITE_MULT
|
||||
out["cache_read_usd"] = (cache_read / 1_000_000.0) * base * ANTHROPIC_CACHE_READ_MULT
|
||||
out["cache_read_usd"] = (
|
||||
(cache_read / 1_000_000.0) * base * ANTHROPIC_CACHE_READ_MULT
|
||||
)
|
||||
# Server-tool surcharges.
|
||||
srv = usage.get("server_tool_use") or {}
|
||||
if isinstance(srv, dict):
|
||||
|
|
@ -234,7 +241,9 @@ def calculate_cost(provider: str, model: str, usage: dict[str, Any]) -> dict[str
|
|||
if cache_read > 0:
|
||||
non_cached_input = max(0, input_tokens - cache_read)
|
||||
out["input_usd"] = (non_cached_input / 1_000_000.0) * base
|
||||
out["cache_read_usd"] = (cache_read / 1_000_000.0) * base * OPENAI_CACHE_READ_MULT
|
||||
out["cache_read_usd"] = (
|
||||
(cache_read / 1_000_000.0) * base * OPENAI_CACHE_READ_MULT
|
||||
)
|
||||
# OpenAI server-tool surcharges arrive under `openai_tool_use`
|
||||
# (normalised by the SSE finaliser from output items).
|
||||
srv = usage.get("openai_tool_use") or {}
|
||||
|
|
|
|||
|
|
@ -92,7 +92,9 @@ def _detect_render_html_tool_start(content: str) -> bool:
|
|||
if not function_match and tool_call_index < 0:
|
||||
return False
|
||||
|
||||
if function_match and (tool_call_index < 0 or function_match.start() < tool_call_index):
|
||||
if function_match and (
|
||||
tool_call_index < 0 or function_match.start() < tool_call_index
|
||||
):
|
||||
return function_match.group(1) == "render_html"
|
||||
|
||||
if tool_call_index >= 0:
|
||||
|
|
@ -182,7 +184,9 @@ def run_safetensors_tool_loop(
|
|||
# Forced first-pass RAG (mirrors the GGUF loop) so doc Qs don't lose to web_search.
|
||||
from core.inference.tools import build_rag_autoinject
|
||||
|
||||
_auto = None if confirm_tool_calls else build_rag_autoinject(conversation, rag_scope)
|
||||
_auto = (
|
||||
None if confirm_tool_calls else build_rag_autoinject(conversation, rag_scope)
|
||||
)
|
||||
if _auto:
|
||||
for _ev in _auto["events"]:
|
||||
yield _ev
|
||||
|
|
@ -201,7 +205,9 @@ def run_safetensors_tool_loop(
|
|||
def _tool_succeeded(tool_name: str) -> bool:
|
||||
key_prefix = f"{tool_name}:"
|
||||
return any(
|
||||
record.executed and not record.is_error and record.key.startswith(key_prefix)
|
||||
record.executed
|
||||
and not record.is_error
|
||||
and record.key.startswith(key_prefix)
|
||||
for record in tool_controller.history
|
||||
)
|
||||
|
||||
|
|
@ -226,7 +232,9 @@ def run_safetensors_tool_loop(
|
|||
final_attempt_done = True
|
||||
active_tools = []
|
||||
|
||||
tool_protocol_active = not final_attempt_done and (unrestricted_tools or bool(active_tools))
|
||||
tool_protocol_active = not final_attempt_done and (
|
||||
unrestricted_tools or bool(active_tools)
|
||||
)
|
||||
tool_xml_signals = TOOL_XML_SIGNALS if tool_protocol_active else ()
|
||||
|
||||
detect_state = _state_buffering
|
||||
|
|
@ -515,11 +523,15 @@ def run_safetensors_tool_loop(
|
|||
conversation.append(assistant_msg)
|
||||
assistant_appended = True
|
||||
else:
|
||||
assistant_msg.setdefault("tool_calls", []).append(decision.as_assistant_tool_call())
|
||||
assistant_msg.setdefault("tool_calls", []).append(
|
||||
decision.as_assistant_tool_call()
|
||||
)
|
||||
|
||||
needs_confirm = bool(confirm_tool_calls)
|
||||
approval_id = new_approval_id() if needs_confirm else ""
|
||||
decision_slot = begin_tool_decision(session_id, approval_id) if needs_confirm else None
|
||||
decision_slot = (
|
||||
begin_tool_decision(session_id, approval_id) if needs_confirm else None
|
||||
)
|
||||
start_event = decision.tool_start_event()
|
||||
start_event["approval_id"] = approval_id
|
||||
start_event["awaiting_confirmation"] = needs_confirm
|
||||
|
|
|
|||
|
|
@ -194,7 +194,11 @@ def parse_tool_calls_from_text(
|
|||
for idx, fm in enumerate(func_starts):
|
||||
func_name = fm.group(1)
|
||||
body_start = fm.end()
|
||||
next_func = func_starts[idx + 1].start() if idx + 1 < len(func_starts) else len(content)
|
||||
next_func = (
|
||||
func_starts[idx + 1].start()
|
||||
if idx + 1 < len(func_starts)
|
||||
else len(content)
|
||||
)
|
||||
end_tag = _TC_END_TAG_RE.search(content[body_start:])
|
||||
if end_tag:
|
||||
body_end = body_start + end_tag.start()
|
||||
|
|
|
|||
|
|
@ -287,7 +287,9 @@ class ToolLoopController:
|
|||
self._restrict_to_allowed = tools is not None
|
||||
self._tools = [copy.deepcopy(dict(tool)) for tool in (tools or [])]
|
||||
self._allowed_tool_names = {
|
||||
name for name in (_tool_name_from_schema(tool) for tool in self._tools) if name
|
||||
name
|
||||
for name in (_tool_name_from_schema(tool) for tool in self._tools)
|
||||
if name
|
||||
}
|
||||
self._auto_heal_tool_calls = auto_heal_tool_calls
|
||||
self._one_shot_tools = one_shot_tools
|
||||
|
|
@ -364,7 +366,9 @@ class ToolLoopController:
|
|||
noop_result = noop,
|
||||
)
|
||||
|
||||
def record_result(self, decision: ToolCallDecision, result: Any) -> ToolCallCompletion:
|
||||
def record_result(
|
||||
self, decision: ToolCallDecision, result: Any
|
||||
) -> ToolCallCompletion:
|
||||
"""Record a real tool execution and return model/frontend payload helpers."""
|
||||
result_text = result if isinstance(result, str) else str(result)
|
||||
failed = is_tool_error(result_text)
|
||||
|
|
|
|||
|
|
@ -125,7 +125,9 @@ _BLOCKED_COMMANDS = (
|
|||
)
|
||||
|
||||
|
||||
_SHELL_SEPARATORS = frozenset({";", "&&", "||", "|", "&", "\n", "(", ")", "`", "{", "}"})
|
||||
_SHELL_SEPARATORS = frozenset(
|
||||
{";", "&&", "||", "|", "&", "\n", "(", ")", "`", "{", "}"}
|
||||
)
|
||||
# Bash keywords starting a new command position (then $cmd, do $cmd, etc.).
|
||||
_SHELL_KEYWORDS_AS_SEP = frozenset({"then", "do", "else", "elif"})
|
||||
# Wrappers whose next non-flag argument is the command Bash will exec.
|
||||
|
|
@ -248,7 +250,9 @@ def _find_blocked_commands(command: str) -> set[str]:
|
|||
tok_lower = token.lower()
|
||||
# Match -c exactly, or combined flags ending in c (e.g. -lc, -xc)
|
||||
is_unix_c = tok_lower == "-c" or (
|
||||
tok_lower.startswith("-") and tok_lower.endswith("c") and not tok_lower.startswith("--")
|
||||
tok_lower.startswith("-")
|
||||
and tok_lower.endswith("c")
|
||||
and not tok_lower.startswith("--")
|
||||
)
|
||||
is_win_c = tok_lower == "/c"
|
||||
if not (is_unix_c or is_win_c) or i < 1 or i + 1 >= len(tokens):
|
||||
|
|
@ -352,11 +356,18 @@ def _sandbox_preexec():
|
|||
except (ValueError, OSError, AttributeError):
|
||||
pass
|
||||
try:
|
||||
_resource.setrlimit(_resource.RLIMIT_FSIZE, (100 * 1024 * 1024, 100 * 1024 * 1024))
|
||||
_resource.setrlimit(
|
||||
_resource.RLIMIT_FSIZE, (100 * 1024 * 1024, 100 * 1024 * 1024)
|
||||
)
|
||||
except (ValueError, OSError):
|
||||
pass
|
||||
try:
|
||||
as_bytes = int(os.environ.get("UNSLOTH_STUDIO_SANDBOX_AS_GB", "8")) * 1024 * 1024 * 1024
|
||||
as_bytes = (
|
||||
int(os.environ.get("UNSLOTH_STUDIO_SANDBOX_AS_GB", "8"))
|
||||
* 1024
|
||||
* 1024
|
||||
* 1024
|
||||
)
|
||||
_resource.setrlimit(_resource.RLIMIT_AS, (as_bytes, as_bytes))
|
||||
except (ValueError, OSError, AttributeError):
|
||||
pass
|
||||
|
|
@ -371,7 +382,9 @@ def _sandbox_preexec():
|
|||
# when the parent's hard cap is below the request.
|
||||
nofile = int(os.environ.get("UNSLOTH_STUDIO_SANDBOX_NOFILE", "16384"))
|
||||
_soft_cur, hard_cur = _resource.getrlimit(_resource.RLIMIT_NOFILE)
|
||||
target = nofile if hard_cur == _resource.RLIM_INFINITY else min(nofile, hard_cur)
|
||||
target = (
|
||||
nofile if hard_cur == _resource.RLIM_INFINITY else min(nofile, hard_cur)
|
||||
)
|
||||
_resource.setrlimit(_resource.RLIMIT_NOFILE, (target, target))
|
||||
except (ValueError, OSError, AttributeError):
|
||||
pass
|
||||
|
|
@ -404,7 +417,9 @@ def _get_project_workdir(session_id: str) -> str | None:
|
|||
from storage.studio_db import ensure_chat_project_workspace
|
||||
project = ensure_chat_project_workspace(project_id)
|
||||
except Exception:
|
||||
logger.warning("Failed to resolve project sandbox for %s", session_id, exc_info = True)
|
||||
logger.warning(
|
||||
"Failed to resolve project sandbox for %s", session_id, exc_info = True
|
||||
)
|
||||
return None
|
||||
if not project:
|
||||
return None
|
||||
|
|
@ -435,7 +450,9 @@ def _get_workdir(session_id: str | None = None) -> str:
|
|||
workdir = project_workdir
|
||||
elif session_id and _SESSION_ID_RE.match(session_id):
|
||||
workdir = os.path.join(sandbox_root, session_id)
|
||||
if not os.path.realpath(workdir).startswith(os.path.realpath(sandbox_root) + os.sep):
|
||||
if not os.path.realpath(workdir).startswith(
|
||||
os.path.realpath(sandbox_root) + os.sep
|
||||
):
|
||||
workdir = os.path.join(sandbox_root, "_invalid")
|
||||
elif session_id:
|
||||
workdir = os.path.join(sandbox_root, "_invalid")
|
||||
|
|
@ -614,7 +631,9 @@ def _mcp_specs_for_server(server: dict, mcp_tools: list[dict]) -> list[dict]:
|
|||
continue
|
||||
# Duplicate tool names would also 400 OpenAI; drop dupes.
|
||||
if name in seen_names:
|
||||
logger.warning("Skipping duplicate MCP tool '%s' on '%s'.", raw_name, display)
|
||||
logger.warning(
|
||||
"Skipping duplicate MCP tool '%s' on '%s'.", raw_name, display
|
||||
)
|
||||
continue
|
||||
seen_names.add(name)
|
||||
specs.append(
|
||||
|
|
@ -623,7 +642,8 @@ def _mcp_specs_for_server(server: dict, mcp_tools: list[dict]) -> list[dict]:
|
|||
"function": {
|
||||
"name": name,
|
||||
"description": f"[{display}] {tool.get('description') or ''}".strip(),
|
||||
"parameters": tool.get("inputSchema") or {"type": "object", "properties": {}},
|
||||
"parameters": tool.get("inputSchema")
|
||||
or {"type": "object", "properties": {}},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
|
@ -643,7 +663,9 @@ async def get_enabled_mcp_tools() -> list[dict]:
|
|||
# server gets re-probed -- and blocks the send for the full timeout -- on
|
||||
# every message.
|
||||
uncached = [
|
||||
s for s in servers if get_cached_tools(s["id"]) is None and not in_failure_cooloff(s["id"])
|
||||
s
|
||||
for s in servers
|
||||
if get_cached_tools(s["id"]) is None and not in_failure_cooloff(s["id"])
|
||||
]
|
||||
if uncached:
|
||||
results = await asyncio.gather(
|
||||
|
|
@ -731,7 +753,9 @@ def execute_tool(
|
|||
``rag_scope``: hidden per-request RAG context the model never sees; consumed
|
||||
by ``search_knowledge_base``.
|
||||
"""
|
||||
logger.info(f"execute_tool: name={name}, session_id={session_id}, timeout={timeout}")
|
||||
logger.info(
|
||||
f"execute_tool: name={name}, session_id={session_id}, timeout={timeout}"
|
||||
)
|
||||
effective_timeout = _EXEC_TIMEOUT if timeout is _TIMEOUT_UNSET else timeout
|
||||
if name == "search_knowledge_base":
|
||||
return _search_knowledge_base(arguments, rag_scope)
|
||||
|
|
@ -765,9 +789,13 @@ def execute_tool(
|
|||
timeout = effective_timeout,
|
||||
)
|
||||
if name == "python":
|
||||
return _python_exec(arguments.get("code", ""), cancel_event, effective_timeout, session_id)
|
||||
return _python_exec(
|
||||
arguments.get("code", ""), cancel_event, effective_timeout, session_id
|
||||
)
|
||||
if name == "terminal":
|
||||
return _bash_exec(arguments.get("command", ""), cancel_event, effective_timeout, session_id)
|
||||
return _bash_exec(
|
||||
arguments.get("command", ""), cancel_event, effective_timeout, session_id
|
||||
)
|
||||
return f"Unknown tool: {name}"
|
||||
|
||||
|
||||
|
|
@ -875,7 +903,9 @@ def _last_user_text(conversation: list[dict]) -> str:
|
|||
return ""
|
||||
|
||||
|
||||
def build_rag_autoinject(conversation: list[dict], rag_scope: dict | None) -> dict | None:
|
||||
def build_rag_autoinject(
|
||||
conversation: list[dict], rag_scope: dict | None
|
||||
) -> dict | None:
|
||||
"""Pre-retrieve the latest user turn; if a hit clears the cosine floor return
|
||||
``{"events": [...], "messages": [...]}`` to splice into the loop, else ``None``.
|
||||
Toggle via ``rag_scope.autoinject`` (else env ``RAG_AUTOINJECT``); floor via
|
||||
|
|
@ -971,7 +1001,9 @@ def build_rag_autoinject(conversation: list[dict], rag_scope: dict | None) -> di
|
|||
"content": text,
|
||||
},
|
||||
]
|
||||
logger.info("RAG auto-inject: %d passage(s) >= %.2f for %r", len(sources), floor, query[:80])
|
||||
logger.info(
|
||||
"RAG auto-inject: %d passage(s) >= %.2f for %r", len(sources), floor, query[:80]
|
||||
)
|
||||
return {"events": events, "messages": messages}
|
||||
|
||||
|
||||
|
|
@ -1138,7 +1170,9 @@ def _fetch_page_text(
|
|||
resp = opener.open(req, timeout = timeout)
|
||||
except _HTTPError as e:
|
||||
if e.code not in (301, 302, 303, 307, 308):
|
||||
return f"Failed to fetch URL: HTTP {e.code} {getattr(e, 'reason', '')}"
|
||||
return (
|
||||
f"Failed to fetch URL: HTTP {e.code} {getattr(e, 'reason', '')}"
|
||||
)
|
||||
location = e.headers.get("Location")
|
||||
if not location:
|
||||
return "Failed to fetch URL: redirect missing Location header."
|
||||
|
|
@ -1398,7 +1432,9 @@ def _check_signal_escape_patterns(code: str):
|
|||
if func_name:
|
||||
if func_name in ("signal.signal", "signal"):
|
||||
if len(node.args) >= 1:
|
||||
if _ast_name_matches(node.args[0], ("SIGALRM", "signal.SIGALRM")):
|
||||
if _ast_name_matches(
|
||||
node.args[0], ("SIGALRM", "signal.SIGALRM")
|
||||
):
|
||||
signal_tampering.append(
|
||||
{
|
||||
"type": "signal_handler_override",
|
||||
|
|
@ -1408,7 +1444,9 @@ def _check_signal_escape_patterns(code: str):
|
|||
)
|
||||
elif func_name in ("signal.setitimer", "setitimer"):
|
||||
if len(node.args) >= 1:
|
||||
if _ast_name_matches(node.args[0], ("ITIMER_REAL", "signal.ITIMER_REAL")):
|
||||
if _ast_name_matches(
|
||||
node.args[0], ("ITIMER_REAL", "signal.ITIMER_REAL")
|
||||
):
|
||||
signal_tampering.append(
|
||||
{
|
||||
"type": "timer_manipulation",
|
||||
|
|
@ -1461,7 +1499,9 @@ def _check_signal_escape_patterns(code: str):
|
|||
else:
|
||||
has_opaque_kwargs = True
|
||||
|
||||
cmd_kw_values = [v for k, v in expanded_kwargs.items() if k in _CMD_KWARGS]
|
||||
cmd_kw_values = [
|
||||
v for k, v in expanded_kwargs.items() if k in _CMD_KWARGS
|
||||
]
|
||||
all_call_args = list(node.args) + cmd_kw_values
|
||||
blocked_in_args = _check_args_for_blocked(all_call_args)
|
||||
|
||||
|
|
@ -1471,7 +1511,9 @@ def _check_signal_escape_patterns(code: str):
|
|||
{
|
||||
"type": "shell_escape_dynamic",
|
||||
"line": node.lineno,
|
||||
"description": (f"{shell_func}() called with dynamic **kwargs"),
|
||||
"description": (
|
||||
f"{shell_func}() called with dynamic **kwargs"
|
||||
),
|
||||
}
|
||||
)
|
||||
elif blocked_in_args:
|
||||
|
|
@ -1502,7 +1544,8 @@ def _check_signal_escape_patterns(code: str):
|
|||
)
|
||||
shell_node = expanded_kwargs.get("shell")
|
||||
shell_safe = shell_node is None or (
|
||||
isinstance(shell_node, ast.Constant) and shell_node.value is False
|
||||
isinstance(shell_node, ast.Constant)
|
||||
and shell_node.value is False
|
||||
)
|
||||
# Dynamic shell-exec args (chr/format/concat bypasses).
|
||||
if (
|
||||
|
|
@ -1515,10 +1558,15 @@ def _check_signal_escape_patterns(code: str):
|
|||
if _extract_string_from_node(n) is not None:
|
||||
return True
|
||||
if isinstance(n, (ast.List, ast.Tuple)):
|
||||
return all(_extract_string_from_node(e) is not None for e in n.elts)
|
||||
return all(
|
||||
_extract_string_from_node(e) is not None
|
||||
for e in n.elts
|
||||
)
|
||||
return False
|
||||
|
||||
has_non_literal = any(not _is_safe_literal(a) for a in all_call_args)
|
||||
has_non_literal = any(
|
||||
not _is_safe_literal(a) for a in all_call_args
|
||||
)
|
||||
if has_non_literal:
|
||||
shell_escapes.append(
|
||||
{
|
||||
|
|
@ -1775,7 +1823,9 @@ def _check_signal_escape_patterns(code: str):
|
|||
"/etc/sudoers",
|
||||
"/etc/ssh/",
|
||||
)
|
||||
_SENSITIVE_FILE_RE = re.compile(r"^/proc/(?:self|\d+)/(?:environ|cmdline|task/\d+/environ)$")
|
||||
_SENSITIVE_FILE_RE = re.compile(
|
||||
r"^/proc/(?:self|\d+)/(?:environ|cmdline|task/\d+/environ)$"
|
||||
)
|
||||
|
||||
def _normalize_host(host: str) -> str:
|
||||
if not host:
|
||||
|
|
@ -1818,9 +1868,15 @@ def _check_signal_escape_patterns(code: str):
|
|||
return True
|
||||
if kw.arg == "data":
|
||||
v = kw.value
|
||||
if isinstance(v, ast.Call) and isinstance(v.func, ast.Name) and v.func.id == "open":
|
||||
if (
|
||||
isinstance(v, ast.Call)
|
||||
and isinstance(v.func, ast.Name)
|
||||
and v.func.id == "open"
|
||||
):
|
||||
return True
|
||||
if isinstance(v, ast.Constant) and isinstance(v.value, (bytes, bytearray)):
|
||||
if isinstance(v, ast.Constant) and isinstance(
|
||||
v.value, (bytes, bytearray)
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
@ -1951,7 +2007,9 @@ def _check_signal_escape_patterns(code: str):
|
|||
"""Whether the path argument resolves to a sandbox-local literal."""
|
||||
if node is None:
|
||||
return False
|
||||
if isinstance(node, ast.Constant) and isinstance(node.value, (bytes, bytearray)):
|
||||
if isinstance(node, ast.Constant) and isinstance(
|
||||
node.value, (bytes, bytearray)
|
||||
):
|
||||
return True # inline bytes, no file access
|
||||
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
||||
return _is_safe_relative_path(node.value)
|
||||
|
|
@ -2037,7 +2095,11 @@ def _check_signal_escape_patterns(code: str):
|
|||
)
|
||||
|
||||
# Direct sock.connect((host, port)) bypasses the FQ-prefix branch.
|
||||
if isinstance(node.func, ast.Attribute) and node.func.attr == "connect" and node.args:
|
||||
if (
|
||||
isinstance(node.func, ast.Attribute)
|
||||
and node.func.attr == "connect"
|
||||
and node.args
|
||||
):
|
||||
a0 = node.args[0]
|
||||
host_lit = None
|
||||
if isinstance(a0, ast.Tuple) and a0.elts:
|
||||
|
|
@ -2074,7 +2136,9 @@ def _check_signal_escape_patterns(code: str):
|
|||
{
|
||||
"type": "upload_blocked",
|
||||
"line": getattr(node, "lineno", -1),
|
||||
"description": ("Blocked: file upload disallowed in sandbox"),
|
||||
"description": (
|
||||
"Blocked: file upload disallowed in sandbox"
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -2175,18 +2239,28 @@ def _check_code_safety(code: str) -> str | None:
|
|||
if info.get("error"):
|
||||
return None
|
||||
|
||||
reasons = [item.get("description", "") for item in info.get("signal_tampering", [])]
|
||||
shell_reasons = [item.get("description", "") for item in info.get("shell_escapes", [])]
|
||||
reasons = [
|
||||
item.get("description", "") for item in info.get("signal_tampering", [])
|
||||
]
|
||||
shell_reasons = [
|
||||
item.get("description", "") for item in info.get("shell_escapes", [])
|
||||
]
|
||||
exception_reasons = [
|
||||
item.get("description", "") for item in info.get("exception_catching", [])
|
||||
]
|
||||
network_reasons = [item.get("description", "") for item in info.get("network_calls", [])]
|
||||
network_reasons = [
|
||||
item.get("description", "") for item in info.get("network_calls", [])
|
||||
]
|
||||
file_reasons = [
|
||||
item.get("description", "") for item in info.get("sensitive_file_reads", [])
|
||||
]
|
||||
all_reasons = [
|
||||
r
|
||||
for r in reasons + shell_reasons + exception_reasons + network_reasons + file_reasons
|
||||
for r in reasons
|
||||
+ shell_reasons
|
||||
+ exception_reasons
|
||||
+ network_reasons
|
||||
+ file_reasons
|
||||
if r
|
||||
]
|
||||
if all_reasons:
|
||||
|
|
@ -2266,7 +2340,9 @@ def _python_exec(
|
|||
except OSError:
|
||||
pass
|
||||
try:
|
||||
fd, tmp_path = tempfile.mkstemp(suffix = ".py", prefix = "studio_exec_", dir = workdir)
|
||||
fd, tmp_path = tempfile.mkstemp(
|
||||
suffix = ".py", prefix = "studio_exec_", dir = workdir
|
||||
)
|
||||
with os.fdopen(fd, "w") as f:
|
||||
f.write(code)
|
||||
|
||||
|
|
|
|||
|
|
@ -89,7 +89,9 @@ def _build_model_config(config: dict):
|
|||
return mc
|
||||
|
||||
|
||||
def _get_hf_download_state(model_names: list[str] | None = None) -> tuple[int, bool] | None:
|
||||
def _get_hf_download_state(
|
||||
model_names: list[str] | None = None,
|
||||
) -> tuple[int, bool] | None:
|
||||
"""Return (total_bytes, has_incomplete) for the HF Hub cache, or None on error.
|
||||
|
||||
With *model_names*, only those models' ``blobs/`` dirs are checked (faster);
|
||||
|
|
@ -240,10 +242,14 @@ def _handle_load(backend, config: dict, resp_queue: Any) -> None:
|
|||
adapter_cfg = json.load(f)
|
||||
training_method = adapter_cfg.get("unsloth_training_method")
|
||||
if training_method == "lora" and load_in_4bit:
|
||||
logger.info("adapter_config.json says lora — setting load_in_4bit=False")
|
||||
logger.info(
|
||||
"adapter_config.json says lora — setting load_in_4bit=False"
|
||||
)
|
||||
load_in_4bit = False
|
||||
elif training_method == "qlora" and not load_in_4bit:
|
||||
logger.info("adapter_config.json says qlora — setting load_in_4bit=True")
|
||||
logger.info(
|
||||
"adapter_config.json says qlora — setting load_in_4bit=True"
|
||||
)
|
||||
load_in_4bit = True
|
||||
elif not training_method:
|
||||
if (
|
||||
|
|
@ -513,7 +519,9 @@ def _handle_generate_audio(backend, cmd: dict, resp_queue: Any) -> None:
|
|||
)
|
||||
|
||||
|
||||
def _handle_generate_audio_input(backend, cmd: dict, resp_queue: Any, cancel_event) -> None:
|
||||
def _handle_generate_audio_input(
|
||||
backend, cmd: dict, resp_queue: Any, cancel_event
|
||||
) -> None:
|
||||
"""Handle audio input generation (ASR/Whisper) — streams text tokens back."""
|
||||
request_id = cmd.get("request_id", "")
|
||||
|
||||
|
|
@ -548,7 +556,9 @@ def _handle_generate_audio_input(backend, cmd: dict, resp_queue: Any, cancel_eve
|
|||
|
||||
for text_chunk in generator:
|
||||
if cancel_event.is_set():
|
||||
logger.info("Audio input generation cancelled for request %s", request_id)
|
||||
logger.info(
|
||||
"Audio input generation cancelled for request %s", request_id
|
||||
)
|
||||
break
|
||||
|
||||
_send_response(
|
||||
|
|
@ -615,7 +625,9 @@ def _handle_unload(backend, cmd: dict, resp_queue: Any) -> None:
|
|||
)
|
||||
|
||||
|
||||
def run_inference_process(*, cmd_queue: Any, resp_queue: Any, cancel_event, config: dict) -> None:
|
||||
def run_inference_process(
|
||||
*, cmd_queue: Any, resp_queue: Any, cancel_event, config: dict
|
||||
) -> None:
|
||||
"""Subprocess entrypoint. Persistent — runs the command loop until shutdown.
|
||||
|
||||
Args:
|
||||
|
|
@ -625,7 +637,9 @@ def run_inference_process(*, cmd_queue: Any, resp_queue: Any, cancel_event, conf
|
|||
config: Initial configuration dict with model info.
|
||||
"""
|
||||
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
||||
os.environ["PYTHONWARNINGS"] = "ignore" # Suppress warnings at C-level before imports
|
||||
os.environ["PYTHONWARNINGS"] = (
|
||||
"ignore" # Suppress warnings at C-level before imports
|
||||
)
|
||||
|
||||
if config.get("disable_xet"):
|
||||
os.environ["HF_HUB_DISABLE_XET"] = "1"
|
||||
|
|
@ -930,7 +944,9 @@ def run_inference_process(*, cmd_queue: Any, resp_queue: Any, cancel_event, conf
|
|||
)
|
||||
|
||||
except Exception as exc:
|
||||
logger.error("Error handling command '%s': %s", cmd_type, exc, exc_info = True)
|
||||
logger.error(
|
||||
"Error handling command '%s': %s", cmd_type, exc, exc_info = True
|
||||
)
|
||||
_send_response(
|
||||
resp_queue,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -26,14 +26,18 @@ def vision_endpoint() -> tuple[str, str] | None:
|
|||
try:
|
||||
from routes.inference import get_llama_cpp_backend
|
||||
backend = get_llama_cpp_backend()
|
||||
if getattr(backend, "is_loaded", False) and getattr(backend, "is_vision", False):
|
||||
if getattr(backend, "is_loaded", False) and getattr(
|
||||
backend, "is_vision", False
|
||||
):
|
||||
return backend.base_url, "local"
|
||||
except Exception: # noqa: BLE001 - never let discovery break ingestion
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def _caption_one(base_url: str, model: str, image_bytes: bytes, timeout: float) -> str | None:
|
||||
def _caption_one(
|
||||
base_url: str, model: str, image_bytes: bytes, timeout: float
|
||||
) -> str | None:
|
||||
import httpx
|
||||
|
||||
data_url = "data:image/png;base64," + base64.b64encode(image_bytes).decode("ascii")
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ class Chunk:
|
|||
page_char_end: int
|
||||
|
||||
|
||||
def _split(text: str, seps: tuple[str, ...], max_tokens: int, count: TokenCounter) -> list[str]:
|
||||
def _split(
|
||||
text: str, seps: tuple[str, ...], max_tokens: int, count: TokenCounter
|
||||
) -> list[str]:
|
||||
"""Recursively split into pieces each <= max_tokens (best effort). Pieces
|
||||
rejoin to ``text`` exactly, so offsets are a running length."""
|
||||
if count(text) <= max_tokens:
|
||||
|
|
@ -41,7 +43,9 @@ def _split(text: str, seps: tuple[str, ...], max_tokens: int, count: TokenCounte
|
|||
out: list[str] = []
|
||||
for p in parts:
|
||||
out.extend(
|
||||
[p] if count(p) <= max_tokens else _split(p, seps[i + 1 :], max_tokens, count)
|
||||
[p]
|
||||
if count(p) <= max_tokens
|
||||
else _split(p, seps[i + 1 :], max_tokens, count)
|
||||
)
|
||||
return [p for p in out if p]
|
||||
n = max(1, max_tokens * 4)
|
||||
|
|
@ -49,7 +53,11 @@ def _split(text: str, seps: tuple[str, ...], max_tokens: int, count: TokenCounte
|
|||
|
||||
|
||||
def _merge(
|
||||
pieces: list[str], starts: list[int], max_tokens: int, overlap: int, count: TokenCounter
|
||||
pieces: list[str],
|
||||
starts: list[int],
|
||||
max_tokens: int,
|
||||
overlap: int,
|
||||
count: TokenCounter,
|
||||
) -> list[tuple[str, int, int]]:
|
||||
"""Greedy-merge pieces into <= max_tokens chunks with token overlap.
|
||||
``starts[i]`` is ``pieces[i]``'s page char offset; returns
|
||||
|
|
@ -106,7 +114,9 @@ def chunk_pages(
|
|||
for piece in pieces:
|
||||
starts.append(cursor)
|
||||
cursor += len(piece)
|
||||
for text, char_start, char_end in _merge(pieces, starts, max_tokens, overlap, count):
|
||||
for text, char_start, char_end in _merge(
|
||||
pieces, starts, max_tokens, overlap, count
|
||||
):
|
||||
out.append(
|
||||
Chunk(
|
||||
text = text,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ CAPTION_TIMEOUT_S = float(os.environ.get("RAG_CAPTION_TIMEOUT_S", "30"))
|
|||
EMBED_BACKEND = os.environ.get("RAG_EMBED_BACKEND", "auto")
|
||||
# llama-server backend only. F16 over Q8_0: faster (no per-block dequant for this
|
||||
# tiny model) and exact vs fp32, for ~30MB more on disk.
|
||||
EMBED_GGUF_REPO = os.environ.get("RAG_EMBED_GGUF_REPO", "unsloth/bge-small-en-v1.5-GGUF")
|
||||
EMBED_GGUF_REPO = os.environ.get(
|
||||
"RAG_EMBED_GGUF_REPO", "unsloth/bge-small-en-v1.5-GGUF"
|
||||
)
|
||||
EMBED_GGUF_VARIANT = os.environ.get("RAG_EMBED_GGUF_VARIANT", "F16")
|
||||
EMBED_DEVICE = os.environ.get("RAG_EMBED_DEVICE", "auto") # "auto" | "gpu" | "cpu"
|
||||
EMBED_HOST = os.environ.get("RAG_EMBED_HOST", "127.0.0.1")
|
||||
|
|
|
|||
|
|
@ -123,7 +123,9 @@ class LlamaServerBackend:
|
|||
|
||||
repo = config.EMBED_GGUF_REPO
|
||||
token = os.environ.get("HF_TOKEN") or None
|
||||
files = [f for f in list_repo_files(repo, token = token) if f.lower().endswith(".gguf")]
|
||||
files = [
|
||||
f for f in list_repo_files(repo, token = token) if f.lower().endswith(".gguf")
|
||||
]
|
||||
files = [f for f in files if "mmproj" not in f.lower()]
|
||||
if not files:
|
||||
raise RuntimeError(f"no .gguf file found in embedder repo {repo!r}")
|
||||
|
|
@ -161,7 +163,9 @@ class LlamaServerBackend:
|
|||
gpus = LlamaCppBackend._get_gpu_free_memory() # [(idx, free_mib)], honors CVD
|
||||
return any(free >= LlamaServerBackend._MIN_GPU_FREE_MIB for _, free in gpus)
|
||||
|
||||
def _build_cmd(self, binary: str, model_path: str, port: int, *, use_gpu: bool) -> list[str]:
|
||||
def _build_cmd(
|
||||
self, binary: str, model_path: str, port: int, *, use_gpu: bool
|
||||
) -> list[str]:
|
||||
# No --embd-normalize (not in every build; we normalize in Python to match
|
||||
# the ST path). --fit off: don't auto-resize ctx/offload to device memory.
|
||||
cmd = [
|
||||
|
|
@ -204,8 +208,12 @@ class LlamaServerBackend:
|
|||
arch = platform.machine()
|
||||
lib_dirs = [binary_dir]
|
||||
for pattern in (
|
||||
os.path.join(sys.prefix, "lib", "python*", "site-packages", "nvidia", "cu*", "lib"),
|
||||
os.path.join(sys.prefix, "lib", "python*", "site-packages", "nvidia", "cudnn", "lib"),
|
||||
os.path.join(
|
||||
sys.prefix, "lib", "python*", "site-packages", "nvidia", "cu*", "lib"
|
||||
),
|
||||
os.path.join(
|
||||
sys.prefix, "lib", "python*", "site-packages", "nvidia", "cudnn", "lib"
|
||||
),
|
||||
):
|
||||
lib_dirs.extend(d for d in glob.glob(pattern) if os.path.isdir(d))
|
||||
for cuda_lib in (
|
||||
|
|
@ -382,7 +390,9 @@ class LlamaServerBackend:
|
|||
raise RuntimeError(
|
||||
f"llama-server embedder POST {path} -> {e.response.status_code}: {body}"
|
||||
) from e
|
||||
raise RuntimeError(f"llama-server embedder POST {path} failed after retry") from last_exc
|
||||
raise RuntimeError(
|
||||
f"llama-server embedder POST {path} failed after retry"
|
||||
) from last_exc
|
||||
|
||||
def encode(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -108,7 +108,9 @@ def _run(
|
|||
stored_path, max_figures = config.CAPTION_MAX_IMAGES
|
||||
)
|
||||
except Exception:
|
||||
logger.warning("figure rendering failed for job %s", job_id, exc_info = True)
|
||||
logger.warning(
|
||||
"figure rendering failed for job %s", job_id, exc_info = True
|
||||
)
|
||||
figures = []
|
||||
if figures:
|
||||
_progress(conn, job_id, "captioning", 0.2)
|
||||
|
|
@ -139,12 +141,16 @@ def _run(
|
|||
from . import locators
|
||||
regions = locators.pdf_regions_for_chunks(stored_path, pages, chunks)
|
||||
except Exception:
|
||||
logger.warning("pdf region location failed for job %s", job_id, exc_info = True)
|
||||
logger.warning(
|
||||
"pdf region location failed for job %s", job_id, exc_info = True
|
||||
)
|
||||
regions = None
|
||||
|
||||
_progress(conn, job_id, "storing", 0.9)
|
||||
store.add_chunks(conn, scope, document_id, chunks, vectors, regions)
|
||||
store.set_document_status(conn, document_id, "completed", num_chunks = len(chunks))
|
||||
store.set_document_status(
|
||||
conn, document_id, "completed", num_chunks = len(chunks)
|
||||
)
|
||||
|
||||
_set_job(conn, job_id, status = "completed", stage = "done", progress = 1.0)
|
||||
_emit(job_id, {"type": "complete", "num_chunks": len(chunks)})
|
||||
|
|
@ -267,7 +273,9 @@ def get_job_status(job_id: str) -> dict | None:
|
|||
"""Read the persisted ingestion job row (status / stage / progress / error)."""
|
||||
conn = rag_db.get_connection()
|
||||
try:
|
||||
row = conn.execute("SELECT * FROM ingestion_jobs WHERE id=?", (job_id,)).fetchone()
|
||||
row = conn.execute(
|
||||
"SELECT * FROM ingestion_jobs WHERE id=?", (job_id,)
|
||||
).fetchone()
|
||||
return dict(row) if row else None
|
||||
finally:
|
||||
conn.close()
|
||||
|
|
|
|||
|
|
@ -118,7 +118,9 @@ def _rects_from_words(page_words: list, indices: list[int], pw: float, ph: float
|
|||
return out
|
||||
|
||||
|
||||
def _regions_for_match(doc: Any, page_text: str, match: LocatorMatch) -> list[dict[str, Any]]:
|
||||
def _regions_for_match(
|
||||
doc: Any, page_text: str, match: LocatorMatch
|
||||
) -> list[dict[str, Any]]:
|
||||
try:
|
||||
if match.page_index < 0 or match.page_index >= len(doc):
|
||||
return []
|
||||
|
|
@ -145,7 +147,9 @@ def _regions_for_match(doc: Any, page_text: str, match: LocatorMatch) -> list[di
|
|||
return []
|
||||
|
||||
|
||||
def pdf_regions_for_chunks(pdf_path: Path, pages: list, chunks: list) -> list[list[dict[str, Any]]]:
|
||||
def pdf_regions_for_chunks(
|
||||
pdf_path: Path, pages: list, chunks: list
|
||||
) -> list[list[dict[str, Any]]]:
|
||||
"""Region rects per chunk (parallel to ``chunks``), keyed off each chunk's
|
||||
``source_page_index`` / ``page_char_start`` / ``page_char_end``. Non-PDFs and
|
||||
failures yield [], never an exception."""
|
||||
|
|
|
|||
|
|
@ -27,7 +27,10 @@ def retrieve_lexical(
|
|||
k: int | None = None,
|
||||
) -> list[Hit]:
|
||||
k = k or config.TOP_K_LEXICAL
|
||||
return [Hit(cid, s, lexical_score = s) for cid, s in store.search_lexical(conn, scope, query, k)]
|
||||
return [
|
||||
Hit(cid, s, lexical_score = s)
|
||||
for cid, s in store.search_lexical(conn, scope, query, k)
|
||||
]
|
||||
|
||||
|
||||
def retrieve_dense(
|
||||
|
|
@ -40,7 +43,9 @@ def retrieve_dense(
|
|||
) -> list[Hit]:
|
||||
k = k or config.TOP_K_DENSE
|
||||
vec = embeddings.encode([query], model_name = model_name, normalize = True)[0]
|
||||
return [Hit(cid, s, dense_score = s) for cid, s in store.search_dense(conn, scope, vec, k)]
|
||||
return [
|
||||
Hit(cid, s, dense_score = s) for cid, s in store.search_dense(conn, scope, vec, k)
|
||||
]
|
||||
|
||||
|
||||
def _rrf(rankings: list[list[Hit]], rrf_k: int, top_k: int) -> list[Hit]:
|
||||
|
|
@ -48,13 +53,19 @@ def _rrf(rankings: list[list[Hit]], rrf_k: int, top_k: int) -> list[Hit]:
|
|||
best: dict[str, Hit] = {}
|
||||
for ranking in rankings:
|
||||
for rank, hit in enumerate(ranking):
|
||||
fused[hit.chunk_id] = fused.get(hit.chunk_id, 0.0) + 1.0 / (rrf_k + rank + 1)
|
||||
fused[hit.chunk_id] = fused.get(hit.chunk_id, 0.0) + 1.0 / (
|
||||
rrf_k + rank + 1
|
||||
)
|
||||
cur = best.get(hit.chunk_id)
|
||||
if cur is None:
|
||||
best[hit.chunk_id] = Hit(hit.chunk_id, 0.0, hit.lexical_score, hit.dense_score)
|
||||
best[hit.chunk_id] = Hit(
|
||||
hit.chunk_id, 0.0, hit.lexical_score, hit.dense_score
|
||||
)
|
||||
else:
|
||||
cur.lexical_score = (
|
||||
cur.lexical_score if cur.lexical_score is not None else hit.lexical_score
|
||||
cur.lexical_score
|
||||
if cur.lexical_score is not None
|
||||
else hit.lexical_score
|
||||
)
|
||||
cur.dense_score = (
|
||||
cur.dense_score if cur.dense_score is not None else hit.dense_score
|
||||
|
|
@ -85,7 +96,9 @@ def retrieve_hybrid(
|
|||
if mode == "dense":
|
||||
return retrieve_dense(conn, scope, query, k, model_name = model_name)
|
||||
lexical = retrieve_lexical(conn, scope, query, config.TOP_K_LEXICAL)
|
||||
dense = retrieve_dense(conn, scope, query, config.TOP_K_DENSE, model_name = model_name)
|
||||
dense = retrieve_dense(
|
||||
conn, scope, query, config.TOP_K_DENSE, model_name = model_name
|
||||
)
|
||||
return _rrf([lexical, dense], config.RRF_K, k)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -89,7 +89,10 @@ def delete_kb(conn: sqlite3.Connection, kb_id: str) -> None:
|
|||
"""Delete a knowledge base and every document (+ chunks) under it."""
|
||||
scope = kb_scope(kb_id)
|
||||
doc_ids = [
|
||||
r["id"] for r in conn.execute("SELECT id FROM documents WHERE scope=?", (scope,)).fetchall()
|
||||
r["id"]
|
||||
for r in conn.execute(
|
||||
"SELECT id FROM documents WHERE scope=?", (scope,)
|
||||
).fetchall()
|
||||
]
|
||||
for doc_id in doc_ids:
|
||||
delete_document(conn, doc_id)
|
||||
|
|
@ -170,7 +173,9 @@ def document_by_hash(conn: sqlite3.Connection, scope: str, sha256: str) -> str |
|
|||
return row["id"] if row else None
|
||||
|
||||
|
||||
def failed_documents_by_hash(conn: sqlite3.Connection, scope: str, sha256: str) -> list[dict]:
|
||||
def failed_documents_by_hash(
|
||||
conn: sqlite3.Connection, scope: str, sha256: str
|
||||
) -> list[dict]:
|
||||
rows = conn.execute(
|
||||
"SELECT id, stored_path FROM documents WHERE scope=? AND sha256=? AND status='failed'",
|
||||
(scope, sha256),
|
||||
|
|
|
|||
|
|
@ -167,10 +167,14 @@ def search_for_autoinject(
|
|||
mode = mode,
|
||||
)
|
||||
strong = [
|
||||
h for h in hits if h.dense_score is not None and h.dense_score >= min_dense_score
|
||||
h
|
||||
for h in hits
|
||||
if h.dense_score is not None and h.dense_score >= min_dense_score
|
||||
][:k]
|
||||
if not strong and hits and mode == "lexical":
|
||||
probe = retrieval.retrieve_dense(conn, scope, query, 1, model_name = model_name)
|
||||
probe = retrieval.retrieve_dense(
|
||||
conn, scope, query, 1, model_name = model_name
|
||||
)
|
||||
if (
|
||||
probe
|
||||
and probe[0].dense_score is not None
|
||||
|
|
|
|||
|
|
@ -83,7 +83,9 @@ def parse_tool_calls_from_text(content: str) -> list[dict]:
|
|||
},
|
||||
}
|
||||
if isinstance(tc["function"]["arguments"], dict):
|
||||
tc["function"]["arguments"] = json.dumps(tc["function"]["arguments"])
|
||||
tc["function"]["arguments"] = json.dumps(
|
||||
tc["function"]["arguments"]
|
||||
)
|
||||
tool_calls.append(tc)
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
pass
|
||||
|
|
@ -99,7 +101,11 @@ def parse_tool_calls_from_text(content: str) -> list[dict]:
|
|||
func_name = fm.group(1)
|
||||
body_start = fm.end()
|
||||
# Boundaries: next <function= tag or </tool_call>
|
||||
next_func = func_starts[idx + 1].start() if idx + 1 < len(func_starts) else len(content)
|
||||
next_func = (
|
||||
func_starts[idx + 1].start()
|
||||
if idx + 1 < len(func_starts)
|
||||
else len(content)
|
||||
)
|
||||
end_tag = _TC_END_TAG_RE.search(content[body_start:])
|
||||
if end_tag:
|
||||
body_end = body_start + end_tag.start()
|
||||
|
|
|
|||
|
|
@ -159,7 +159,9 @@ def prepare_s3_dataset_download(
|
|||
bucket/prefix contains no supported dataset files.
|
||||
"""
|
||||
if not boto3_available():
|
||||
raise RuntimeError("S3 dataset loading requires boto3. Install it with: pip install boto3")
|
||||
raise RuntimeError(
|
||||
"S3 dataset loading requires boto3. Install it with: pip install boto3"
|
||||
)
|
||||
|
||||
bucket = s3_config.get("bucket")
|
||||
if not bucket:
|
||||
|
|
@ -193,7 +195,9 @@ def prepare_s3_dataset_download(
|
|||
local_path = _unique_local_path(target_dir, filename, used_paths)
|
||||
download_kwargs = {}
|
||||
if cancel_callback is not None:
|
||||
download_kwargs["Callback"] = lambda _bytes: _raise_if_cancelled(cancel_callback)
|
||||
download_kwargs["Callback"] = lambda _bytes: _raise_if_cancelled(
|
||||
cancel_callback
|
||||
)
|
||||
client.download_file(bucket, key, local_path, **download_kwargs)
|
||||
_raise_if_cancelled(cancel_callback)
|
||||
local_files.append(local_path)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -42,7 +42,9 @@ _HF_TMP_CHECKPOINT_RE = re.compile(r"^tmp-checkpoint-\d+$")
|
|||
|
||||
def _sanitize_db_config(config: dict[str, Any]) -> dict[str, Any]:
|
||||
db_config = {
|
||||
k: v for k, v in config.items() if k not in {"hf_token", "wandb_token", "s3_config"}
|
||||
k: v
|
||||
for k, v in config.items()
|
||||
if k not in {"hf_token", "wandb_token", "s3_config"}
|
||||
}
|
||||
s3_config = config.get("s3_config")
|
||||
if hasattr(s3_config, "model_dump"):
|
||||
|
|
@ -202,7 +204,9 @@ class TrainingBackend:
|
|||
if self._pump_thread is not None and self._pump_thread.is_alive():
|
||||
self._pump_thread.join(timeout = 5.0)
|
||||
if self._pump_thread.is_alive():
|
||||
logger.warning("Previous pump thread did not exit within 5s — refusing to start")
|
||||
logger.warning(
|
||||
"Previous pump thread did not exit within 5s — refusing to start"
|
||||
)
|
||||
return False
|
||||
self._pump_thread = None
|
||||
|
||||
|
|
@ -254,7 +258,9 @@ class TrainingBackend:
|
|||
"train_on_completions": kwargs.get("train_on_completions", False),
|
||||
"finetune_vision_layers": kwargs.get("finetune_vision_layers", True),
|
||||
"finetune_language_layers": kwargs.get("finetune_language_layers", True),
|
||||
"finetune_attention_modules": kwargs.get("finetune_attention_modules", True),
|
||||
"finetune_attention_modules": kwargs.get(
|
||||
"finetune_attention_modules", True
|
||||
),
|
||||
"finetune_mlp_modules": kwargs.get("finetune_mlp_modules", True),
|
||||
"enable_wandb": kwargs.get("enable_wandb", False),
|
||||
"wandb_token": kwargs.get("wandb_token"),
|
||||
|
|
@ -367,7 +373,9 @@ class TrainingBackend:
|
|||
pass
|
||||
# Update progress immediately for responsive UI.
|
||||
self._progress.status_message = (
|
||||
"Stopping training and saving checkpoint..." if save else "Cancelling training..."
|
||||
"Stopping training and saving checkpoint..."
|
||||
if save
|
||||
else "Cancelling training..."
|
||||
)
|
||||
return True
|
||||
|
||||
|
|
@ -375,7 +383,9 @@ class TrainingBackend:
|
|||
"""Force-kill the training subprocess so state can be reset immediately."""
|
||||
with self._lock:
|
||||
if self._proc is not None and self._proc.is_alive():
|
||||
logger.info("Force-terminating training subprocess (pid=%s)", self._proc.pid)
|
||||
logger.info(
|
||||
"Force-terminating training subprocess (pid=%s)", self._proc.pid
|
||||
)
|
||||
self._proc.terminate()
|
||||
proc = self._proc
|
||||
cancelled = self._cancel_requested
|
||||
|
|
@ -529,7 +539,8 @@ class TrainingBackend:
|
|||
else:
|
||||
self._progress.is_training = False
|
||||
self._progress.error = (
|
||||
self._progress.error or "Training process exited unexpectedly"
|
||||
self._progress.error
|
||||
or "Training process exited unexpectedly"
|
||||
)
|
||||
|
||||
self._ensure_db_run_created()
|
||||
|
|
@ -563,7 +574,9 @@ class TrainingBackend:
|
|||
except (TypeError, ValueError):
|
||||
logger.debug("Could not convert loss to float: %s", _raw_loss)
|
||||
_safe_loss = None
|
||||
_loss_is_nonfinite = _safe_loss is not None and not math.isfinite(_safe_loss)
|
||||
_loss_is_nonfinite = _safe_loss is not None and not math.isfinite(
|
||||
_safe_loss
|
||||
)
|
||||
if _loss_is_nonfinite:
|
||||
# Drop the value rather than laundering it back to the last
|
||||
# finite loss; clients see loss=None at this step so the NaN
|
||||
|
|
@ -579,7 +592,9 @@ class TrainingBackend:
|
|||
try:
|
||||
_safe_lr = float(_raw_lr) if _raw_lr is not None else None
|
||||
except (TypeError, ValueError):
|
||||
logger.debug("Could not convert learning_rate to float: %s", _raw_lr)
|
||||
logger.debug(
|
||||
"Could not convert learning_rate to float: %s", _raw_lr
|
||||
)
|
||||
_safe_lr = None
|
||||
if _safe_lr is not None and not math.isfinite(_safe_lr):
|
||||
_safe_lr = None
|
||||
|
|
@ -591,7 +606,9 @@ class TrainingBackend:
|
|||
self._progress.loss = None
|
||||
if _safe_lr is not None:
|
||||
self._progress.learning_rate = _safe_lr
|
||||
self._progress.total_steps = event.get("total_steps", self._progress.total_steps)
|
||||
self._progress.total_steps = event.get(
|
||||
"total_steps", self._progress.total_steps
|
||||
)
|
||||
self._progress.elapsed_seconds = event.get("elapsed_seconds")
|
||||
self._progress.eta_seconds = event.get("eta_seconds")
|
||||
self._progress.grad_norm = event.get("grad_norm")
|
||||
|
|
@ -635,7 +652,9 @@ class TrainingBackend:
|
|||
try:
|
||||
eval_loss = float(eval_loss)
|
||||
except (TypeError, ValueError):
|
||||
logger.debug("Could not convert eval_loss to float: %s", eval_loss)
|
||||
logger.debug(
|
||||
"Could not convert eval_loss to float: %s", eval_loss
|
||||
)
|
||||
eval_loss = None
|
||||
if step > 0 and eval_loss is not None and math.isfinite(eval_loss):
|
||||
self.eval_loss_history.append(eval_loss)
|
||||
|
|
@ -665,9 +684,12 @@ class TrainingBackend:
|
|||
"job_id": self.current_job_id,
|
||||
"model_name": self._db_config["model_name"],
|
||||
"dataset_name": self._db_config.get("hf_dataset")
|
||||
or next(iter(self._db_config.get("local_datasets") or []), "unknown"),
|
||||
or next(
|
||||
iter(self._db_config.get("local_datasets") or []), "unknown"
|
||||
),
|
||||
"config_json": _json.dumps(self._db_config),
|
||||
"started_at": self._db_started_at or datetime.now(timezone.utc).isoformat(),
|
||||
"started_at": self._db_started_at
|
||||
or datetime.now(timezone.utc).isoformat(),
|
||||
"total_steps": event.get("total_steps"),
|
||||
}
|
||||
elif (
|
||||
|
|
@ -745,7 +767,9 @@ class TrainingBackend:
|
|||
elif db_action == "update_total_steps":
|
||||
try:
|
||||
from storage.studio_db import update_run_total_steps
|
||||
update_run_total_steps(db_action_kwargs["job_id"], db_action_kwargs["total_steps"])
|
||||
update_run_total_steps(
|
||||
db_action_kwargs["job_id"], db_action_kwargs["total_steps"]
|
||||
)
|
||||
self._db_total_steps_set = True
|
||||
except Exception:
|
||||
logger.warning("Failed to update total_steps in DB", exc_info = True)
|
||||
|
|
@ -772,12 +796,15 @@ class TrainingBackend:
|
|||
model_name = self._db_config["model_name"],
|
||||
dataset_name = dataset_name,
|
||||
config_json = _json.dumps(self._db_config),
|
||||
started_at = self._db_started_at or datetime.now(timezone.utc).isoformat(),
|
||||
started_at = self._db_started_at
|
||||
or datetime.now(timezone.utc).isoformat(),
|
||||
total_steps = self._progress.total_steps or None,
|
||||
)
|
||||
self._db_run_created = True
|
||||
except Exception:
|
||||
logger.warning("Failed to create DB run record for early failure", exc_info = True)
|
||||
logger.warning(
|
||||
"Failed to create DB run record for early failure", exc_info = True
|
||||
)
|
||||
|
||||
def _finalize_run_in_db(
|
||||
self,
|
||||
|
|
@ -800,7 +827,10 @@ class TrainingBackend:
|
|||
ended_at = datetime.now(timezone.utc).isoformat(),
|
||||
final_step = self._progress.step,
|
||||
final_loss = self._progress.loss
|
||||
if (self._progress.loss is not None and math.isfinite(self._progress.loss))
|
||||
if (
|
||||
self._progress.loss is not None
|
||||
and math.isfinite(self._progress.loss)
|
||||
)
|
||||
else None,
|
||||
duration_seconds = self._progress.elapsed_seconds,
|
||||
loss_sparkline = _json.dumps(sparkline),
|
||||
|
|
@ -809,11 +839,17 @@ class TrainingBackend:
|
|||
)
|
||||
self._run_finalized = True
|
||||
except Exception:
|
||||
logger.warning("Failed to finalize run in DB (status=%s)", status, exc_info = True)
|
||||
logger.warning(
|
||||
"Failed to finalize run in DB (status=%s)", status, exc_info = True
|
||||
)
|
||||
|
||||
def _flush_metrics_to_db(self) -> None:
|
||||
"""Flush buffered metrics to the database and update live progress."""
|
||||
if not self._metric_buffer or not self.current_job_id or not self._db_run_created:
|
||||
if (
|
||||
not self._metric_buffer
|
||||
or not self.current_job_id
|
||||
or not self._db_run_created
|
||||
):
|
||||
return
|
||||
# Cap buffer to bound memory growth.
|
||||
if len(self._metric_buffer) > 500:
|
||||
|
|
@ -833,7 +869,10 @@ class TrainingBackend:
|
|||
id = self.current_job_id,
|
||||
step = self._progress.step,
|
||||
loss = self._progress.loss
|
||||
if (self._progress.loss is not None and math.isfinite(self._progress.loss))
|
||||
if (
|
||||
self._progress.loss is not None
|
||||
and math.isfinite(self._progress.loss)
|
||||
)
|
||||
else None,
|
||||
duration_seconds = self._progress.elapsed_seconds,
|
||||
)
|
||||
|
|
@ -951,7 +990,9 @@ class TrainingBackend:
|
|||
else:
|
||||
title = "Training Loss"
|
||||
|
||||
ax.set_title(title, fontsize = 11, fontweight = "bold", pad = 10, color = style["text"])
|
||||
ax.set_title(
|
||||
title, fontsize = 11, fontweight = "bold", pad = 10, color = style["text"]
|
||||
)
|
||||
ax.grid(True, alpha = 0.4, linestyle = "--", color = style["grid_color"])
|
||||
ax.tick_params(colors = style["text"], which = "both")
|
||||
ax.spines["top"].set_visible(False)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ from typing import Any, Callable
|
|||
if sys.platform.startswith("linux") and "HSA_ENABLE_DXG_DETECTION" not in os.environ:
|
||||
try:
|
||||
if os.path.exists("/dev/dxg") and any(
|
||||
os.path.exists(_p + "/librocdxg.so") for _p in ("/opt/rocm/lib", "/opt/rocm/lib64")
|
||||
os.path.exists(_p + "/librocdxg.so")
|
||||
for _p in ("/opt/rocm/lib", "/opt/rocm/lib64")
|
||||
):
|
||||
os.environ["HSA_ENABLE_DXG_DETECTION"] = "1"
|
||||
except Exception:
|
||||
|
|
@ -54,7 +55,9 @@ from utils.wheel_utils import (
|
|||
)
|
||||
|
||||
|
||||
def _output_dir_from_resume_checkpoint(resume_from_checkpoint: str | None) -> str | None:
|
||||
def _output_dir_from_resume_checkpoint(
|
||||
resume_from_checkpoint: str | None,
|
||||
) -> str | None:
|
||||
if not resume_from_checkpoint:
|
||||
return None
|
||||
path = Path(resume_from_checkpoint)
|
||||
|
|
@ -117,7 +120,9 @@ if sys.platform == "win32":
|
|||
|
||||
try:
|
||||
if os.path.isdir(_default_root):
|
||||
for _ver in sorted(os.listdir(_default_root), key = _ver_key, reverse = True):
|
||||
for _ver in sorted(
|
||||
os.listdir(_default_root), key = _ver_key, reverse = True
|
||||
):
|
||||
_bin = os.path.join(_default_root, _ver, "bin")
|
||||
if os.path.isdir(_bin):
|
||||
_candidates.append(_bin)
|
||||
|
|
@ -258,7 +263,9 @@ def _install_package_wheel_first(
|
|||
"(this may take several minutes)..."
|
||||
)
|
||||
else:
|
||||
pypi_status_message = f"Installing {display_name} from PyPI for faster training..."
|
||||
pypi_status_message = (
|
||||
f"Installing {display_name} from PyPI for faster training..."
|
||||
)
|
||||
|
||||
_send_status(event_queue, pypi_status_message)
|
||||
|
||||
|
|
@ -343,7 +350,8 @@ def _install_package_wheel_first(
|
|||
)
|
||||
_send_status(
|
||||
event_queue,
|
||||
f"{display_name} installation timed out after " f"{_run_kwargs.get('timeout')}s",
|
||||
f"{display_name} installation timed out after "
|
||||
f"{_run_kwargs.get('timeout')}s",
|
||||
)
|
||||
return False
|
||||
|
||||
|
|
@ -461,7 +469,9 @@ def _ensure_flash_linear_attention_unconditional(event_queue: Any) -> bool:
|
|||
if os.getenv(_FLA_SKIP_ENV) == "1":
|
||||
return False
|
||||
if sys.platform == "win32":
|
||||
logger.info("Skipping flash-linear-attention install: no prebuilt wheel for Windows")
|
||||
logger.info(
|
||||
"Skipping flash-linear-attention install: no prebuilt wheel for Windows"
|
||||
)
|
||||
return False
|
||||
if sys.version_info < _FLA_MIN_PYTHON:
|
||||
logger.info(
|
||||
|
|
@ -536,7 +546,9 @@ def _ensure_flash_linear_attention_unconditional(event_queue: Any) -> bool:
|
|||
)
|
||||
except _sp.TimeoutExpired:
|
||||
logger.warning("flash-linear-attention install timed out; continuing")
|
||||
_send_status(event_queue, "flash-linear-attention install timed out; continuing")
|
||||
_send_status(
|
||||
event_queue, "flash-linear-attention install timed out; continuing"
|
||||
)
|
||||
return False
|
||||
|
||||
if result.returncode != 0:
|
||||
|
|
@ -727,7 +739,10 @@ def _rocm_classify_unified_memory(props: Any) -> tuple[str, bool]:
|
|||
# Arch attrs absent — fall back to device-name matching.
|
||||
dev_lower = (getattr(props, "name", "") or "").lower()
|
||||
is_unified = (
|
||||
"890m" in dev_lower or "880m" in dev_lower or "8060s" in dev_lower or "8050s" in dev_lower
|
||||
"890m" in dev_lower
|
||||
or "880m" in dev_lower
|
||||
or "8060s" in dev_lower
|
||||
or "8050s" in dev_lower
|
||||
)
|
||||
return gcn_arch, is_unified
|
||||
|
||||
|
|
@ -770,7 +785,9 @@ def _run_pip(cmd: list[str], event_queue: Any, label: str) -> bool:
|
|||
_send_status(event_queue, f"{label} install timed out; continuing")
|
||||
return False
|
||||
if result.returncode != 0:
|
||||
logger.warning("%s install failed (continuing without it):\n%s", label, result.stdout)
|
||||
logger.warning(
|
||||
"%s install failed (continuing without it):\n%s", label, result.stdout
|
||||
)
|
||||
_send_status(event_queue, f"{label} install failed; continuing")
|
||||
return False
|
||||
return True
|
||||
|
|
@ -871,7 +888,9 @@ def _ensure_tilelang_backend(event_queue: Any, model_name: str) -> None:
|
|||
# UNSLOTH_STUDIO_SKIP_FAST_PATH_HOOKS=1 falls back to the substring path.
|
||||
|
||||
|
||||
def _rebind_in_already_imported_modules(*, attr_name: str, old_obj: Any, new_obj: Any) -> int:
|
||||
def _rebind_in_already_imported_modules(
|
||||
*, attr_name: str, old_obj: Any, new_obj: Any
|
||||
) -> int:
|
||||
"""Rebind `attr_name -> new_obj` in every module that imported `old_obj`.
|
||||
|
||||
`from X import Y` creates a local binding that reassigning X.Y won't reach.
|
||||
|
|
@ -944,7 +963,9 @@ def _install_fast_path_hooks(event_queue: Any, model_name: str) -> None:
|
|||
try:
|
||||
ok = bool(install_fn(event_queue))
|
||||
except Exception as exc:
|
||||
logger.warning("%s install raised: %s; falling back to torch", gate_name, exc)
|
||||
logger.warning(
|
||||
"%s install raised: %s; falling back to torch", gate_name, exc
|
||||
)
|
||||
ok = False
|
||||
logger.info("%s hook done; available=%s", gate_name, ok)
|
||||
# post_available_fn handles "gate already True but ancillary kernel broken"
|
||||
|
|
@ -953,7 +974,9 @@ def _install_fast_path_hooks(event_queue: Any, model_name: str) -> None:
|
|||
try:
|
||||
post_available_fn(event_queue)
|
||||
except Exception as exc:
|
||||
logger.warning("%s post-available step raised: %s; continuing", gate_name, exc)
|
||||
logger.warning(
|
||||
"%s post-available step raised: %s; continuing", gate_name, exc
|
||||
)
|
||||
state["installed"] = True
|
||||
return ok
|
||||
|
||||
|
|
@ -964,7 +987,9 @@ def _install_fast_path_hooks(event_queue: Any, model_name: str) -> None:
|
|||
def _fla_install(eq: Any) -> bool:
|
||||
# FLA alone ~2.35x; +tilelang adds ~26%. tilelang is GDN-only (Qwen3.5 family).
|
||||
if not _ensure_flash_linear_attention_unconditional(eq):
|
||||
logger.info("FLA install did not produce an importable runtime; skipping TileLang")
|
||||
logger.info(
|
||||
"FLA install did not produce an importable runtime; skipping TileLang"
|
||||
)
|
||||
return False
|
||||
if _model_wants_tilelang(model_name):
|
||||
_ensure_tilelang_backend_unconditional(eq)
|
||||
|
|
@ -979,7 +1004,10 @@ def _install_fast_path_hooks(event_queue: Any, model_name: str) -> None:
|
|||
# FLA imports; repair tilelang if missing or on the broken tvm-ffi list.
|
||||
if not _model_wants_tilelang(model_name):
|
||||
return
|
||||
if _installed_tvm_ffi_version() not in _TVM_FFI_BROKEN_VERSIONS and _tilelang_importable():
|
||||
if (
|
||||
_installed_tvm_ffi_version() not in _TVM_FFI_BROKEN_VERSIONS
|
||||
and _tilelang_importable()
|
||||
):
|
||||
return
|
||||
_ensure_tilelang_backend_unconditional(eq)
|
||||
|
||||
|
|
@ -995,7 +1023,9 @@ def _install_fast_path_hooks(event_queue: Any, model_name: str) -> None:
|
|||
pypi_version = _CAUSAL_CONV1D_PACKAGE_VERSION,
|
||||
filename_prefix = "causal_conv1d",
|
||||
release_tag = _CAUSAL_CONV1D_RELEASE_TAG,
|
||||
release_base_url = ("https://github.com/Dao-AILab/causal-conv1d/releases/download"),
|
||||
release_base_url = (
|
||||
"https://github.com/Dao-AILab/causal-conv1d/releases/download"
|
||||
),
|
||||
)
|
||||
return bool(ok)
|
||||
|
||||
|
|
@ -1015,7 +1045,9 @@ def _install_fast_path_hooks(event_queue: Any, model_name: str) -> None:
|
|||
rebound = _rebind_in_already_imported_modules(
|
||||
attr_name = gate_name, old_obj = original, new_obj = wrapped
|
||||
)
|
||||
logger.info("Installed fast-path hook on %s (rebound %d modules)", gate_name, rebound)
|
||||
logger.info(
|
||||
"Installed fast-path hook on %s (rebound %d modules)", gate_name, rebound
|
||||
)
|
||||
|
||||
|
||||
def _should_try_runtime_flash_attn_install(max_seq_length: int) -> bool:
|
||||
|
|
@ -1171,7 +1203,10 @@ def _resize_mlx_vlm_images(
|
|||
image_layout = None,
|
||||
):
|
||||
if isinstance(value, list):
|
||||
return [_resize_mlx_vlm_image(image, resize, image_layout = image_layout) for image in value]
|
||||
return [
|
||||
_resize_mlx_vlm_image(image, resize, image_layout = image_layout)
|
||||
for image in value
|
||||
]
|
||||
return _resize_mlx_vlm_image(value, resize, image_layout = image_layout)
|
||||
|
||||
|
||||
|
|
@ -1255,7 +1290,8 @@ def _normalize_mlx_studio_optimizer(value):
|
|||
except KeyError:
|
||||
supported = ", ".join(sorted(_MLX_STUDIO_OPTIM_MAP))
|
||||
raise ValueError(
|
||||
f"Unsupported optimizer for MLX training: {value!r}. " f"Supported values: {supported}."
|
||||
f"Unsupported optimizer for MLX training: {value!r}. "
|
||||
f"Supported values: {supported}."
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -1277,7 +1313,9 @@ def _resolve_mlx_local_dataset_files(file_paths: list) -> list[str]:
|
|||
all_files: list[str] = []
|
||||
for dataset_file in file_paths or []:
|
||||
file_path = (
|
||||
dataset_file if os.path.isabs(dataset_file) else str(resolve_dataset_path(dataset_file))
|
||||
dataset_file
|
||||
if os.path.isabs(dataset_file)
|
||||
else str(resolve_dataset_path(dataset_file))
|
||||
)
|
||||
file_path_obj = Path(file_path)
|
||||
|
||||
|
|
@ -1409,7 +1447,9 @@ def _run_mlx_training(event_queue, stop_queue, config):
|
|||
raise NotImplementedError(message)
|
||||
|
||||
optim_name = _normalize_mlx_studio_optimizer(config.get("optim", "adamw_8bit"))
|
||||
lr_scheduler_type = _normalize_mlx_studio_scheduler(config.get("lr_scheduler_type", "linear"))
|
||||
lr_scheduler_type = _normalize_mlx_studio_scheduler(
|
||||
config.get("lr_scheduler_type", "linear")
|
||||
)
|
||||
|
||||
# ── 1. Load model ──
|
||||
# Force text-only for non-image datasets even on vision-capable models
|
||||
|
|
@ -1489,9 +1529,15 @@ def _run_mlx_training(event_queue, stop_queue, config):
|
|||
finetune_language = config.get("finetune_language_layers", True)
|
||||
finetune_attention = config.get("finetune_attention_modules", True)
|
||||
finetune_mlp = config.get("finetune_mlp_modules", True)
|
||||
finetune_vision = config.get("finetune_vision_layers", False) if is_vlm else False
|
||||
finetune_vision = (
|
||||
config.get("finetune_vision_layers", False) if is_vlm else False
|
||||
)
|
||||
|
||||
if (finetune_attention or finetune_mlp) and not finetune_language and not finetune_vision:
|
||||
if (
|
||||
(finetune_attention or finetune_mlp)
|
||||
and not finetune_language
|
||||
and not finetune_vision
|
||||
):
|
||||
finetune_language = True
|
||||
|
||||
peft_kwargs["finetune_language_layers"] = finetune_language
|
||||
|
|
@ -1524,7 +1570,9 @@ def _run_mlx_training(event_queue, stop_queue, config):
|
|||
|
||||
if len(file_paths) == 1:
|
||||
p = Path(file_paths[0])
|
||||
if p.is_dir() and ((p / "dataset_info.json").exists() or (p / "state.json").exists()):
|
||||
if p.is_dir() and (
|
||||
(p / "dataset_info.json").exists() or (p / "state.json").exists()
|
||||
):
|
||||
return load_from_disk(str(p))
|
||||
all_files = _resolve_mlx_local_dataset_files(file_paths)
|
||||
if not all_files:
|
||||
|
|
@ -1612,7 +1660,9 @@ def _run_mlx_training(event_queue, stop_queue, config):
|
|||
)
|
||||
else:
|
||||
errors = vlm_info.get("errors", [])
|
||||
raise ValueError(f"VLM dataset format conversion failed: {'; '.join(errors)}")
|
||||
raise ValueError(
|
||||
f"VLM dataset format conversion failed: {'; '.join(errors)}"
|
||||
)
|
||||
if eval_dataset is not None:
|
||||
ev_info = format_and_template_dataset(
|
||||
eval_dataset,
|
||||
|
|
@ -1757,7 +1807,11 @@ def _run_mlx_training(event_queue, stop_queue, config):
|
|||
)
|
||||
|
||||
template_name = MODEL_TO_TEMPLATE_MAPPER.get(model_name.lower())
|
||||
markers = TEMPLATE_TO_RESPONSES_MAPPER.get(template_name) if template_name else None
|
||||
markers = (
|
||||
TEMPLATE_TO_RESPONSES_MAPPER.get(template_name)
|
||||
if template_name
|
||||
else None
|
||||
)
|
||||
if markers:
|
||||
trainer = train_on_responses_only(
|
||||
trainer,
|
||||
|
|
@ -1849,7 +1903,11 @@ def _run_mlx_training(event_queue, stop_queue, config):
|
|||
"train/tokens_per_sec": tok_s,
|
||||
"train/peak_gb": peak_gb,
|
||||
"train/num_tokens": num_tokens,
|
||||
**({"train/grad_norm": grad_norm} if grad_norm is not None else {}),
|
||||
**(
|
||||
{"train/grad_norm": grad_norm}
|
||||
if grad_norm is not None
|
||||
else {}
|
||||
),
|
||||
},
|
||||
step = step,
|
||||
)
|
||||
|
|
@ -1872,7 +1930,9 @@ def _run_mlx_training(event_queue, stop_queue, config):
|
|||
_send("progress", step = step, eval_loss = eval_loss)
|
||||
if wandb_run is not None:
|
||||
try:
|
||||
wandb_run.log({"eval/loss": eval_loss, "eval/perplexity": perplexity}, step = step)
|
||||
wandb_run.log(
|
||||
{"eval/loss": eval_loss, "eval/perplexity": perplexity}, step = step
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
if tb_writer is not None:
|
||||
|
|
@ -2174,7 +2234,9 @@ def run_training_process(*, event_queue: Any, stop_queue: Any, config: dict) ->
|
|||
if os.path.isfile(os.path.join(_scripts_dir, "hipInfo.exe")):
|
||||
import shutil as _shutil
|
||||
if not _shutil.which("hipinfo.exe"):
|
||||
os.environ["PATH"] = _scripts_dir + os.pathsep + os.environ.get("PATH", "")
|
||||
os.environ["PATH"] = (
|
||||
_scripts_dir + os.pathsep + os.environ.get("PATH", "")
|
||||
)
|
||||
|
||||
# BNB picks a rocm DLL from torch.version.hip, but AMD's Windows BNB
|
||||
# wheel may ship a DLL whose suffix doesn't match. Detect the actual
|
||||
|
|
@ -2215,7 +2277,9 @@ def run_training_process(*, event_queue: Any, stop_queue: Any, config: dict) ->
|
|||
# so later import fixes can still redetect or opt out. DLL
|
||||
# with unparsable name -> seeded value or "72".
|
||||
if _found_rocm_bnb:
|
||||
_bnb_rocm_ver = _bnb_rocm_ver or os.environ.get("BNB_ROCM_VERSION") or "72"
|
||||
_bnb_rocm_ver = (
|
||||
_bnb_rocm_ver or os.environ.get("BNB_ROCM_VERSION") or "72"
|
||||
)
|
||||
os.environ["BNB_ROCM_VERSION"] = _bnb_rocm_ver
|
||||
os.environ["UNSLOTH_BNB_ROCM_VERSION_SOURCE"] = "detected"
|
||||
logger.info(
|
||||
|
|
@ -2229,7 +2293,9 @@ def run_training_process(*, event_queue: Any, stop_queue: Any, config: dict) ->
|
|||
# the rocm version embedded in torch.__version__ when version.hip is
|
||||
# unset (AMD SDK / Radeon wheels).
|
||||
def _hip_ver_at_least(major: int, minor: int) -> bool:
|
||||
_hip_str = getattr(getattr(_torch_for_rocm, "version", None), "hip", None)
|
||||
_hip_str = getattr(
|
||||
getattr(_torch_for_rocm, "version", None), "hip", None
|
||||
)
|
||||
if not _hip_str:
|
||||
# Try the standard "+rocmX.Y.Z" embedded version first.
|
||||
_ver_match = re.search(r"rocm(\d+)\.(\d+)", _build_version_for_rocm)
|
||||
|
|
@ -2321,7 +2387,9 @@ def run_training_process(*, event_queue: Any, stop_queue: Any, config: dict) ->
|
|||
if prev < self.shape[0]:
|
||||
a_tail = self[prev:].contiguous()
|
||||
b_tail = (
|
||||
mat2[-1].contiguous() if mat2.dim() == 3 else mat2.contiguous()
|
||||
mat2[-1].contiguous()
|
||||
if mat2.dim() == 3
|
||||
else mat2.contiguous()
|
||||
)
|
||||
pieces.append(_t.mm(a_tail, b_tail))
|
||||
result = (
|
||||
|
|
@ -2491,7 +2559,11 @@ def run_training_process(*, event_queue: Any, stop_queue: Any, config: dict) ->
|
|||
def _on_progress(progress: TrainingProgress):
|
||||
has_train_loss = progress.step > 0 and progress.loss is not None
|
||||
has_eval_loss = progress.eval_loss is not None
|
||||
if (progress.step == 0 and progress.total_steps > 0) or has_train_loss or has_eval_loss:
|
||||
if (
|
||||
(progress.step == 0 and progress.total_steps > 0)
|
||||
or has_train_loss
|
||||
or has_eval_loss
|
||||
):
|
||||
event_queue.put(
|
||||
{
|
||||
"type": "progress",
|
||||
|
|
@ -2601,12 +2673,15 @@ def run_training_process(*, event_queue: Any, stop_queue: Any, config: dict) ->
|
|||
|
||||
if dataset is None or trainer.should_stop:
|
||||
if trainer.should_stop:
|
||||
event_queue.put({"type": "complete", "output_dir": None, "ts": time.time()})
|
||||
event_queue.put(
|
||||
{"type": "complete", "output_dir": None, "ts": time.time()}
|
||||
)
|
||||
else:
|
||||
event_queue.put(
|
||||
{
|
||||
"type": "error",
|
||||
"error": trainer.training_progress.error or "Failed to load dataset",
|
||||
"error": trainer.training_progress.error
|
||||
or "Failed to load dataset",
|
||||
"stack": "",
|
||||
"ts": time.time(),
|
||||
}
|
||||
|
|
@ -2627,7 +2702,9 @@ def run_training_process(*, event_queue: Any, stop_queue: Any, config: dict) ->
|
|||
desc = getattr(bar, "desc", "") or ""
|
||||
if total > 0 and n > 0 and desc:
|
||||
pct = min(int(n * 100 / total), 100)
|
||||
_send_status(event_queue, f"{desc.strip()} {pct}% ({n:,}/{total:,})")
|
||||
_send_status(
|
||||
event_queue, f"{desc.strip()} {pct}% ({n:,}/{total:,})"
|
||||
)
|
||||
except (AttributeError, ReferenceError):
|
||||
pass
|
||||
_tqdm_stop.wait(3)
|
||||
|
|
@ -2655,7 +2732,9 @@ def run_training_process(*, event_queue: Any, stop_queue: Any, config: dict) ->
|
|||
)
|
||||
if not success or trainer.should_stop:
|
||||
if trainer.should_stop:
|
||||
event_queue.put({"type": "complete", "output_dir": None, "ts": time.time()})
|
||||
event_queue.put(
|
||||
{"type": "complete", "output_dir": None, "ts": time.time()}
|
||||
)
|
||||
else:
|
||||
error_msg = trainer.training_progress.error or "Failed to load model"
|
||||
event_queue.put(
|
||||
|
|
@ -2696,7 +2775,9 @@ def run_training_process(*, event_queue: Any, stop_queue: Any, config: dict) ->
|
|||
lora_r = config.get("lora_r", 128),
|
||||
lora_alpha = config.get("lora_alpha", 32),
|
||||
lora_dropout = config.get("lora_dropout", 0.0),
|
||||
use_gradient_checkpointing = config.get("gradient_checkpointing", "unsloth"),
|
||||
use_gradient_checkpointing = config.get(
|
||||
"gradient_checkpointing", "unsloth"
|
||||
),
|
||||
use_rslora = config.get("use_rslora", False),
|
||||
use_loftq = config.get("use_loftq", False),
|
||||
)
|
||||
|
|
@ -2706,13 +2787,17 @@ def run_training_process(*, event_queue: Any, stop_queue: Any, config: dict) ->
|
|||
use_lora = True,
|
||||
finetune_vision_layers = config.get("finetune_vision_layers", True),
|
||||
finetune_language_layers = config.get("finetune_language_layers", True),
|
||||
finetune_attention_modules = config.get("finetune_attention_modules", True),
|
||||
finetune_attention_modules = config.get(
|
||||
"finetune_attention_modules", True
|
||||
),
|
||||
finetune_mlp_modules = config.get("finetune_mlp_modules", True),
|
||||
target_modules = config.get("target_modules"),
|
||||
lora_r = config.get("lora_r", 16),
|
||||
lora_alpha = config.get("lora_alpha", 16),
|
||||
lora_dropout = config.get("lora_dropout", 0.0),
|
||||
use_gradient_checkpointing = config.get("gradient_checkpointing", "unsloth"),
|
||||
use_gradient_checkpointing = config.get(
|
||||
"gradient_checkpointing", "unsloth"
|
||||
),
|
||||
use_rslora = config.get("use_rslora", False),
|
||||
use_loftq = config.get("use_loftq", False),
|
||||
)
|
||||
|
|
@ -2722,12 +2807,15 @@ def run_training_process(*, event_queue: Any, stop_queue: Any, config: dict) ->
|
|||
|
||||
if not success or trainer.should_stop:
|
||||
if trainer.should_stop:
|
||||
event_queue.put({"type": "complete", "output_dir": None, "ts": time.time()})
|
||||
event_queue.put(
|
||||
{"type": "complete", "output_dir": None, "ts": time.time()}
|
||||
)
|
||||
else:
|
||||
event_queue.put(
|
||||
{
|
||||
"type": "error",
|
||||
"error": trainer.training_progress.error or "Failed to prepare model",
|
||||
"error": trainer.training_progress.error
|
||||
or "Failed to prepare model",
|
||||
"stack": "",
|
||||
"ts": time.time(),
|
||||
}
|
||||
|
|
@ -2783,7 +2871,9 @@ def run_training_process(*, event_queue: Any, stop_queue: Any, config: dict) ->
|
|||
ensure_dir(Path(tensorboard_dir))
|
||||
|
||||
# Start training directly — no inner thread, we ARE the subprocess.
|
||||
dataset_display = config.get("hf_dataset", "") or config.get("uploaded_file", "") or ""
|
||||
dataset_display = (
|
||||
config.get("hf_dataset", "") or config.get("uploaded_file", "") or ""
|
||||
)
|
||||
_send_status(
|
||||
event_queue,
|
||||
f'Training "{model_name}"'
|
||||
|
|
@ -2807,7 +2897,9 @@ def run_training_process(*, event_queue: Any, stop_queue: Any, config: dict) ->
|
|||
weight_decay = config.get("weight_decay", 0.001),
|
||||
random_seed = config.get("random_seed", 3407),
|
||||
packing = config.get("packing", False),
|
||||
train_on_completions = False if is_cpt else config.get("train_on_completions", False),
|
||||
train_on_completions = False
|
||||
if is_cpt
|
||||
else config.get("train_on_completions", False),
|
||||
enable_wandb = config.get("enable_wandb", False),
|
||||
wandb_project = config.get("wandb_project", "unsloth-training"),
|
||||
wandb_token = config.get("wandb_token"),
|
||||
|
|
@ -3068,7 +3160,9 @@ def _run_embedding_training(event_queue: Any, stop_queue: Any, config: dict) ->
|
|||
if candidates:
|
||||
all_files.extend(str(c) for c in candidates)
|
||||
continue
|
||||
raise ValueError(f"No supported data files in directory: {file_path_obj}")
|
||||
raise ValueError(
|
||||
f"No supported data files in directory: {file_path_obj}"
|
||||
)
|
||||
else:
|
||||
all_files.append(file_path)
|
||||
|
||||
|
|
@ -3182,7 +3276,9 @@ def _run_embedding_training(event_queue: Any, stop_queue: Any, config: dict) ->
|
|||
resume_from_checkpoint
|
||||
)
|
||||
if not output_dir:
|
||||
output_dir = str(resolve_output_dir(f"{model_name.replace('/', '_')}_{int(time.time())}"))
|
||||
output_dir = str(
|
||||
resolve_output_dir(f"{model_name.replace('/', '_')}_{int(time.time())}")
|
||||
)
|
||||
output_dir = str(resolve_output_dir(output_dir))
|
||||
|
||||
num_epochs = config.get("num_epochs", 2)
|
||||
|
|
|
|||
|
|
@ -61,14 +61,17 @@ async def list_cached_datasets(current_subject: str = Depends(get_current_subjec
|
|||
|
||||
@router.delete("/cached", response_model = DeleteCachedDatasetResponse)
|
||||
async def delete_cached_dataset(
|
||||
repo_id: str = Body(..., embed = True), current_subject: str = Depends(get_current_subject)
|
||||
repo_id: str = Body(..., embed = True),
|
||||
current_subject: str = Depends(get_current_subject),
|
||||
):
|
||||
return await cache_inventory.delete_cached_dataset_response(repo_id)
|
||||
|
||||
|
||||
@router.get("/download-progress", response_model = DownloadProgressResponse)
|
||||
async def get_dataset_download_progress(
|
||||
repo_id: str = Query(..., description = "HuggingFace dataset repo ID, e.g. 'unsloth/LaTeX_OCR'"),
|
||||
repo_id: str = Query(
|
||||
..., description = "HuggingFace dataset repo ID, e.g. 'unsloth/LaTeX_OCR'"
|
||||
),
|
||||
expected_bytes: int = Query(0, description = "Expected total download size in bytes"),
|
||||
hf_token: Optional[str] = Depends(get_hf_token),
|
||||
current_subject: str = Depends(get_current_subject),
|
||||
|
|
@ -89,9 +92,12 @@ async def download_dataset(
|
|||
return await downloads.download_dataset_response(body, hf_token)
|
||||
|
||||
|
||||
@router.post("/download/cancel", response_model = CancelDatasetDownloadResponse, status_code = 202)
|
||||
@router.post(
|
||||
"/download/cancel", response_model = CancelDatasetDownloadResponse, status_code = 202
|
||||
)
|
||||
async def cancel_dataset_download(
|
||||
body: CancelDatasetDownloadRequest, current_subject: str = Depends(get_current_subject)
|
||||
body: CancelDatasetDownloadRequest,
|
||||
current_subject: str = Depends(get_current_subject),
|
||||
):
|
||||
return await downloads.cancel_dataset_download_response(body)
|
||||
|
||||
|
|
|
|||
|
|
@ -130,7 +130,9 @@ async def cancel_download_model(
|
|||
@router.get("/download-status", response_model = DownloadJobStatus)
|
||||
async def get_download_status(
|
||||
repo_id: str = Query(..., description = "HuggingFace repo ID"),
|
||||
gguf_variant: str = Query("", description = "Quantization variant (empty for safetensors)"),
|
||||
gguf_variant: str = Query(
|
||||
"", description = "Quantization variant (empty for safetensors)"
|
||||
),
|
||||
current_subject: str = Depends(get_current_subject),
|
||||
):
|
||||
return await downloads.get_download_status_response(repo_id, gguf_variant)
|
||||
|
|
@ -147,7 +149,9 @@ async def get_active_downloads(
|
|||
@router.get("/transport-status", response_model = TransportStatusResponse)
|
||||
async def get_model_transport_status(
|
||||
repo_id: str = Query(..., description = "HuggingFace repo ID"),
|
||||
gguf_variant: str = Query("", description = "Quantization variant (empty for safetensors)"),
|
||||
gguf_variant: str = Query(
|
||||
"", description = "Quantization variant (empty for safetensors)"
|
||||
),
|
||||
hf_token: Optional[str] = Depends(get_hf_token),
|
||||
current_subject: str = Depends(get_current_subject),
|
||||
):
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ from pydantic import BaseModel, Field
|
|||
from typing import List, Literal, Optional
|
||||
|
||||
|
||||
DownloadJobState = Literal["idle", "running", "cancelling", "cancelled", "complete", "error"]
|
||||
DownloadJobState = Literal[
|
||||
"idle", "running", "cancelling", "cancelled", "complete", "error"
|
||||
]
|
||||
|
||||
|
||||
class DownloadModelRequest(BaseModel):
|
||||
|
|
|
|||
|
|
@ -17,13 +17,19 @@ ModelRuntime = Literal["llama_cpp", "transformers", "adapter", "unknown"]
|
|||
class GgufVariantDetail(BaseModel):
|
||||
"""A single GGUF quantization variant in a HuggingFace repo."""
|
||||
|
||||
filename: str = Field(..., description = "GGUF filename (e.g., 'gemma-3-4b-it-Q4_K_M.gguf')")
|
||||
quant: str = Field(..., description = "Quantization label or internal GGUF variant key")
|
||||
filename: str = Field(
|
||||
..., description = "GGUF filename (e.g., 'gemma-3-4b-it-Q4_K_M.gguf')"
|
||||
)
|
||||
quant: str = Field(
|
||||
..., description = "Quantization label or internal GGUF variant key"
|
||||
)
|
||||
display_label: Optional[str] = Field(
|
||||
None, description = "Optional user-facing label when quant is an internal key"
|
||||
)
|
||||
size_bytes: int = Field(0, description = "File size in bytes")
|
||||
download_size_bytes: int = Field(0, description = "Total bytes needed to download this variant")
|
||||
download_size_bytes: int = Field(
|
||||
0, description = "Total bytes needed to download this variant"
|
||||
)
|
||||
downloaded: bool = Field(
|
||||
False, description = "Whether this variant is already in the local HF cache"
|
||||
)
|
||||
|
|
@ -132,7 +138,9 @@ class LocalModelInfo(BaseModel):
|
|||
class LocalModelListResponse(BaseModel):
|
||||
"""Response schema for listing local/cached models."""
|
||||
|
||||
models_dir: str = Field(..., description = "Directory scanned for custom local models")
|
||||
models_dir: str = Field(
|
||||
..., description = "Directory scanned for custom local models"
|
||||
)
|
||||
hf_cache_dir: Optional[str] = Field(
|
||||
None,
|
||||
description = "HF cache root that was scanned",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ from fastapi import HTTPException
|
|||
from hub.utils.hf_cache_state import resolve_destructive_case_matches
|
||||
|
||||
|
||||
def resolve_destructive_repo_ids(repo_id: str, candidates: Iterable[str], *, noun: str) -> set[str]:
|
||||
def resolve_destructive_repo_ids(
|
||||
repo_id: str, candidates: Iterable[str], *, noun: str
|
||||
) -> set[str]:
|
||||
"""Cache-dir repo ids a destructive op on *repo_id* may target.
|
||||
|
||||
Refuses with 409 on ambiguous case-only matches so a delete never removes
|
||||
|
|
|
|||
|
|
@ -194,7 +194,9 @@ def _hf_datasets_cache_roots() -> list[Path]:
|
|||
if hf_home:
|
||||
_add(Path(hf_home).expanduser() / "datasets")
|
||||
|
||||
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()
|
||||
_add(xdg_cache / "huggingface" / "datasets")
|
||||
return roots
|
||||
|
||||
|
|
@ -284,7 +286,11 @@ def _scan_hf_dataset_caches() -> list[dict]:
|
|||
rev_id = getattr(rev, "commit_hash", None) or str(id(rev))
|
||||
for f in rev.files:
|
||||
blob_path = getattr(f, "blob_path", None)
|
||||
key = str(blob_path) if blob_path else f"{rev_id}:{f.file_name}"
|
||||
key = (
|
||||
str(blob_path)
|
||||
if blob_path
|
||||
else f"{rev_id}:{f.file_name}"
|
||||
)
|
||||
unique_blobs[key] = int(f.size_on_disk or 0)
|
||||
total_size = sum(unique_blobs.values())
|
||||
key = repo_info.repo_id.lower()
|
||||
|
|
@ -320,7 +326,9 @@ def _scan_hf_dataset_caches() -> list[dict]:
|
|||
existing = seen_lower.get(key)
|
||||
if _prefer_dataset_cache_row(row, existing):
|
||||
seen_lower[key] = row
|
||||
elif existing is not None and bool(existing.get("partial")) == bool(row.get("partial")):
|
||||
elif existing is not None and bool(existing.get("partial")) == bool(
|
||||
row.get("partial")
|
||||
):
|
||||
existing["size_bytes"] = max(existing["size_bytes"], row["size_bytes"])
|
||||
existing["cache_path"] = existing.get("cache_path") or row.get("cache_path")
|
||||
if (
|
||||
|
|
@ -332,7 +340,9 @@ def _scan_hf_dataset_caches() -> list[dict]:
|
|||
for row in _scan_processed_dataset_caches():
|
||||
key = row["repo_id"].lower()
|
||||
existing = seen_lower.get(key)
|
||||
if existing is None or (bool(existing.get("partial")) and not bool(row.get("partial"))):
|
||||
if existing is None or (
|
||||
bool(existing.get("partial")) and not bool(row.get("partial"))
|
||||
):
|
||||
seen_lower[key] = row
|
||||
else:
|
||||
existing["size_bytes"] = max(existing["size_bytes"], row["size_bytes"])
|
||||
|
|
@ -366,7 +376,9 @@ async def delete_cached_dataset_response(repo_id: str) -> dict:
|
|||
if not _is_valid_repo_id(repo_id):
|
||||
raise HTTPException(status_code = 400, detail = "Invalid repo_id format")
|
||||
|
||||
repo_key = await asyncio.to_thread(resolve_cached_repo_id_case, repo_id, repo_type = "dataset")
|
||||
repo_key = await asyncio.to_thread(
|
||||
resolve_cached_repo_id_case, repo_id, repo_type = "dataset"
|
||||
)
|
||||
if not downloads.registry.begin_delete(repo_key):
|
||||
raise HTTPException(
|
||||
status_code = 400,
|
||||
|
|
@ -401,7 +413,9 @@ def _delete_cached_dataset_blocking(repo_id: str) -> dict:
|
|||
if str(repo_info.repo_id) not in matched_repo_ids:
|
||||
continue
|
||||
try:
|
||||
strategy = hf_cache.delete_revisions(*(rev.commit_hash for rev in repo_info.revisions))
|
||||
strategy = hf_cache.delete_revisions(
|
||||
*(rev.commit_hash for rev in repo_info.revisions)
|
||||
)
|
||||
strategy.execute()
|
||||
deleted = True
|
||||
except Exception as exc:
|
||||
|
|
@ -430,7 +444,9 @@ def _delete_cached_dataset_blocking(repo_id: str) -> dict:
|
|||
cache_purged = purge_repo_cache_dirs("dataset", repo_id)
|
||||
partial_purged = purge_partial_repo("dataset", repo_id)
|
||||
state_purged = download_manifest.purge_all_state_for_repo("dataset", repo_id) > 0
|
||||
if not (deleted or processed_deleted or cache_purged or partial_purged or state_purged):
|
||||
if not (
|
||||
deleted or processed_deleted or cache_purged or partial_purged or state_purged
|
||||
):
|
||||
raise HTTPException(status_code = 404, detail = "Dataset not found in cache")
|
||||
return {"status": "deleted", "repo_id": repo_id}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,9 +37,7 @@ from hub.utils.snapshot_filters import (
|
|||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
_dataset_size_cache: "OrderedDict[str, tuple[int, frozenset[str], bool, str, float]]" = (
|
||||
OrderedDict()
|
||||
)
|
||||
_dataset_size_cache: "OrderedDict[str, tuple[int, frozenset[str], bool, str, float]]" = OrderedDict()
|
||||
_dataset_size_neg_cache: "OrderedDict[tuple[str, str], float]" = OrderedDict()
|
||||
_DATASET_SIZE_CACHE_MAX = 256
|
||||
_DATASET_SIZE_POS_TTL = 60.0
|
||||
|
|
@ -88,7 +86,9 @@ def get_dataset_snapshot_metadata_cached(
|
|||
)
|
||||
total = total_size_for_siblings(info.siblings)
|
||||
hashes = blob_hashes_for_siblings(info.siblings)
|
||||
restricted = bool(getattr(info, "private", False) or getattr(info, "gated", False))
|
||||
restricted = bool(
|
||||
getattr(info, "private", False) or getattr(info, "gated", False)
|
||||
)
|
||||
except Exception:
|
||||
with _dataset_size_cache_lock:
|
||||
_dataset_size_neg_cache[cache_key] = time.monotonic()
|
||||
|
|
@ -132,7 +132,9 @@ async def get_dataset_download_progress_response(
|
|||
)
|
||||
|
||||
|
||||
def _dataset_status(key: str, *, repo_id: Optional[str] = None) -> DatasetDownloadJobStatus:
|
||||
def _dataset_status(
|
||||
key: str, *, repo_id: Optional[str] = None
|
||||
) -> DatasetDownloadJobStatus:
|
||||
state, error, generation = download_lifecycle.idle_status(
|
||||
_registry,
|
||||
key,
|
||||
|
|
@ -154,7 +156,9 @@ async def download_dataset_response(
|
|||
detail = f"Invalid repo_id: {repo_id!r}",
|
||||
)
|
||||
# Canonicalize so two different-cased paste-ins share one job + cache dir.
|
||||
repo_id = await asyncio.to_thread(resolve_cached_repo_id_case, repo_id, repo_type = "dataset")
|
||||
repo_id = await asyncio.to_thread(
|
||||
resolve_cached_repo_id_case, repo_id, repo_type = "dataset"
|
||||
)
|
||||
key = _download_job_key(repo_id)
|
||||
|
||||
transport = download_lifecycle.resolve_transport(body.use_xet)
|
||||
|
|
@ -211,7 +215,9 @@ async def cancel_dataset_download_response(body: CancelDatasetDownloadRequest) -
|
|||
status_code = 400,
|
||||
detail = f"Invalid repo_id: {repo_id!r}",
|
||||
)
|
||||
repo_id = await asyncio.to_thread(resolve_cached_repo_id_case, repo_id, repo_type = "dataset")
|
||||
repo_id = await asyncio.to_thread(
|
||||
resolve_cached_repo_id_case, repo_id, repo_type = "dataset"
|
||||
)
|
||||
key = _download_job_key(repo_id)
|
||||
|
||||
state = download_lifecycle.cancel_worker(
|
||||
|
|
@ -224,21 +230,29 @@ async def cancel_dataset_download_response(body: CancelDatasetDownloadRequest) -
|
|||
return {"repo_id": repo_id, "state": state}
|
||||
|
||||
|
||||
async def get_dataset_download_status_response(repo_id: str) -> DatasetDownloadJobStatus:
|
||||
async def get_dataset_download_status_response(
|
||||
repo_id: str,
|
||||
) -> DatasetDownloadJobStatus:
|
||||
"""Return the latest state of a background dataset download job."""
|
||||
repo_id = repo_id.strip()
|
||||
if not _is_valid_repo_id(repo_id):
|
||||
return DatasetDownloadJobStatus(state = "idle")
|
||||
repo_id = await asyncio.to_thread(resolve_cached_repo_id_case, repo_id, repo_type = "dataset")
|
||||
repo_id = await asyncio.to_thread(
|
||||
resolve_cached_repo_id_case, repo_id, repo_type = "dataset"
|
||||
)
|
||||
return _dataset_status(_download_job_key(repo_id), repo_id = repo_id)
|
||||
|
||||
|
||||
async def get_active_dataset_downloads_response(repo_id: str = "") -> ActiveDownloadsResponse:
|
||||
async def get_active_dataset_downloads_response(
|
||||
repo_id: str = "",
|
||||
) -> ActiveDownloadsResponse:
|
||||
repo_id = repo_id.strip()
|
||||
if repo_id and not _is_valid_repo_id(repo_id):
|
||||
return ActiveDownloadsResponse(downloads = [])
|
||||
canonical_repo_id = (
|
||||
await asyncio.to_thread(resolve_cached_repo_id_case, repo_id, repo_type = "dataset")
|
||||
await asyncio.to_thread(
|
||||
resolve_cached_repo_id_case, repo_id, repo_type = "dataset"
|
||||
)
|
||||
if repo_id
|
||||
else None
|
||||
)
|
||||
|
|
@ -260,7 +274,9 @@ async def get_dataset_transport_status_response(repo_id: str) -> dict:
|
|||
return {"has_partial": False, "last_transport": None, "resumable": False}
|
||||
return {
|
||||
"has_partial": has_active_incomplete_blobs("dataset", repo_id),
|
||||
"last_transport": download_registry.read_active_transport_marker("dataset", repo_id),
|
||||
"last_transport": download_registry.read_active_transport_marker(
|
||||
"dataset", repo_id
|
||||
),
|
||||
"resumable": download_registry.is_resumable_partial("dataset", repo_id),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -177,10 +177,14 @@ def _repo_file_matches_split(path: str, split: str) -> bool:
|
|||
def _select_tier1_repo_file(
|
||||
files: list[str], *, subset: Optional[str], train_split: str
|
||||
) -> Optional[str]:
|
||||
data_files = sorted(f for f in files if any(f.lower().endswith(ext) for ext in DATA_EXTS))
|
||||
data_files = sorted(
|
||||
f for f in files if any(f.lower().endswith(ext) for ext in DATA_EXTS)
|
||||
)
|
||||
if not data_files:
|
||||
return None
|
||||
tabular_files = [f for f in data_files if any(f.lower().endswith(ext) for ext in _TABULAR_EXTS)]
|
||||
tabular_files = [
|
||||
f for f in data_files if any(f.lower().endswith(ext) for ext in _TABULAR_EXTS)
|
||||
]
|
||||
candidates = tabular_files or data_files
|
||||
if subset:
|
||||
candidates = [f for f in candidates if _repo_file_matches_label(f, subset)]
|
||||
|
|
@ -405,7 +409,9 @@ def check_format_response(
|
|||
processed = format_dataset_preview(preview_slice)
|
||||
preview_samples = _serialize_preview_rows(processed)
|
||||
except Exception as e:
|
||||
logger.warning(f"Processed preview generation failed (non-fatal): {e}")
|
||||
logger.warning(
|
||||
f"Processed preview generation failed (non-fatal): {e}"
|
||||
)
|
||||
preview_samples = _serialize_preview_rows(preview_slice)
|
||||
else:
|
||||
preview_samples = _serialize_preview_rows(preview_slice)
|
||||
|
|
@ -416,7 +422,9 @@ def check_format_response(
|
|||
if image_col and image_col in (result.get("columns") or []):
|
||||
try:
|
||||
sample_val = preview_slice[0][image_col]
|
||||
if isinstance(sample_val, str) and sample_val.startswith(("http://", "https://")):
|
||||
if isinstance(sample_val, str) and sample_val.startswith(
|
||||
("http://", "https://")
|
||||
):
|
||||
url_warning = (
|
||||
"This dataset contains image URLs instead of embedded images. "
|
||||
"Images will be downloaded during training, which may be slow for large datasets."
|
||||
|
|
@ -483,7 +491,8 @@ def ai_assist_mapping_response(
|
|||
from hub.utils.llm_assist import llm_conversion_advisor
|
||||
|
||||
truncated = [
|
||||
{col: str(s.get(col, ""))[:200] for col in request.columns} for s in request.samples[:5]
|
||||
{col: str(s.get(col, ""))[:200] for col in request.columns}
|
||||
for s in request.samples[:5]
|
||||
]
|
||||
|
||||
result = llm_conversion_advisor(
|
||||
|
|
|
|||
|
|
@ -223,7 +223,9 @@ def _stream_file_preview_slice(path: Path, preview_size: int):
|
|||
return Dataset.from_list(rows), None
|
||||
|
||||
|
||||
def _load_local_preview_slice(*, dataset_path: Path, train_split: str, preview_size: int):
|
||||
def _load_local_preview_slice(
|
||||
*, dataset_path: Path, train_split: str, preview_size: int
|
||||
):
|
||||
# Non-streaming loads take the cached builder lock; use the EACCES-safe wrapper.
|
||||
from utils.datasets.cache_safe import load_dataset_cache_safe as load_dataset
|
||||
|
||||
|
|
@ -258,7 +260,9 @@ def _load_local_preview_slice(*, dataset_path: Path, train_split: str, preview_s
|
|||
# Parquet/Arrow give a cheap exact total_rows via len()+select; JSON/CSV
|
||||
# carry no such metadata, so stream them and report total_rows=None.
|
||||
if suffix == ".parquet":
|
||||
dataset = load_dataset("parquet", data_files = str(dataset_path), split = train_split)
|
||||
dataset = load_dataset(
|
||||
"parquet", data_files = str(dataset_path), split = train_split
|
||||
)
|
||||
total_rows = len(dataset)
|
||||
preview_slice = dataset.select(range(min(preview_size, total_rows)))
|
||||
return preview_slice, total_rows
|
||||
|
|
@ -272,7 +276,9 @@ def _load_local_preview_slice(*, dataset_path: Path, train_split: str, preview_s
|
|||
)
|
||||
return preview
|
||||
|
||||
raise HTTPException(status_code = 400, detail = f"Unsupported file format: {dataset_path.suffix}")
|
||||
raise HTTPException(
|
||||
status_code = 400, detail = f"Unsupported file format: {dataset_path.suffix}"
|
||||
)
|
||||
|
||||
|
||||
def _sanitize_filename(filename: str) -> str:
|
||||
|
|
@ -285,7 +291,10 @@ def _sanitize_filename(filename: str) -> str:
|
|||
def _upload_too_large(size_bytes: int) -> HTTPException:
|
||||
return HTTPException(
|
||||
status_code = 413,
|
||||
detail = (f"Upload is too large " f"({size_bytes:,} bytes; max {LOCAL_UPLOAD_MAX_BYTES:,})."),
|
||||
detail = (
|
||||
f"Upload is too large "
|
||||
f"({size_bytes:,} bytes; max {LOCAL_UPLOAD_MAX_BYTES:,})."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -26,8 +26,12 @@ def backend_dir() -> Path:
|
|||
|
||||
|
||||
def resolve_transport(use_xet: bool) -> str:
|
||||
transport = download_registry.TRANSPORT_XET if use_xet else download_registry.TRANSPORT_HTTP
|
||||
unavailable_reason = download_registry.download_transport_unavailable_reason(transport)
|
||||
transport = (
|
||||
download_registry.TRANSPORT_XET if use_xet else download_registry.TRANSPORT_HTTP
|
||||
)
|
||||
unavailable_reason = download_registry.download_transport_unavailable_reason(
|
||||
transport
|
||||
)
|
||||
if unavailable_reason is not None:
|
||||
raise HTTPException(status_code = 400, detail = unavailable_reason)
|
||||
return transport
|
||||
|
|
@ -49,7 +53,9 @@ def spawn_worker(
|
|||
shared ``.incomplete`` (e.g. bundled mmproj) is never deleted.
|
||||
"""
|
||||
cwd = backend_dir()
|
||||
mode = download_registry.TRANSPORT_XET if use_xet else download_registry.TRANSPORT_HTTP
|
||||
mode = (
|
||||
download_registry.TRANSPORT_XET if use_xet else download_registry.TRANSPORT_HTTP
|
||||
)
|
||||
env = os.environ.copy()
|
||||
if protected_blob_hashes:
|
||||
env["UNSLOTH_PROTECTED_BLOB_HASHES"] = ",".join(sorted(protected_blob_hashes))
|
||||
|
|
@ -73,7 +79,9 @@ def spawn_worker(
|
|||
if hf_token:
|
||||
env["HF_TOKEN"] = hf_token
|
||||
existing_path = env.get("PYTHONPATH", "")
|
||||
env["PYTHONPATH"] = f"{cwd}{os.pathsep}{existing_path}" if existing_path else str(cwd)
|
||||
env["PYTHONPATH"] = (
|
||||
f"{cwd}{os.pathsep}{existing_path}" if existing_path else str(cwd)
|
||||
)
|
||||
return subprocess.Popen(
|
||||
[
|
||||
sys.executable,
|
||||
|
|
@ -216,7 +224,9 @@ def finalize_worker_exit(
|
|||
f"{label}: {stderr_text}"
|
||||
)
|
||||
else:
|
||||
logger.info(f"{log_prefix} worker diagnostics for {label}: {stderr_text}")
|
||||
logger.info(
|
||||
f"{log_prefix} worker diagnostics for {label}: {stderr_text}"
|
||||
)
|
||||
logger.info(f"{log_prefix} complete: {label}")
|
||||
# Defensive cleanup: the canonical clear is at download-start; this
|
||||
# catches the rare case where that failed but the download succeeded.
|
||||
|
|
@ -422,13 +432,18 @@ def idle_status(
|
|||
|
||||
|
||||
def active_download_refs(
|
||||
registry: download_registry.DownloadRegistry, repo_id: Optional[str], *, with_variant: bool
|
||||
registry: download_registry.DownloadRegistry,
|
||||
repo_id: Optional[str],
|
||||
*,
|
||||
with_variant: bool,
|
||||
) -> list[ActiveDownload]:
|
||||
downloads: list[ActiveDownload] = []
|
||||
for ref in registry.active_job_refs(repo_id):
|
||||
metadata = ref.metadata
|
||||
if with_variant:
|
||||
ref_repo_id = metadata.repo_id if metadata is not None else ref.key.split("::", 1)[0]
|
||||
ref_repo_id = (
|
||||
metadata.repo_id if metadata is not None else ref.key.split("::", 1)[0]
|
||||
)
|
||||
if metadata is not None:
|
||||
variant = metadata.variant
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -39,7 +39,9 @@ from hub.services.models.common import (
|
|||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
_repo_size_cache: "OrderedDict[tuple[str, str], tuple[int, frozenset[str], float]]" = OrderedDict()
|
||||
_repo_size_cache: "OrderedDict[tuple[str, str], tuple[int, frozenset[str], float]]" = (
|
||||
OrderedDict()
|
||||
)
|
||||
_repo_size_neg_cache: "OrderedDict[tuple[str, str], float]" = OrderedDict()
|
||||
_REPO_SIZE_CACHE_MAX = 256
|
||||
_REPO_SIZE_POS_TTL = 60.0
|
||||
|
|
@ -168,7 +170,9 @@ def _scan_cached_gguf() -> list[dict]:
|
|||
continue
|
||||
repo_id = repo_info.repo_id
|
||||
total_size = _repo_gguf_size_bytes(repo_info)
|
||||
has_variant_state, variant_state_size = _gguf_variant_state_summary(repo_id)
|
||||
has_variant_state, variant_state_size = _gguf_variant_state_summary(
|
||||
repo_id
|
||||
)
|
||||
if total_size == 0 and not has_variant_state:
|
||||
continue
|
||||
partial = hf_cache_scan.is_gguf_repo_partial(
|
||||
|
|
@ -239,7 +243,9 @@ def _repo_non_gguf_model_payload(repo_info) -> _CachedNonGgufPayload:
|
|||
has_transformers_safetensors = False
|
||||
has_checkpoint = False
|
||||
|
||||
def _record_blob(target: dict[str, int], file_obj, rev_id: str, file_name: str) -> None:
|
||||
def _record_blob(
|
||||
target: dict[str, int], file_obj, rev_id: str, file_name: str
|
||||
) -> None:
|
||||
blob_path = getattr(file_obj, "blob_path", None)
|
||||
size = int(file_obj.size_on_disk or 0)
|
||||
key = str(blob_path) if blob_path else f"{rev_id}:{file_name}"
|
||||
|
|
@ -367,7 +373,9 @@ def _cached_model_local_metadata(repo_path: Path) -> dict:
|
|||
result["library_name"] = library_name.strip()
|
||||
tags = card.get("tags")
|
||||
if isinstance(tags, list):
|
||||
clean_tags = [tag.strip() for tag in tags if isinstance(tag, str) and tag.strip()]
|
||||
clean_tags = [
|
||||
tag.strip() for tag in tags if isinstance(tag, str) and tag.strip()
|
||||
]
|
||||
if clean_tags:
|
||||
result["tags"] = clean_tags
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -84,7 +84,9 @@ def _is_model_directory(d: Path) -> bool:
|
|||
return False
|
||||
|
||||
try:
|
||||
has_config = (d / "config.json").exists() or (d / "adapter_config.json").exists()
|
||||
has_config = (d / "config.json").exists() or (
|
||||
d / "adapter_config.json"
|
||||
).exists()
|
||||
if not has_config:
|
||||
return False
|
||||
return any(_is_weight_file(f) for f in d.iterdir() if f.is_file())
|
||||
|
|
@ -193,7 +195,9 @@ def _apply_format_aware_partial(
|
|||
continue
|
||||
# GGUF row-level transport is ambiguous (variants may differ); per-variant
|
||||
# detail lives on GgufVariantDetail.partial_transport via the variants endpoint.
|
||||
partial_transport = None if row.model_format == "gguf" else snapshot_partial_transport
|
||||
partial_transport = (
|
||||
None if row.model_format == "gguf" else snapshot_partial_transport
|
||||
)
|
||||
rewritten.append(
|
||||
row.model_copy(
|
||||
update = {
|
||||
|
|
@ -217,7 +221,9 @@ def _weight_basename(name: str) -> str:
|
|||
|
||||
def _is_adapter_weight_name(name: str) -> bool:
|
||||
lower = _weight_basename(name)
|
||||
return lower.startswith("adapter_model") and lower.endswith((".safetensors", ".bin"))
|
||||
return lower.startswith("adapter_model") and lower.endswith(
|
||||
(".safetensors", ".bin")
|
||||
)
|
||||
|
||||
|
||||
def _is_transformers_safetensors_weight_name(name: str) -> bool:
|
||||
|
|
@ -267,7 +273,9 @@ def _classify_non_gguf_model_format(
|
|||
has_checkpoint_weights: bool,
|
||||
trusted_hf_cache_repo: bool = False,
|
||||
) -> Optional[ModelFormat]:
|
||||
if has_safetensors and (has_config or (trusted_hf_cache_repo and has_transformers_safetensors)):
|
||||
if has_safetensors and (
|
||||
has_config or (trusted_hf_cache_repo and has_transformers_safetensors)
|
||||
):
|
||||
return "safetensors"
|
||||
if has_adapter_config and has_adapter_weights:
|
||||
return "adapter"
|
||||
|
|
@ -278,7 +286,9 @@ def _classify_non_gguf_model_format(
|
|||
|
||||
def _is_main_gguf_filename(name: str) -> bool:
|
||||
return (
|
||||
_is_gguf_filename(name) and not _is_mmproj_filename(name) and not _is_mtp_drafter_path(name)
|
||||
_is_gguf_filename(name)
|
||||
and not _is_mmproj_filename(name)
|
||||
and not _is_mtp_drafter_path(name)
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -445,7 +455,8 @@ def _local_model_info(
|
|||
),
|
||||
load_id = load_id,
|
||||
model_id = model_id,
|
||||
display_name = display_name or (scan_path.stem if scan_path.is_file() else scan_path.name),
|
||||
display_name = display_name
|
||||
or (scan_path.stem if scan_path.is_file() else scan_path.name),
|
||||
path = str(load_path),
|
||||
size_bytes = max(0, int(size_bytes or 0)),
|
||||
source = source,
|
||||
|
|
@ -520,12 +531,17 @@ def _classify_local_path(
|
|||
(scan_path / "adapter_config.json").is_file() if scan_path.is_dir() else False
|
||||
)
|
||||
adapter_config = _read_adapter_config(scan_path) if has_adapter_config else {}
|
||||
adapter_base_model = _clean_optional_string(adapter_config.get("base_model_name_or_path"))
|
||||
adapter_base_model = _clean_optional_string(
|
||||
adapter_config.get("base_model_name_or_path")
|
||||
)
|
||||
adapter_type = _clean_optional_string(adapter_config.get("peft_type"))
|
||||
training_method = _clean_optional_string(adapter_config.get("unsloth_training_method"))
|
||||
training_method = _clean_optional_string(
|
||||
adapter_config.get("unsloth_training_method")
|
||||
)
|
||||
has_adapter_weights = any(_is_adapter_weight_file(f) for f in files)
|
||||
has_safetensors = any(
|
||||
f.suffix.lower() == ".safetensors" and not _is_adapter_weight_file(f) for f in files
|
||||
f.suffix.lower() == ".safetensors" and not _is_adapter_weight_file(f)
|
||||
for f in files
|
||||
)
|
||||
has_transformers_safetensors = any(
|
||||
_is_transformers_safetensors_weight_file(f) and not _is_adapter_weight_file(f)
|
||||
|
|
@ -554,7 +570,9 @@ def _classify_local_path(
|
|||
if f.suffix.lower() == ".safetensors" and not _is_adapter_weight_file(f)
|
||||
)
|
||||
else:
|
||||
size_bytes = _sum_file_sizes(f for f in files if _is_checkpoint_weight_file(f))
|
||||
size_bytes = _sum_file_sizes(
|
||||
f for f in files if _is_checkpoint_weight_file(f)
|
||||
)
|
||||
rows.append(
|
||||
_local_model_info(
|
||||
scan_path = scan_path,
|
||||
|
|
|
|||
|
|
@ -75,7 +75,9 @@ def _path_exists_or_symlink(path: Path) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def _repo_file_matches(target_repo, predicate) -> list[tuple[Path, Optional[Path], str]]:
|
||||
def _repo_file_matches(
|
||||
target_repo, predicate
|
||||
) -> list[tuple[Path, Optional[Path], str]]:
|
||||
matches: list[tuple[Path, Optional[Path], str]] = []
|
||||
for rev in getattr(target_repo, "revisions", ()):
|
||||
for f in getattr(rev, "files", ()):
|
||||
|
|
@ -121,7 +123,11 @@ def _delete_gguf_variant_from_repos(
|
|||
completed_hashes: set[str] = set()
|
||||
|
||||
for target_repo in target_repos:
|
||||
repo_dir = Path(target_repo.repo_path) if getattr(target_repo, "repo_path", None) else None
|
||||
repo_dir = (
|
||||
Path(target_repo.repo_path)
|
||||
if getattr(target_repo, "repo_path", None)
|
||||
else None
|
||||
)
|
||||
matched = _repo_file_matches(
|
||||
target_repo,
|
||||
lambda name: _is_main_gguf_filename(name)
|
||||
|
|
@ -233,7 +239,10 @@ def _loaded_id_matches_repo(loaded_id: str, repo_id: str) -> bool:
|
|||
|
||||
|
||||
def _loaded_repo_variant_blocks_delete(
|
||||
loaded_id: str, repo_id: str, delete_variant: Optional[str], loaded_variant: Optional[str]
|
||||
loaded_id: str,
|
||||
repo_id: str,
|
||||
delete_variant: Optional[str],
|
||||
loaded_variant: Optional[str],
|
||||
) -> bool:
|
||||
if not _loaded_id_matches_repo(loaded_id, repo_id):
|
||||
return False
|
||||
|
|
@ -256,7 +265,9 @@ def _llama_cpp_blocks_delete(repo_id: str, variant: Optional[str]) -> bool:
|
|||
from routes.inference import get_llama_cpp_backend
|
||||
backend = get_llama_cpp_backend()
|
||||
except Exception as e:
|
||||
logger.debug(f"llama.cpp backend unavailable during delete guard for {repo_id}: {e}")
|
||||
logger.debug(
|
||||
f"llama.cpp backend unavailable during delete guard for {repo_id}: {e}"
|
||||
)
|
||||
return False
|
||||
loaded_id = backend.model_identifier
|
||||
loaded_variant = getattr(backend, "hf_variant", None)
|
||||
|
|
@ -283,7 +294,9 @@ def _inference_backend_blocks_delete(repo_id: str) -> bool:
|
|||
from core.inference import get_inference_backend
|
||||
backend = get_inference_backend()
|
||||
except Exception as e:
|
||||
logger.debug(f"Inference backend unavailable during delete guard for {repo_id}: {e}")
|
||||
logger.debug(
|
||||
f"Inference backend unavailable during delete guard for {repo_id}: {e}"
|
||||
)
|
||||
return False
|
||||
active_name = backend.active_model_name
|
||||
return bool(active_name) and _loaded_id_matches_repo(active_name, repo_id)
|
||||
|
|
@ -316,7 +329,9 @@ async def delete_cached_model_response(
|
|||
_inference_backend_blocks_delete(repo_id)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Load-state verification failed for {repo_id}; refusing delete: {e}")
|
||||
logger.warning(
|
||||
f"Load-state verification failed for {repo_id}; refusing delete: {e}"
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code = 503,
|
||||
detail = _LOAD_STATE_UNVERIFIABLE_DETAIL,
|
||||
|
|
@ -327,7 +342,9 @@ async def delete_cached_model_response(
|
|||
detail = "Unload the model before deleting",
|
||||
)
|
||||
|
||||
repo_key = await asyncio.to_thread(resolve_cached_repo_id_case, repo_id, repo_type = "model")
|
||||
repo_key = await asyncio.to_thread(
|
||||
resolve_cached_repo_id_case, repo_id, repo_type = "model"
|
||||
)
|
||||
if not downloads.registry.begin_delete(repo_key, variant):
|
||||
detail = (
|
||||
f"Cancel the {variant} download before deleting it."
|
||||
|
|
@ -336,7 +353,9 @@ async def delete_cached_model_response(
|
|||
)
|
||||
raise HTTPException(status_code = 400, detail = detail)
|
||||
try:
|
||||
return await asyncio.to_thread(_delete_cached_model_blocking, repo_id, variant, hf_token)
|
||||
return await asyncio.to_thread(
|
||||
_delete_cached_model_blocking, repo_id, variant, hf_token
|
||||
)
|
||||
finally:
|
||||
downloads.registry.end_delete(repo_key, variant)
|
||||
cache_inventory.invalidate_hf_cache_scans()
|
||||
|
|
@ -375,18 +394,22 @@ def _delete_cached_model_blocking(
|
|||
|
||||
if not target_entries:
|
||||
if variant is None:
|
||||
cache_purged = purge_repo_cache_dirs("model", repo_id) or purge_partial_repo(
|
||||
cache_purged = purge_repo_cache_dirs(
|
||||
"model", repo_id
|
||||
) or purge_partial_repo("model", repo_id)
|
||||
state_purged = (
|
||||
download_manifest.purge_all_state_for_repo("model", repo_id) > 0
|
||||
)
|
||||
state_purged = download_manifest.purge_all_state_for_repo("model", repo_id) > 0
|
||||
if cache_purged or state_purged:
|
||||
return {"status": "deleted", "repo_id": repo_id}
|
||||
if variant:
|
||||
incomplete_result = gguf_variants.delete_variant_incomplete_blobs_result(
|
||||
repo_id,
|
||||
variant,
|
||||
hf_token,
|
||||
companions = not sibling_active,
|
||||
incomplete_result = (
|
||||
gguf_variants.delete_variant_incomplete_blobs_result(
|
||||
repo_id,
|
||||
variant,
|
||||
hf_token,
|
||||
companions = not sibling_active,
|
||||
)
|
||||
)
|
||||
if incomplete_result.unresolved:
|
||||
raise HTTPException(
|
||||
|
|
@ -422,7 +445,9 @@ def _delete_cached_model_blocking(
|
|||
deleted_revisions = False
|
||||
for hf_cache, repo_info in target_entries:
|
||||
revision_hashes = [
|
||||
rev.commit_hash for rev in repo_info.revisions if getattr(rev, "commit_hash", None)
|
||||
rev.commit_hash
|
||||
for rev in repo_info.revisions
|
||||
if getattr(rev, "commit_hash", None)
|
||||
]
|
||||
if not revision_hashes:
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -78,7 +78,9 @@ def _spawn_download_worker(
|
|||
)
|
||||
|
||||
|
||||
async def download_model_response(body: DownloadModelRequest, hf_token: Optional[str] = None):
|
||||
async def download_model_response(
|
||||
body: DownloadModelRequest, hf_token: Optional[str] = None
|
||||
):
|
||||
"""Start a background download for a HuggingFace model."""
|
||||
repo_id = body.repo_id.strip()
|
||||
if not _is_valid_repo_id(repo_id):
|
||||
|
|
@ -87,7 +89,9 @@ async def download_model_response(body: DownloadModelRequest, hf_token: Optional
|
|||
detail = f"Invalid repo_id: {repo_id!r}",
|
||||
)
|
||||
# Canonicalize so two different-cased paste-ins share one job + cache dir.
|
||||
repo_id = await asyncio.to_thread(resolve_cached_repo_id_case, repo_id, repo_type = "model")
|
||||
repo_id = await asyncio.to_thread(
|
||||
resolve_cached_repo_id_case, repo_id, repo_type = "model"
|
||||
)
|
||||
|
||||
variant = (body.gguf_variant or "").strip() or None
|
||||
if variant is not None and not _is_valid_gguf_variant(variant):
|
||||
|
|
@ -200,7 +204,9 @@ async def cancel_download_model_response(body: CancelDownloadRequest):
|
|||
status_code = 400,
|
||||
detail = f"Invalid repo_id: {repo_id!r}",
|
||||
)
|
||||
repo_id = await asyncio.to_thread(resolve_cached_repo_id_case, repo_id, repo_type = "model")
|
||||
repo_id = await asyncio.to_thread(
|
||||
resolve_cached_repo_id_case, repo_id, repo_type = "model"
|
||||
)
|
||||
variant = (body.gguf_variant or "").strip() or None
|
||||
if variant is not None and not _is_valid_gguf_variant(variant):
|
||||
raise HTTPException(
|
||||
|
|
@ -219,12 +225,16 @@ async def cancel_download_model_response(body: CancelDownloadRequest):
|
|||
return {"job_key": key, "state": state}
|
||||
|
||||
|
||||
async def get_download_status_response(repo_id: str, gguf_variant: str = "") -> DownloadJobStatus:
|
||||
async def get_download_status_response(
|
||||
repo_id: str, gguf_variant: str = ""
|
||||
) -> DownloadJobStatus:
|
||||
"""Return the latest state of a background download job."""
|
||||
repo_id = repo_id.strip()
|
||||
if not _is_valid_repo_id(repo_id):
|
||||
return DownloadJobStatus(state = "idle")
|
||||
repo_id = await asyncio.to_thread(resolve_cached_repo_id_case, repo_id, repo_type = "model")
|
||||
repo_id = await asyncio.to_thread(
|
||||
resolve_cached_repo_id_case, repo_id, repo_type = "model"
|
||||
)
|
||||
variant = (gguf_variant or "").strip() or None
|
||||
key = _download_job_key(repo_id, variant)
|
||||
return _job_status(key, repo_id = repo_id, variant = variant)
|
||||
|
|
@ -249,7 +259,9 @@ async def get_active_downloads_response(repo_id: str = "") -> ActiveDownloadsRes
|
|||
)
|
||||
|
||||
|
||||
def _variant_transport_status(repo_id: str, variant: str, hf_token: Optional[str]) -> dict:
|
||||
def _variant_transport_status(
|
||||
repo_id: str, variant: str, hf_token: Optional[str]
|
||||
) -> dict:
|
||||
incomplete_hashes = download_registry.incomplete_blob_hashes(
|
||||
"model",
|
||||
repo_id,
|
||||
|
|
@ -281,13 +293,16 @@ def _variant_transport_status(repo_id: str, variant: str, hf_token: Optional[str
|
|||
variant,
|
||||
)
|
||||
has_matching_incomplete = bool(
|
||||
incomplete_hashes and variant_hashes and incomplete_hashes.intersection(variant_hashes)
|
||||
incomplete_hashes
|
||||
and variant_hashes
|
||||
and incomplete_hashes.intersection(variant_hashes)
|
||||
)
|
||||
return {
|
||||
"has_partial": has_partial,
|
||||
"last_transport": last_transport,
|
||||
"resumable": (
|
||||
has_matching_incomplete and last_transport == download_registry.TRANSPORT_HTTP
|
||||
has_matching_incomplete
|
||||
and last_transport == download_registry.TRANSPORT_HTTP
|
||||
),
|
||||
}
|
||||
|
||||
|
|
@ -315,7 +330,9 @@ async def get_model_transport_status_response(
|
|||
return _variant_transport_status(repo_id, variant, hf_token)
|
||||
return {
|
||||
"has_partial": has_active_incomplete_blobs("model", repo_id),
|
||||
"last_transport": download_registry.read_active_transport_marker("model", repo_id),
|
||||
"last_transport": download_registry.read_active_transport_marker(
|
||||
"model", repo_id
|
||||
),
|
||||
"resumable": download_registry.is_resumable_partial("model", repo_id),
|
||||
}
|
||||
|
||||
|
|
@ -359,7 +376,9 @@ async def get_gguf_download_progress_response(
|
|||
if manifest is not None:
|
||||
return (
|
||||
sum(max(0, int(file.size or 0)) for file in manifest.expected_files),
|
||||
frozenset(file.sha256 for file in manifest.expected_files if file.sha256),
|
||||
frozenset(
|
||||
file.sha256 for file in manifest.expected_files if file.sha256
|
||||
),
|
||||
)
|
||||
return (
|
||||
expected_total,
|
||||
|
|
|
|||
|
|
@ -267,7 +267,12 @@ def _browse_relative_parts(requested_path: str, root: Path) -> Optional[list[str
|
|||
parts = [part for part in rel_text.split(os.sep) if part not in ("", ".")]
|
||||
altsep = os.altsep
|
||||
for part in parts:
|
||||
if part == ".." or "\x00" in part or os.sep in part or (altsep and altsep in part):
|
||||
if (
|
||||
part == ".."
|
||||
or "\x00" in part
|
||||
or os.sep in part
|
||||
or (altsep and altsep in part)
|
||||
):
|
||||
return None
|
||||
return parts
|
||||
|
||||
|
|
@ -460,7 +465,9 @@ def browse_folders_response(
|
|||
# Parent is None at the FS root and when it would step outside the sandbox,
|
||||
# so the up-row never 403s on click.
|
||||
parent: Optional[str]
|
||||
if target.parent == target or not _is_path_inside_allowlist(target.parent, allowed_roots):
|
||||
if target.parent == target or not _is_path_inside_allowlist(
|
||||
target.parent, allowed_roots
|
||||
):
|
||||
parent = None
|
||||
else:
|
||||
parent = str(target.parent)
|
||||
|
|
|
|||
|
|
@ -49,9 +49,7 @@ from hub.utils.gguf_plan import (
|
|||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
_VARIANT_HASH_CACHE: "OrderedDict[tuple[str, str, str, bool], tuple[frozenset[str], float]]" = (
|
||||
OrderedDict()
|
||||
)
|
||||
_VARIANT_HASH_CACHE: "OrderedDict[tuple[str, str, str, bool], tuple[frozenset[str], float]]" = OrderedDict()
|
||||
_VARIANT_REQUIREMENT_CACHE: "OrderedDict[tuple[str, str, str], tuple[_GgufVariantRequirement, float]]" = OrderedDict()
|
||||
_VARIANT_REQUIREMENT_NEG_CACHE: "OrderedDict[tuple[str, str], float]" = OrderedDict()
|
||||
_VARIANT_HASH_MAX = 512
|
||||
|
|
@ -120,7 +118,9 @@ def _variant_requirement_neg_cache_clear(key: tuple[str, str]) -> None:
|
|||
_VARIANT_REQUIREMENT_NEG_CACHE.pop(key, None)
|
||||
|
||||
|
||||
def _variant_hash_cache_get(key: tuple[str, str, str, bool]) -> Optional[frozenset[str]]:
|
||||
def _variant_hash_cache_get(
|
||||
key: tuple[str, str, str, bool],
|
||||
) -> Optional[frozenset[str]]:
|
||||
with _VARIANT_HASH_LOCK:
|
||||
cached = _VARIANT_HASH_CACHE.get(key)
|
||||
if cached is None:
|
||||
|
|
@ -133,7 +133,9 @@ def _variant_hash_cache_get(key: tuple[str, str, str, bool]) -> Optional[frozens
|
|||
return hashes
|
||||
|
||||
|
||||
def _variant_hash_cache_set(key: tuple[str, str, str, bool], hashes: frozenset[str]) -> None:
|
||||
def _variant_hash_cache_set(
|
||||
key: tuple[str, str, str, bool], hashes: frozenset[str]
|
||||
) -> None:
|
||||
with _VARIANT_HASH_LOCK:
|
||||
_VARIANT_HASH_CACHE[key] = (hashes, time.monotonic())
|
||||
_VARIANT_HASH_CACHE.move_to_end(key)
|
||||
|
|
@ -141,7 +143,9 @@ def _variant_hash_cache_set(key: tuple[str, str, str, bool], hashes: frozenset[s
|
|||
_VARIANT_HASH_CACHE.popitem(last = False)
|
||||
|
||||
|
||||
def _variant_requirement_cache_get(key: tuple[str, str, str]) -> Optional[_GgufVariantRequirement]:
|
||||
def _variant_requirement_cache_get(
|
||||
key: tuple[str, str, str],
|
||||
) -> Optional[_GgufVariantRequirement]:
|
||||
with _VARIANT_HASH_LOCK:
|
||||
cached = _VARIANT_REQUIREMENT_CACHE.get(key)
|
||||
if cached is None:
|
||||
|
|
@ -155,7 +159,9 @@ def _variant_requirement_cache_get(key: tuple[str, str, str]) -> Optional[_GgufV
|
|||
|
||||
|
||||
def _variant_requirement_cache_set_many(
|
||||
repo_id: str, hf_token: Optional[str], requirements: dict[str, _GgufVariantRequirement]
|
||||
repo_id: str,
|
||||
hf_token: Optional[str],
|
||||
requirements: dict[str, _GgufVariantRequirement],
|
||||
) -> None:
|
||||
with _VARIANT_HASH_LOCK:
|
||||
now = time.monotonic()
|
||||
|
|
@ -167,7 +173,9 @@ def _variant_requirement_cache_set_many(
|
|||
_VARIANT_REQUIREMENT_CACHE.popitem(last = False)
|
||||
|
||||
|
||||
def _build_gguf_variant_requirements(siblings: list) -> dict[str, _GgufVariantRequirement]:
|
||||
def _build_gguf_variant_requirements(
|
||||
siblings: list,
|
||||
) -> dict[str, _GgufVariantRequirement]:
|
||||
return build_gguf_variant_plans(siblings)
|
||||
|
||||
|
||||
|
|
@ -278,7 +286,11 @@ def gguf_variant_blob_hashes(
|
|||
if requirement is None and allow_remote:
|
||||
requirement = gguf_variant_requirements(repo_id, variant, hf_token)
|
||||
if requirement is not None:
|
||||
hashes = requirement.required_hashes if include_companions else requirement.main_hashes
|
||||
hashes = (
|
||||
requirement.required_hashes
|
||||
if include_companions
|
||||
else requirement.main_hashes
|
||||
)
|
||||
if hashes:
|
||||
_variant_hash_cache_set(key, hashes)
|
||||
return hashes
|
||||
|
|
@ -300,7 +312,9 @@ def delete_variant_incomplete_blobs_result(
|
|||
# With a sibling still downloading, ``companions=False`` keeps a shared mmproj
|
||||
# from being unlinked out from under it; the repo's last delete reclaims it.
|
||||
target_hashes = (
|
||||
gguf_variant_blob_hashes(repo_id, variant, hf_token, include_companions = companions)
|
||||
gguf_variant_blob_hashes(
|
||||
repo_id, variant, hf_token, include_companions = companions
|
||||
)
|
||||
| extra_hashes
|
||||
)
|
||||
if not target_hashes:
|
||||
|
|
@ -310,7 +324,9 @@ def delete_variant_incomplete_blobs_result(
|
|||
incomplete_blob_hashes = set(),
|
||||
variant_blob_hashes = frozenset(),
|
||||
)
|
||||
has_repo_partials = bool(download_registry.incomplete_blob_hashes("model", repo_id))
|
||||
has_repo_partials = bool(
|
||||
download_registry.incomplete_blob_hashes("model", repo_id)
|
||||
)
|
||||
return VariantIncompleteDeleteResult(
|
||||
deleted = 0,
|
||||
unresolved = has_variant_partial_state and has_repo_partials,
|
||||
|
|
@ -440,7 +456,9 @@ async def get_gguf_variants_response(
|
|||
)
|
||||
|
||||
try:
|
||||
variants, has_vision, siblings = list_gguf_variants(repo_id, hf_token = hf_token)
|
||||
variants, has_vision, siblings = list_gguf_variants(
|
||||
repo_id, hf_token = hf_token
|
||||
)
|
||||
except Exception:
|
||||
cached = list_gguf_variants_from_hf_cache(repo_id)
|
||||
if cached is not None:
|
||||
|
|
@ -555,17 +573,25 @@ async def get_gguf_variants_response(
|
|||
partial_quants: set[str] = set()
|
||||
partial_quant_transports: dict[str, Optional[str]] = {}
|
||||
try:
|
||||
incomplete_hashes = download_registry.incomplete_blob_hashes("model", repo_id)
|
||||
incomplete_hashes = download_registry.incomplete_blob_hashes(
|
||||
"model", repo_id
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to compute partial GGUF variants for {repo_id}: {e}")
|
||||
logger.warning(
|
||||
f"Failed to compute partial GGUF variants for {repo_id}: {e}"
|
||||
)
|
||||
incomplete_hashes = set()
|
||||
scan_snapshot_dir = hf_cache_scan.resolve_snapshot_dir_for_scan("model", repo_id)
|
||||
scan_snapshot_dir = hf_cache_scan.resolve_snapshot_dir_for_scan(
|
||||
"model", repo_id
|
||||
)
|
||||
# Manifest + marker + main incomplete-blob check: catches variants whose
|
||||
# download was cancelled or whose expected shards are missing/undersized.
|
||||
for variant in variants:
|
||||
try:
|
||||
requirement = requirements_by_quant.get(variant.quant.lower())
|
||||
variant_hashes = requirement.main_hashes if requirement is not None else None
|
||||
variant_hashes = (
|
||||
requirement.main_hashes if requirement is not None else None
|
||||
)
|
||||
if variant_hashes is None and incomplete_hashes:
|
||||
variant_hashes = gguf_variant_blob_hashes(
|
||||
repo_id,
|
||||
|
|
@ -581,13 +607,16 @@ async def get_gguf_variants_response(
|
|||
variant_blob_hashes = variant_hashes,
|
||||
):
|
||||
partial_quants.add(variant.quant)
|
||||
partial_quant_transports[variant.quant] = _partial_transport_for_variant(
|
||||
repo_id,
|
||||
variant.quant,
|
||||
partial_quant_transports[variant.quant] = (
|
||||
_partial_transport_for_variant(
|
||||
repo_id,
|
||||
variant.quant,
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Manifest-based partial check failed for " f"{repo_id}/{variant.quant}: {e}"
|
||||
f"Manifest-based partial check failed for "
|
||||
f"{repo_id}/{variant.quant}: {e}"
|
||||
)
|
||||
if incomplete_hashes:
|
||||
for variant in variants:
|
||||
|
|
@ -597,7 +626,8 @@ async def get_gguf_variants_response(
|
|||
# companion_hashes adds the MTP drafter (mmproj_hashes covers
|
||||
# every mmproj precision in the repo, not just the planned one).
|
||||
if (
|
||||
(requirement.mmproj_hashes | requirement.companion_hashes) & incomplete_hashes
|
||||
(requirement.mmproj_hashes | requirement.companion_hashes)
|
||||
& incomplete_hashes
|
||||
) and _filenames_cached(
|
||||
requirement.main_filenames,
|
||||
requirement.main_size_bytes,
|
||||
|
|
@ -617,11 +647,15 @@ async def get_gguf_variants_response(
|
|||
display_label = v.display_label,
|
||||
size_bytes = v.size_bytes,
|
||||
download_size_bytes = (
|
||||
requirement.download_size_bytes if requirement is not None else v.size_bytes
|
||||
requirement.download_size_bytes
|
||||
if requirement is not None
|
||||
else v.size_bytes
|
||||
),
|
||||
downloaded = _is_fully_downloaded(v) and not is_partial,
|
||||
partial = is_partial,
|
||||
partial_transport = (partial_quant_transports.get(v.quant) if is_partial else None),
|
||||
partial_transport = (
|
||||
partial_quant_transports.get(v.quant) if is_partial else None
|
||||
),
|
||||
)
|
||||
|
||||
return GgufVariantsResponse(
|
||||
|
|
|
|||
|
|
@ -98,7 +98,9 @@ def _is_model_directory_for_scan(path: Path, *, entry_limit: int | None) -> bool
|
|||
if entry_limit is None:
|
||||
return _is_model_directory(path)
|
||||
try:
|
||||
has_config = (path / "config.json").exists() or (path / "adapter_config.json").exists()
|
||||
has_config = (path / "config.json").exists() or (
|
||||
path / "adapter_config.json"
|
||||
).exists()
|
||||
except OSError:
|
||||
return False
|
||||
return has_config and _has_immediate_model_weight(path)
|
||||
|
|
@ -151,7 +153,9 @@ def _scan_models_dir(
|
|||
break
|
||||
try:
|
||||
is_dir = child.is_dir()
|
||||
is_gguf_file = not is_dir and child.suffix.lower() == ".gguf" and child.is_file()
|
||||
is_gguf_file = (
|
||||
not is_dir and child.suffix.lower() == ".gguf" and child.is_file()
|
||||
)
|
||||
if not is_dir and not is_gguf_file:
|
||||
continue
|
||||
has_model_files = is_gguf_file or _has_immediate_model_signal(child)
|
||||
|
|
@ -190,7 +194,9 @@ def _hf_repo_dir_has_content(repo_dir: Path) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def _scan_hf_cache(cache_dir: Path, *, entry_limit: int | None = None) -> List[LocalModelInfo]:
|
||||
def _scan_hf_cache(
|
||||
cache_dir: Path, *, entry_limit: int | None = None
|
||||
) -> List[LocalModelInfo]:
|
||||
if not cache_dir.exists() or not cache_dir.is_dir():
|
||||
return []
|
||||
|
||||
|
|
@ -228,7 +234,9 @@ def _scan_hf_cache(cache_dir: Path, *, entry_limit: int | None = None) -> List[L
|
|||
repo_dir,
|
||||
)
|
||||
gguf_partial = hf_cache_scan.is_gguf_repo_partial(model_id, repo_dir)
|
||||
has_gguf_variant_state, gguf_variant_state_size = _gguf_variant_state_summary(model_id)
|
||||
has_gguf_variant_state, gguf_variant_state_size = _gguf_variant_state_summary(
|
||||
model_id
|
||||
)
|
||||
snapshot_partial_transport = (
|
||||
hf_cache_scan.partial_transport_for(
|
||||
"model",
|
||||
|
|
@ -311,7 +319,9 @@ def _scan_hf_cache(cache_dir: Path, *, entry_limit: int | None = None) -> List[L
|
|||
return found
|
||||
|
||||
|
||||
def _scan_lmstudio_dir(lm_dir: Path, *, entry_limit: int | None = None) -> List[LocalModelInfo]:
|
||||
def _scan_lmstudio_dir(
|
||||
lm_dir: Path, *, entry_limit: int | None = None
|
||||
) -> List[LocalModelInfo]:
|
||||
"""Scan an LM Studio models dir (``publisher/model-name`` folders of GGUFs, or top-level standalone GGUFs)."""
|
||||
if not lm_dir.exists() or not lm_dir.is_dir():
|
||||
return []
|
||||
|
|
@ -427,7 +437,9 @@ def _resolve_allowed_models_dir(models_dir: str, allowed_roots: list[Path]) -> P
|
|||
if not models_dir or not models_dir.strip():
|
||||
raise ValueError("Directory not allowed")
|
||||
|
||||
requested = Path(os.path.realpath(os.path.expanduser(normalize_path(models_dir.strip()))))
|
||||
requested = Path(
|
||||
os.path.realpath(os.path.expanduser(normalize_path(models_dir.strip())))
|
||||
)
|
||||
if any(path_is_same_or_child(requested, root) for root in allowed_roots):
|
||||
return requested
|
||||
|
||||
|
|
@ -510,7 +522,9 @@ async def _collect_models_from_default_sources(
|
|||
and hf_default.resolve() != hf_cache_dir.resolve()
|
||||
and hf_default.resolve() != legacy_hf.resolve()
|
||||
):
|
||||
local_models += await _scan_source("default HF cache", _scan_hf_cache, hf_default)
|
||||
local_models += await _scan_source(
|
||||
"default HF cache", _scan_hf_cache, hf_default
|
||||
)
|
||||
|
||||
for lm_dir in lm_dirs:
|
||||
local_models += await _scan_source("LM Studio", _scan_lmstudio_dir, lm_dir)
|
||||
|
|
@ -612,7 +626,9 @@ def _dedupe_local_models(local_models: List[LocalModelInfo]) -> list[LocalModelI
|
|||
)
|
||||
|
||||
|
||||
async def list_local_models_response(models_dir: str = "./models") -> LocalModelListResponse:
|
||||
async def list_local_models_response(
|
||||
models_dir: str = "./models",
|
||||
) -> LocalModelListResponse:
|
||||
"""List local model candidates from every supported on-device source."""
|
||||
hf_cache_dir = _resolve_hf_cache_dir()
|
||||
legacy_hf = legacy_hf_cache_dir()
|
||||
|
|
|
|||
|
|
@ -124,7 +124,9 @@ def _ollama_links_dir(ollama_dir: Path) -> Optional[Path]:
|
|||
return None
|
||||
|
||||
|
||||
def _make_ollama_blob_link(link_dir: Path, link_name: str, target: Path) -> Optional[str]:
|
||||
def _make_ollama_blob_link(
|
||||
link_dir: Path, link_name: str, target: Path
|
||||
) -> Optional[str]:
|
||||
"""Create a .gguf-named link to an Ollama blob: tries symlink then hardlink, skips the model if neither works (a full multi-GB copy would block the API). Idempotent."""
|
||||
try:
|
||||
link_dir.mkdir(parents = True, exist_ok = True)
|
||||
|
|
@ -137,7 +139,9 @@ def _make_ollama_blob_link(link_dir: Path, link_name: str, target: Path) -> Opti
|
|||
return None
|
||||
link_path = _contained_link_path(link_dir, link_name)
|
||||
if link_path is None:
|
||||
logger.warning("Refusing unsafe Ollama link name %r under %s", link_name, link_dir)
|
||||
logger.warning(
|
||||
"Refusing unsafe Ollama link name %r under %s", link_name, link_dir
|
||||
)
|
||||
return None
|
||||
try:
|
||||
resolved = target.resolve()
|
||||
|
|
@ -232,7 +236,9 @@ def _ollama_model_info_from_manifest(
|
|||
model_type = cfg.get("model_type", "")
|
||||
file_type = cfg.get("file_type", "")
|
||||
except (json.JSONDecodeError, OSError) as e:
|
||||
logger.debug("Could not parse Ollama config blob %s: %s", config_blob, e)
|
||||
logger.debug(
|
||||
"Could not parse Ollama config blob %s: %s", config_blob, e
|
||||
)
|
||||
|
||||
layers = manifest.get("layers") or []
|
||||
if not isinstance(layers, list):
|
||||
|
|
@ -260,11 +266,17 @@ def _ollama_model_info_from_manifest(
|
|||
model_blob = candidate
|
||||
if materialize_links and model_link_dir is not None:
|
||||
link_name = f"{safe_name}-{tag}{quant}.gguf"
|
||||
gguf_link_path = _make_ollama_blob_link(model_link_dir, link_name, candidate)
|
||||
gguf_link_path = _make_ollama_blob_link(
|
||||
model_link_dir, link_name, candidate
|
||||
)
|
||||
|
||||
elif materialize_links and media == "application/vnd.ollama.image.projector":
|
||||
candidate = _ollama_blob_path(blobs_dir, digest)
|
||||
if candidate is not None and _safe_is_file(candidate) and model_link_dir is not None:
|
||||
if (
|
||||
candidate is not None
|
||||
and _safe_is_file(candidate)
|
||||
and model_link_dir is not None
|
||||
):
|
||||
mmproj_name = f"{safe_name}-{tag}-mmproj.gguf"
|
||||
_make_ollama_blob_link(model_link_dir, mmproj_name, candidate)
|
||||
|
||||
|
|
|
|||
|
|
@ -198,7 +198,9 @@ def test_delete_cached_dataset_absent_everywhere_raises_404(monkeypatch):
|
|||
|
||||
def test_check_format_rejects_invalid_path_as_400():
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
formatting.check_format_response(CheckFormatRequest(dataset_name = "../../etc/passwd"))
|
||||
formatting.check_format_response(
|
||||
CheckFormatRequest(dataset_name = "../../etc/passwd")
|
||||
)
|
||||
|
||||
assert exc_info.value.status_code == 400
|
||||
|
||||
|
|
@ -286,7 +288,9 @@ def test_dataset_claim_register_cancel_uses_registry_marker_owner(monkeypatch):
|
|||
)
|
||||
|
||||
result = asyncio.run(
|
||||
downloads.download_dataset_response(SimpleNamespace(repo_id = "Org/Data", use_xet = False))
|
||||
downloads.download_dataset_response(
|
||||
SimpleNamespace(repo_id = "Org/Data", use_xet = False)
|
||||
)
|
||||
)
|
||||
|
||||
assert result["state"] == "cancelled"
|
||||
|
|
@ -328,7 +332,9 @@ def test_upload_dataset_response_writes_non_empty_file(monkeypatch, tmp_path):
|
|||
payload = b'{"text":"hello"}\n'
|
||||
monkeypatch.setattr(local, "DATASET_UPLOAD_DIR", tmp_path)
|
||||
|
||||
response = asyncio.run(local.upload_dataset_response(_Upload("../train.jsonl", payload)))
|
||||
response = asyncio.run(
|
||||
local.upload_dataset_response(_Upload("../train.jsonl", payload))
|
||||
)
|
||||
|
||||
stored_path = Path(response.stored_path)
|
||||
assert response.filename == "train.jsonl"
|
||||
|
|
|
|||
|
|
@ -184,7 +184,9 @@ def test_make_ollama_blob_link_refuses_escaping_name(tmp_path):
|
|||
blob.parent.mkdir(parents = True)
|
||||
blob.write_bytes(b"weights")
|
||||
|
||||
escaped = ollama._make_ollama_blob_link(link_dir, "model-tag-../../../pwned.gguf", blob)
|
||||
escaped = ollama._make_ollama_blob_link(
|
||||
link_dir, "model-tag-../../../pwned.gguf", blob
|
||||
)
|
||||
assert escaped is None
|
||||
assert not list(tmp_path.rglob("pwned.gguf"))
|
||||
|
||||
|
|
@ -200,7 +202,9 @@ def test_cached_gguf_scan_dedupes_and_excludes_mmproj_only(monkeypatch, tmp_path
|
|||
[_file("Q4_K_M.gguf", 300), _file("Q8_0.gguf", 200)],
|
||||
tmp_path / "large",
|
||||
)
|
||||
mmproj_only = _repo("Org/VisionAdapter", [_file("mmproj-F16.gguf", 900)], tmp_path / "mmproj")
|
||||
mmproj_only = _repo(
|
||||
"Org/VisionAdapter", [_file("mmproj-F16.gguf", 900)], tmp_path / "mmproj"
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
cache_inventory,
|
||||
"all_hf_cache_scans",
|
||||
|
|
@ -241,7 +245,9 @@ def test_cached_gguf_scan_preserves_partial_flag(monkeypatch, tmp_path):
|
|||
assert row["capabilities"]["can_chat"] is False
|
||||
|
||||
|
||||
def test_cached_gguf_scan_includes_variant_state_without_completed_gguf(monkeypatch, tmp_path):
|
||||
def test_cached_gguf_scan_includes_variant_state_without_completed_gguf(
|
||||
monkeypatch, tmp_path
|
||||
):
|
||||
monkeypatch.setattr(state_dir, "cache_root", lambda: tmp_path / "state")
|
||||
repo_path = tmp_path / "hub" / "models--Org--PartialGguf"
|
||||
repo_path.mkdir(parents = True)
|
||||
|
|
@ -257,7 +263,9 @@ def test_cached_gguf_scan_includes_variant_state_without_completed_gguf(monkeypa
|
|||
[download_manifest.ExpectedFile(path = "model-Q4_K_M.gguf", size = 4096)],
|
||||
"http",
|
||||
)
|
||||
assert download_manifest.write_cancel_marker("model", "Org/PartialGguf", "Q4_K_M", "http")
|
||||
assert download_manifest.write_cancel_marker(
|
||||
"model", "Org/PartialGguf", "Q4_K_M", "http"
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
cache_inventory,
|
||||
"all_hf_cache_scans",
|
||||
|
|
@ -366,7 +374,9 @@ def test_gguf_variant_blob_hashes_skip_missing_rfilename(monkeypatch):
|
|||
monkeypatch.setattr(
|
||||
gguf_variants,
|
||||
"_fetch_gguf_variant_requirements",
|
||||
lambda _repo_id, _hf_token = None: gguf_variants._build_gguf_variant_requirements(siblings),
|
||||
lambda _repo_id, _hf_token = None: gguf_variants._build_gguf_variant_requirements(
|
||||
siblings
|
||||
),
|
||||
)
|
||||
|
||||
result = gguf_variants.gguf_variant_blob_hashes("Org/Malformed", "Q4_K_M", None)
|
||||
|
|
@ -410,7 +420,9 @@ def test_download_gguf_variant_purges_only_main_quant_hashes(monkeypatch, tmp_pa
|
|||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
hf_download, "_verify_completed_download", lambda *args, **kwargs: verified.append(args)
|
||||
hf_download,
|
||||
"_verify_completed_download",
|
||||
lambda *args, **kwargs: verified.append(args),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
download_registry,
|
||||
|
|
@ -425,7 +437,8 @@ def test_download_gguf_variant_purges_only_main_quant_hashes(monkeypatch, tmp_pa
|
|||
sys.modules,
|
||||
"huggingface_hub",
|
||||
SimpleNamespace(
|
||||
snapshot_download = lambda **kwargs: snapshot_calls.append(kwargs) or str(tmp_path)
|
||||
snapshot_download = lambda **kwargs: snapshot_calls.append(kwargs)
|
||||
or str(tmp_path)
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -441,12 +454,20 @@ def test_download_gguf_variant_purges_only_main_quant_hashes(monkeypatch, tmp_pa
|
|||
},
|
||||
)
|
||||
]
|
||||
assert [file.path for file in written[0][3]] == ["model-Q4_K_M.gguf", "mmproj-F16.gguf"]
|
||||
assert snapshot_calls[0]["allow_patterns"] == ["model-Q4_K_M.gguf", "mmproj-F16.gguf"]
|
||||
assert [file.path for file in written[0][3]] == [
|
||||
"model-Q4_K_M.gguf",
|
||||
"mmproj-F16.gguf",
|
||||
]
|
||||
assert snapshot_calls[0]["allow_patterns"] == [
|
||||
"model-Q4_K_M.gguf",
|
||||
"mmproj-F16.gguf",
|
||||
]
|
||||
assert verified == [("model", "Org/Vision", "Q4_K_M", str(tmp_path))]
|
||||
|
||||
|
||||
def test_download_gguf_variant_manifest_resume_purges_only_main_quant_hashes(monkeypatch, tmp_path):
|
||||
def test_download_gguf_variant_manifest_resume_purges_only_main_quant_hashes(
|
||||
monkeypatch, tmp_path
|
||||
):
|
||||
prepare_calls = []
|
||||
snapshot_calls = []
|
||||
|
||||
|
|
@ -484,12 +505,15 @@ def test_download_gguf_variant_manifest_resume_purges_only_main_quant_hashes(mon
|
|||
"prepare_cache_for_transport",
|
||||
lambda *args, **kwargs: prepare_calls.append((args, kwargs)) or 0,
|
||||
)
|
||||
monkeypatch.setattr(hf_download, "_verify_completed_download", lambda *_args, **_kwargs: None)
|
||||
monkeypatch.setattr(
|
||||
hf_download, "_verify_completed_download", lambda *_args, **_kwargs: None
|
||||
)
|
||||
monkeypatch.setitem(
|
||||
sys.modules,
|
||||
"huggingface_hub",
|
||||
SimpleNamespace(
|
||||
snapshot_download = lambda **kwargs: snapshot_calls.append(kwargs) or str(tmp_path)
|
||||
snapshot_download = lambda **kwargs: snapshot_calls.append(kwargs)
|
||||
or str(tmp_path)
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -505,10 +529,15 @@ def test_download_gguf_variant_manifest_resume_purges_only_main_quant_hashes(mon
|
|||
},
|
||||
)
|
||||
]
|
||||
assert snapshot_calls[0]["allow_patterns"] == ["model-Q4_K_M.gguf", "mmproj-F16.gguf"]
|
||||
assert snapshot_calls[0]["allow_patterns"] == [
|
||||
"model-Q4_K_M.gguf",
|
||||
"mmproj-F16.gguf",
|
||||
]
|
||||
|
||||
|
||||
def test_download_snapshot_recovers_manifest_after_metadata_fallback(monkeypatch, tmp_path):
|
||||
def test_download_snapshot_recovers_manifest_after_metadata_fallback(
|
||||
monkeypatch, tmp_path
|
||||
):
|
||||
metadata_calls = []
|
||||
written = []
|
||||
cleared = []
|
||||
|
|
@ -518,11 +547,15 @@ def test_download_snapshot_recovers_manifest_after_metadata_fallback(monkeypatch
|
|||
metadata_calls.append(True)
|
||||
if len(metadata_calls) == 1:
|
||||
raise RuntimeError("metadata down")
|
||||
return SimpleNamespace(siblings = [SimpleNamespace(rfilename = "config.json", size = 12)])
|
||||
return SimpleNamespace(
|
||||
siblings = [SimpleNamespace(rfilename = "config.json", size = 12)]
|
||||
)
|
||||
|
||||
monkeypatch.setattr(hf_download, "_model_info_with_retry", _metadata)
|
||||
monkeypatch.setattr(
|
||||
hf_download, "_verify_completed_download", lambda *args, **kwargs: verified.append(args)
|
||||
hf_download,
|
||||
"_verify_completed_download",
|
||||
lambda *args, **kwargs: verified.append(args),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
download_registry, "prepare_cache_for_transport", lambda *_args, **_kwargs: 0
|
||||
|
|
@ -561,7 +594,9 @@ def test_download_dataset_continues_without_metadata_manifest(monkeypatch, tmp_p
|
|||
|
||||
monkeypatch.setattr(hf_download, "_dataset_info_with_retry", _metadata)
|
||||
monkeypatch.setattr(
|
||||
hf_download, "_verify_completed_download", lambda *args, **kwargs: verified.append(args)
|
||||
hf_download,
|
||||
"_verify_completed_download",
|
||||
lambda *args, **kwargs: verified.append(args),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
download_registry, "prepare_cache_for_transport", lambda *_args, **_kwargs: 0
|
||||
|
|
@ -579,7 +614,8 @@ def test_download_dataset_continues_without_metadata_manifest(monkeypatch, tmp_p
|
|||
sys.modules,
|
||||
"huggingface_hub",
|
||||
SimpleNamespace(
|
||||
snapshot_download = lambda **kwargs: snapshot_calls.append(kwargs) or str(tmp_path)
|
||||
snapshot_download = lambda **kwargs: snapshot_calls.append(kwargs)
|
||||
or str(tmp_path)
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -613,13 +649,17 @@ def test_download_snapshot_fails_when_metadata_unavailable_and_partial_remains(
|
|||
|
||||
monkeypatch.setattr(hf_download, "_model_info_with_retry", _metadata)
|
||||
monkeypatch.setattr(
|
||||
hf_download, "_verify_completed_download", lambda *args, **kwargs: verified.append(args)
|
||||
hf_download,
|
||||
"_verify_completed_download",
|
||||
lambda *args, **kwargs: verified.append(args),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
download_registry, "prepare_cache_for_transport", lambda *_args, **_kwargs: 0
|
||||
)
|
||||
monkeypatch.setattr(download_manifest, "clear_cancel_marker", lambda *_args: None)
|
||||
monkeypatch.setattr(download_manifest, "read_manifest", lambda *_args, **_kwargs: None)
|
||||
monkeypatch.setattr(
|
||||
download_manifest, "read_manifest", lambda *_args, **_kwargs: None
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
download_manifest, "write_manifest", lambda *args: written.append(args) or True
|
||||
)
|
||||
|
|
@ -719,7 +759,9 @@ def test_gguf_download_progress_fallback_logs_warning(monkeypatch):
|
|||
assert kwargs == {}
|
||||
|
||||
|
||||
def test_gguf_progress_counts_completed_mmproj_with_expected_bytes(monkeypatch, tmp_path):
|
||||
def test_gguf_progress_counts_completed_mmproj_with_expected_bytes(
|
||||
monkeypatch, tmp_path
|
||||
):
|
||||
"""A finished mmproj companion keeps counting toward progress once the caller
|
||||
supplies expected bytes; resolving the variant requirement credits it."""
|
||||
entry = tmp_path / "models--Org--Model-GGUF"
|
||||
|
|
@ -1138,7 +1180,9 @@ def test_gguf_progress_scoped_hashes_exclude_sibling_quant(monkeypatch, tmp_path
|
|||
assert result["downloaded_bytes"] == 5
|
||||
|
||||
|
||||
def test_gguf_progress_unknown_hashes_does_not_count_foreign_blobs(monkeypatch, tmp_path):
|
||||
def test_gguf_progress_unknown_hashes_does_not_count_foreign_blobs(
|
||||
monkeypatch, tmp_path
|
||||
):
|
||||
# With a variant's hashes unresolved (metadata flaked, no manifest), the
|
||||
# shared blobs/ dir's FINALIZED blobs must NOT be counted wholesale: a cached
|
||||
# sibling quant (``siblinghash``) alongside is the "instant ~900 MB" bug.
|
||||
|
|
@ -1190,7 +1234,9 @@ def test_gguf_progress_unknown_hashes_does_not_count_foreign_blobs(monkeypatch,
|
|||
assert result["complete_on_disk"] is False
|
||||
|
||||
|
||||
def test_gguf_progress_unknown_hashes_drops_unscoped_incomplete_blob(monkeypatch, tmp_path):
|
||||
def test_gguf_progress_unknown_hashes_drops_unscoped_incomplete_blob(
|
||||
monkeypatch, tmp_path
|
||||
):
|
||||
# With hashes unresolved, an .incomplete in the shared blobs/ dir can't be
|
||||
# attributed to this variant (it may be a concurrent sibling's active write),
|
||||
# so it is dropped, mirroring the finalized-blob guard. In production the
|
||||
|
|
@ -1239,7 +1285,9 @@ def test_gguf_progress_unknown_hashes_drops_unscoped_incomplete_blob(monkeypatch
|
|||
assert result["completed_bytes"] == 0 # finalized sibling still ignored
|
||||
|
||||
|
||||
def test_gguf_progress_unknown_hashes_no_backward_dip_when_variant_finalizes(monkeypatch, tmp_path):
|
||||
def test_gguf_progress_unknown_hashes_no_backward_dip_when_variant_finalizes(
|
||||
monkeypatch, tmp_path
|
||||
):
|
||||
# Regression for the two-variant dip: with hashes unresolved, the first quant
|
||||
# finalizes while the sibling still writes its .incomplete. The sibling's
|
||||
# bytes used to leak into this numerator, dipping the bar ~99% -> ~78% for
|
||||
|
|
@ -1310,7 +1358,9 @@ def test_hf_cache_model_file_probe_is_bounded(monkeypatch, tmp_path):
|
|||
model.write_bytes(b"weights")
|
||||
entries = [first, second, model]
|
||||
|
||||
monkeypatch.setattr(model_common.Path, "rglob", lambda _self, _pattern: iter(entries))
|
||||
monkeypatch.setattr(
|
||||
model_common.Path, "rglob", lambda _self, _pattern: iter(entries)
|
||||
)
|
||||
monkeypatch.setattr(model_common, "_HF_CACHE_MODEL_FILE_PROBE_LIMIT", 2)
|
||||
|
||||
bounded = model_common._iter_hf_cache_model_files(snapshot)
|
||||
|
|
@ -1333,7 +1383,9 @@ def test_download_state_lookup_is_repo_case_insensitive(monkeypatch, tmp_path):
|
|||
None,
|
||||
[download_manifest.ExpectedFile(path = "config.json", size = 12)],
|
||||
)
|
||||
assert download_manifest.write_cancel_marker("model", "Owner/Repo", "Q4_K_M", "http")
|
||||
assert download_manifest.write_cancel_marker(
|
||||
"model", "Owner/Repo", "Q4_K_M", "http"
|
||||
)
|
||||
|
||||
manifest = download_manifest.read_manifest("model", "owner/repo", None)
|
||||
|
||||
|
|
@ -1366,7 +1418,9 @@ def test_hf_cache_scan_fallback_row_uses_local_model_info_alias(monkeypatch, tmp
|
|||
blobs_dir = repo_dir / "blobs"
|
||||
blobs_dir.mkdir(parents = True)
|
||||
(blobs_dir / "blob").write_bytes(b"content")
|
||||
monkeypatch.setattr(local_inventory, "_classify_local_path", lambda *_args, **_kwargs: [])
|
||||
monkeypatch.setattr(
|
||||
local_inventory, "_classify_local_path", lambda *_args, **_kwargs: []
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
local_inventory.hf_cache_scan,
|
||||
"is_snapshot_partial",
|
||||
|
|
@ -1405,8 +1459,12 @@ def test_hf_cache_scan_uses_gguf_partial_row_for_variant_state(monkeypatch, tmp_
|
|||
[download_manifest.ExpectedFile(path = "model-Q4_K_M.gguf", size = 8192)],
|
||||
"http",
|
||||
)
|
||||
assert download_manifest.write_cancel_marker("model", "Org/PartialGguf", "Q4_K_M", "http")
|
||||
monkeypatch.setattr(local_inventory, "_classify_local_path", lambda *_args, **_kwargs: [])
|
||||
assert download_manifest.write_cancel_marker(
|
||||
"model", "Org/PartialGguf", "Q4_K_M", "http"
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
local_inventory, "_classify_local_path", lambda *_args, **_kwargs: []
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
local_inventory.hf_cache_scan,
|
||||
"is_snapshot_partial",
|
||||
|
|
@ -1443,7 +1501,9 @@ def test_model_download_job_helpers_preserve_idle_shape():
|
|||
assert status.error is None
|
||||
|
||||
|
||||
def test_gguf_repo_partial_treats_completed_disk_variant_as_clean(monkeypatch, tmp_path):
|
||||
def test_gguf_repo_partial_treats_completed_disk_variant_as_clean(
|
||||
monkeypatch, tmp_path
|
||||
):
|
||||
monkeypatch.setattr(state_dir, "cache_root", lambda: tmp_path / "state")
|
||||
snapshot = tmp_path / "cache" / "models--Org--Repo" / "snapshots" / "abc"
|
||||
snapshot.mkdir(parents = True)
|
||||
|
|
@ -1545,13 +1605,17 @@ def test_variant_partial_accepts_variant_filtered_legacy_hashes(monkeypatch, tmp
|
|||
)
|
||||
|
||||
|
||||
def test_gguf_variants_partial_marker_overrides_size_only_downloaded(monkeypatch, tmp_path):
|
||||
def test_gguf_variants_partial_marker_overrides_size_only_downloaded(
|
||||
monkeypatch, tmp_path
|
||||
):
|
||||
async def _run_inline(fn, *args, **kwargs):
|
||||
return fn(*args, **kwargs)
|
||||
|
||||
monkeypatch.setattr(state_dir, "cache_root", lambda: tmp_path / "state")
|
||||
monkeypatch.setattr(gguf_variants.asyncio, "to_thread", _run_inline)
|
||||
assert download_manifest.write_cancel_marker("model", "Org/PartialRepo", "Q4_K_M", "http")
|
||||
assert download_manifest.write_cancel_marker(
|
||||
"model", "Org/PartialRepo", "Q4_K_M", "http"
|
||||
)
|
||||
snapshot = tmp_path / "cache" / "models--Org--PartialRepo" / "snapshots" / "rev0"
|
||||
snapshot.mkdir(parents = True)
|
||||
(snapshot / "model-Q4_K_M.gguf").write_bytes(b"x" * 100)
|
||||
|
|
@ -1881,7 +1945,9 @@ def test_finalize_worker_exit_never_kills_a_healthy_worker(monkeypatch, tmp_path
|
|||
)
|
||||
|
||||
|
||||
def test_prepare_cache_for_transport_purges_only_requested_hashes(monkeypatch, tmp_path):
|
||||
def test_prepare_cache_for_transport_purges_only_requested_hashes(
|
||||
monkeypatch, tmp_path
|
||||
):
|
||||
root = tmp_path / "hub"
|
||||
blobs = root / "models--Org--Repo" / "blobs"
|
||||
blobs.mkdir(parents = True)
|
||||
|
|
@ -1910,7 +1976,9 @@ def _vision_cache_root(monkeypatch, tmp_path):
|
|||
return blobs
|
||||
|
||||
|
||||
def test_prepare_cache_for_transport_purges_cross_transport_companion(monkeypatch, tmp_path):
|
||||
def test_prepare_cache_for_transport_purges_cross_transport_companion(
|
||||
monkeypatch, tmp_path
|
||||
):
|
||||
blobs = _vision_cache_root(monkeypatch, tmp_path)
|
||||
companion = frozenset({"shared-mmproj"})
|
||||
|
||||
|
|
@ -1940,7 +2008,9 @@ def test_prepare_cache_for_transport_purges_cross_transport_companion(monkeypatc
|
|||
assert not (blobs / "shared-mmproj.incomplete").exists()
|
||||
|
||||
|
||||
def test_prepare_cache_for_transport_preserves_same_transport_companion(monkeypatch, tmp_path):
|
||||
def test_prepare_cache_for_transport_preserves_same_transport_companion(
|
||||
monkeypatch, tmp_path
|
||||
):
|
||||
blobs = _vision_cache_root(monkeypatch, tmp_path)
|
||||
companion = frozenset({"shared-mmproj"})
|
||||
|
||||
|
|
@ -1995,7 +2065,9 @@ def test_prepare_cache_for_transport_protects_peer_companion(monkeypatch, tmp_pa
|
|||
assert (blobs / "shared-mmproj.incomplete").exists()
|
||||
|
||||
|
||||
def test_model_download_records_completed_baseline_for_new_gguf_variant(monkeypatch, tmp_path):
|
||||
def test_model_download_records_completed_baseline_for_new_gguf_variant(
|
||||
monkeypatch, tmp_path
|
||||
):
|
||||
async def _run_inline(fn, *args, **kwargs):
|
||||
return fn(*args, **kwargs)
|
||||
|
||||
|
|
@ -2010,7 +2082,9 @@ def test_model_download_records_completed_baseline_for_new_gguf_variant(monkeypa
|
|||
downloads.gguf_variants,
|
||||
"gguf_variant_blob_hashes",
|
||||
lambda _repo, _variant, _token = None, include_companions = True, **_kwargs: (
|
||||
frozenset({"mainhash", "mmprojhash"}) if include_companions else frozenset({"mainhash"})
|
||||
frozenset({"mainhash", "mmprojhash"})
|
||||
if include_companions
|
||||
else frozenset({"mainhash"})
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
|
|
@ -2053,7 +2127,9 @@ def test_model_download_records_completed_baseline_for_new_gguf_variant(monkeypa
|
|||
|
||||
registry = _Registry()
|
||||
monkeypatch.setattr(downloads, "_registry", registry)
|
||||
monkeypatch.setattr(downloads, "_spawn_download_worker", lambda *_args, **_kwargs: _Proc())
|
||||
monkeypatch.setattr(
|
||||
downloads, "_spawn_download_worker", lambda *_args, **_kwargs: _Proc()
|
||||
)
|
||||
|
||||
asyncio.run(
|
||||
downloads.download_model_response(
|
||||
|
|
@ -2062,7 +2138,9 @@ def test_model_download_records_completed_baseline_for_new_gguf_variant(monkeypa
|
|||
)
|
||||
|
||||
assert registry.claim_kwargs["blob_hashes"] == frozenset({"mainhash"})
|
||||
assert registry.claim_kwargs["progress_blob_hashes"] == frozenset({"mainhash", "mmprojhash"})
|
||||
assert registry.claim_kwargs["progress_blob_hashes"] == frozenset(
|
||||
{"mainhash", "mmprojhash"}
|
||||
)
|
||||
assert registry.claim_kwargs["completed_baseline_bytes"] == 30
|
||||
|
||||
|
||||
|
|
@ -2096,7 +2174,9 @@ def test_gguf_model_download_skips_completed_baseline_for_variant_resume_state(
|
|||
downloads.gguf_variants,
|
||||
"gguf_variant_blob_hashes",
|
||||
lambda _repo, _variant, _token = None, include_companions = True, **_kwargs: (
|
||||
frozenset({"mainhash", "mmprojhash"}) if include_companions else frozenset({"mainhash"})
|
||||
frozenset({"mainhash", "mmprojhash"})
|
||||
if include_companions
|
||||
else frozenset({"mainhash"})
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
|
|
@ -2139,7 +2219,9 @@ def test_gguf_model_download_skips_completed_baseline_for_variant_resume_state(
|
|||
|
||||
registry = _Registry()
|
||||
monkeypatch.setattr(downloads, "_registry", registry)
|
||||
monkeypatch.setattr(downloads, "_spawn_download_worker", lambda *_args, **_kwargs: _Proc())
|
||||
monkeypatch.setattr(
|
||||
downloads, "_spawn_download_worker", lambda *_args, **_kwargs: _Proc()
|
||||
)
|
||||
|
||||
asyncio.run(
|
||||
downloads.download_model_response(
|
||||
|
|
@ -2153,7 +2235,9 @@ def test_gguf_model_download_skips_completed_baseline_for_variant_resume_state(
|
|||
def test_model_idle_status_uses_cancel_marker_after_restart(monkeypatch, tmp_path):
|
||||
monkeypatch.setattr(state_dir, "cache_root", lambda: tmp_path)
|
||||
monkeypatch.setattr(downloads, "_registry", download_registry.DownloadRegistry())
|
||||
assert download_manifest.write_cancel_marker("model", "Owner/Repo", "Q4_K_M", "http")
|
||||
assert download_manifest.write_cancel_marker(
|
||||
"model", "Owner/Repo", "Q4_K_M", "http"
|
||||
)
|
||||
|
||||
status = asyncio.run(downloads.get_download_status_response("owner/repo", "Q4_K_M"))
|
||||
|
||||
|
|
@ -2405,7 +2489,9 @@ def test_model_download_watcher_invalidates_hf_cache_scan(monkeypatch):
|
|||
"_spawn_download_worker",
|
||||
lambda *_args, **_kwargs: object(),
|
||||
)
|
||||
monkeypatch.setattr(downloads.download_lifecycle.threading, "Thread", _ImmediateThread)
|
||||
monkeypatch.setattr(
|
||||
downloads.download_lifecycle.threading, "Thread", _ImmediateThread
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
downloads.hf_cache_scan,
|
||||
"invalidate_hf_cache_scans",
|
||||
|
|
@ -2512,7 +2598,10 @@ def test_two_concurrent_same_repo_variants_both_complete(monkeypatch, tmp_path):
|
|||
while time.monotonic() < deadline:
|
||||
s4 = registry.get_job(key_q4).state
|
||||
s8 = registry.get_job(key_q8).state
|
||||
if s4 in download_registry.TERMINAL_STATES and s8 in download_registry.TERMINAL_STATES:
|
||||
if (
|
||||
s4 in download_registry.TERMINAL_STATES
|
||||
and s8 in download_registry.TERMINAL_STATES
|
||||
):
|
||||
break
|
||||
time.sleep(0.02)
|
||||
|
||||
|
|
@ -2628,7 +2717,9 @@ def test_snapshot_progress_filters_stale_blobs(monkeypatch, tmp_path):
|
|||
assert result["expected_bytes"] == 140
|
||||
|
||||
|
||||
def test_snapshot_progress_confirms_complete_only_with_verified_snapshot(monkeypatch, tmp_path):
|
||||
def test_snapshot_progress_confirms_complete_only_with_verified_snapshot(
|
||||
monkeypatch, tmp_path
|
||||
):
|
||||
entry = tmp_path / "models--Org--Model"
|
||||
blobs = entry / "blobs"
|
||||
snap = entry / "snapshots" / "rev0"
|
||||
|
|
@ -2691,7 +2782,9 @@ def test_expected_files_from_snapshot_dir_records_relative_paths_and_sizes(tmp_p
|
|||
assert all(f.sha256 is None for f in files)
|
||||
|
||||
|
||||
def test_snapshot_progress_complete_with_manifest_synthesized_from_disk(monkeypatch, tmp_path):
|
||||
def test_snapshot_progress_complete_with_manifest_synthesized_from_disk(
|
||||
monkeypatch, tmp_path
|
||||
):
|
||||
"""A finished snapshot whose only manifest was synthesized from on-disk files
|
||||
still verifies as complete, so a refresh finalizes it instead of capping at
|
||||
99% and evicting it as gone."""
|
||||
|
|
@ -2848,7 +2941,9 @@ def test_download_snapshot_writes_manifest_for_xet(monkeypatch, tmp_path):
|
|||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
hf_download, "_verify_completed_download", lambda *args, **kwargs: verified.append(args)
|
||||
hf_download,
|
||||
"_verify_completed_download",
|
||||
lambda *args, **kwargs: verified.append(args),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
download_registry, "prepare_cache_for_transport", lambda *_args, **_kwargs: 0
|
||||
|
|
@ -2883,7 +2978,9 @@ def test_download_gguf_variant_writes_manifest_for_xet(monkeypatch, tmp_path):
|
|||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
hf_download, "_verify_completed_download", lambda *args, **kwargs: verified.append(args)
|
||||
hf_download,
|
||||
"_verify_completed_download",
|
||||
lambda *args, **kwargs: verified.append(args),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
download_registry, "prepare_cache_for_transport", lambda *_args, **_kwargs: 0
|
||||
|
|
@ -2918,7 +3015,9 @@ def test_download_dataset_writes_manifest_for_xet(monkeypatch, tmp_path):
|
|||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
hf_download, "_verify_completed_download", lambda *args, **kwargs: verified.append(args)
|
||||
hf_download,
|
||||
"_verify_completed_download",
|
||||
lambda *args, **kwargs: verified.append(args),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
download_registry, "prepare_cache_for_transport", lambda *_args, **_kwargs: 0
|
||||
|
|
@ -2956,7 +3055,9 @@ def test_dataset_status_includes_generation(monkeypatch):
|
|||
lambda repo_id, **_kwargs: repo_id,
|
||||
)
|
||||
|
||||
result = asyncio.run(dataset_downloads.get_dataset_download_status_response("Org/Data"))
|
||||
result = asyncio.run(
|
||||
dataset_downloads.get_dataset_download_status_response("Org/Data")
|
||||
)
|
||||
|
||||
assert result.state == "running"
|
||||
assert result.generation == 4
|
||||
|
|
|
|||
|
|
@ -50,7 +50,9 @@ def _matches_label(snapshot: Path, path: Path, label: str) -> bool:
|
|||
return label in rel
|
||||
|
||||
|
||||
def dataset_snapshot_from_cache_path(local_path: Optional[str], repo_id: str) -> Optional[Path]:
|
||||
def dataset_snapshot_from_cache_path(
|
||||
local_path: Optional[str], repo_id: str
|
||||
) -> Optional[Path]:
|
||||
if not local_path or not repo_id:
|
||||
return None
|
||||
try:
|
||||
|
|
@ -115,7 +117,9 @@ def cached_dataset_candidates(
|
|||
) -> list[Path]:
|
||||
try:
|
||||
files = [
|
||||
p for p in snapshot.rglob("*") if p.is_file() and p.name.lower().endswith(extensions)
|
||||
p
|
||||
for p in snapshot.rglob("*")
|
||||
if p.is_file() and p.name.lower().endswith(extensions)
|
||||
]
|
||||
except OSError:
|
||||
return []
|
||||
|
|
@ -127,7 +131,9 @@ def cached_dataset_candidates(
|
|||
|
||||
def score(path: Path) -> tuple[int, int, str]:
|
||||
rel = _rel_lower(snapshot, path)
|
||||
subset_match = bool(subset_lower and _matches_label(snapshot, path, subset_lower))
|
||||
subset_match = bool(
|
||||
subset_lower and _matches_label(snapshot, path, subset_lower)
|
||||
)
|
||||
split_match = bool(split_lower and split_label_matches(rel, split_lower))
|
||||
location_rank = 3
|
||||
if split_match and (not subset_lower or subset_match):
|
||||
|
|
|
|||
|
|
@ -23,7 +23,10 @@ def _column_names(dataset, sample: Optional[dict] = None) -> list[str]:
|
|||
|
||||
|
||||
def _keyword_in_column(keyword: str, col_name: str) -> bool:
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
def _unknown_dataset_format(
|
||||
|
|
@ -165,14 +168,19 @@ def detect_custom_format_heuristic(dataset):
|
|||
def has_keyword(col_name, keywords):
|
||||
col_lower = col_name.lower()
|
||||
col_normalized = col_lower.replace("_", "").replace("-", "").replace(" ", "")
|
||||
return any(keyword in col_lower or keyword in col_normalized for keyword in keywords)
|
||||
return any(
|
||||
keyword in col_lower or keyword in col_normalized for keyword in keywords
|
||||
)
|
||||
|
||||
def is_metadata(col_name):
|
||||
col_lower = col_name.lower()
|
||||
if col_lower in metadata_exact_match or col_lower in metadata_prefix_patterns:
|
||||
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"]:
|
||||
|
|
@ -181,7 +189,11 @@ def detect_custom_format_heuristic(dataset):
|
|||
|
||||
def get_priority_score(col_name):
|
||||
col_lower = col_name.lower()
|
||||
return sum(score for pattern, score in priority_patterns.items() if pattern in col_lower)
|
||||
return sum(
|
||||
score
|
||||
for pattern, score in priority_patterns.items()
|
||||
if pattern in col_lower
|
||||
)
|
||||
|
||||
def get_content_length(col_name):
|
||||
try:
|
||||
|
|
@ -195,7 +207,9 @@ def detect_custom_format_heuristic(dataset):
|
|||
score = 10
|
||||
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
|
||||
score += get_priority_score(col_name)
|
||||
if role_type in ["assistant", "user"]:
|
||||
|
|
@ -219,12 +233,19 @@ def detect_custom_format_heuristic(dataset):
|
|||
return score
|
||||
|
||||
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)]
|
||||
assistant_candidates = [
|
||||
(col, score)
|
||||
for col in assistant_potential
|
||||
if (score := score_column(col, assistant_words, "assistant", len(assistant_potential))) > 0
|
||||
if (
|
||||
score := score_column(
|
||||
col, assistant_words, "assistant", len(assistant_potential)
|
||||
)
|
||||
)
|
||||
> 0
|
||||
]
|
||||
if assistant_candidates:
|
||||
assistant_candidates.sort(key = lambda item: item[1], reverse = True)
|
||||
|
|
@ -399,7 +420,9 @@ def detect_multimodal_dataset(dataset):
|
|||
audio_columns.append(col_name)
|
||||
modality_types.add("audio")
|
||||
if audio_columns:
|
||||
multimodal_columns = [col for col in multimodal_columns if col not in set(audio_columns)]
|
||||
multimodal_columns = [
|
||||
col for col in multimodal_columns if col not in set(audio_columns)
|
||||
]
|
||||
|
||||
detected_text_col = None
|
||||
if audio_columns:
|
||||
|
|
@ -454,7 +477,9 @@ def detect_vlm_dataset_structure(dataset):
|
|||
and isinstance(content[0], dict)
|
||||
and "type" in content[0]
|
||||
):
|
||||
has_index = any("index" in item for item in content if isinstance(item, dict))
|
||||
has_index = any(
|
||||
"index" in item for item in content if isinstance(item, dict)
|
||||
)
|
||||
if has_index and "images" in column_names:
|
||||
return {
|
||||
"format": "vlm_messages_llava",
|
||||
|
|
@ -463,7 +488,9 @@ def detect_vlm_dataset_structure(dataset):
|
|||
"image_column": "images",
|
||||
"text_column": None,
|
||||
}
|
||||
has_image = any("image" in item for item in content if isinstance(item, dict))
|
||||
has_image = any(
|
||||
"image" in item for item in content if isinstance(item, dict)
|
||||
)
|
||||
if has_image:
|
||||
return {
|
||||
"format": "vlm_messages",
|
||||
|
|
@ -555,9 +582,9 @@ def detect_vlm_dataset_structure(dataset):
|
|||
image_candidates = []
|
||||
for col in column_names:
|
||||
value = sample[col]
|
||||
if any(_keyword_in_column(keyword, col) for keyword in image_keywords) or _is_image_value(
|
||||
value
|
||||
):
|
||||
if any(
|
||||
_keyword_in_column(keyword, col) for keyword in image_keywords
|
||||
) or _is_image_value(value):
|
||||
if hasattr(value, "size") and hasattr(value, "mode"):
|
||||
score = 100
|
||||
elif isinstance(value, dict) and ("bytes" in value or "path" in value):
|
||||
|
|
@ -726,7 +753,9 @@ def _standardize_sharegpt_row(row: dict[str, Any], chat_column: str) -> dict[str
|
|||
if not isinstance(message, dict):
|
||||
continue
|
||||
role = message.get("role") or message.get("from")
|
||||
content = message.get("content") if "content" in message else message.get("value")
|
||||
content = (
|
||||
message.get("content") if "content" in message else message.get("value")
|
||||
)
|
||||
messages.append(
|
||||
{
|
||||
"role": _ROLE_MAP.get(str(role), str(role or "user")),
|
||||
|
|
|
|||
|
|
@ -59,7 +59,9 @@ _LEGACY_MARKER_VERSION = 1
|
|||
|
||||
# Verbatim phrase the worker emits on a degraded completion and the download
|
||||
# lifecycle escalates to a warning log. Shared so the emit and match stay coupled.
|
||||
MANIFEST_DEGRADED_MARKER = "completed without a manifest so partial detection is degraded"
|
||||
MANIFEST_DEGRADED_MARKER = (
|
||||
"completed without a manifest so partial detection is degraded"
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen = True)
|
||||
|
|
@ -473,14 +475,18 @@ def _iter_variant_state_files(
|
|||
yield _variant_from_state_file(entry, variant), entry
|
||||
|
||||
|
||||
def iter_variant_manifests(repo_type: RepoType, repo_id: str) -> Iterator[tuple[str, Path]]:
|
||||
def iter_variant_manifests(
|
||||
repo_type: RepoType, repo_id: str
|
||||
) -> Iterator[tuple[str, Path]]:
|
||||
"""Yield (variant, manifest_path) for every variant-keyed manifest
|
||||
written for this repo. Used by is_gguf_repo_partial to enumerate all
|
||||
variants present on disk so the all-variants-broken gate can run."""
|
||||
yield from _iter_variant_state_files(manifests_dir(), repo_type, repo_id)
|
||||
|
||||
|
||||
def iter_variant_markers(repo_type: RepoType, repo_id: str) -> Iterator[tuple[str, Path]]:
|
||||
def iter_variant_markers(
|
||||
repo_type: RepoType, repo_id: str
|
||||
) -> Iterator[tuple[str, Path]]:
|
||||
"""Yield (variant, marker_path) for every variant-keyed cancel marker.
|
||||
Companion to iter_variant_manifests: catches variants cancelled
|
||||
before download-start ever wrote a manifest (very early failures)."""
|
||||
|
|
|
|||
|
|
@ -113,7 +113,9 @@ def _worker_breadcrumb_path(key: str) -> Optional[Path]:
|
|||
return parent / f"{safe}.json"
|
||||
|
||||
|
||||
def write_worker_breadcrumb(key: str, pid: int, metadata: Optional["DownloadMetadata"]) -> None:
|
||||
def write_worker_breadcrumb(
|
||||
key: str, pid: int, metadata: Optional["DownloadMetadata"]
|
||||
) -> None:
|
||||
"""Record a live worker's PID so a restarted backend can reap it. Best
|
||||
effort: a write failure only forfeits boot-time reaping for this worker,
|
||||
still covered by the worker's own parent-death watchdog."""
|
||||
|
|
@ -366,7 +368,9 @@ def _iter_active_snapshot_dirs(repo_type: str, repo_id: str) -> Iterator[Path]:
|
|||
yield snapshot
|
||||
|
||||
|
||||
def _manifest_verifies_against_active_cache(repo_type: str, repo_id: str, manifest) -> bool:
|
||||
def _manifest_verifies_against_active_cache(
|
||||
repo_type: str, repo_id: str, manifest
|
||||
) -> bool:
|
||||
from hub.utils import download_manifest
|
||||
for snapshot_dir in _iter_active_snapshot_dirs(repo_type, repo_id):
|
||||
if download_manifest.verify_against_disk(manifest, snapshot_dir).ok:
|
||||
|
|
@ -374,7 +378,9 @@ def _manifest_verifies_against_active_cache(repo_type: str, repo_id: str, manife
|
|||
return False
|
||||
|
||||
|
||||
def _manifest_has_active_incomplete_blobs(repo_type: str, repo_id: str, manifest) -> bool:
|
||||
def _manifest_has_active_incomplete_blobs(
|
||||
repo_type: str, repo_id: str, manifest
|
||||
) -> bool:
|
||||
if not getattr(manifest, "variant", None):
|
||||
return has_active_incomplete_blobs(repo_type, repo_id)
|
||||
expected_hashes = frozenset(
|
||||
|
|
@ -383,7 +389,9 @@ def _manifest_has_active_incomplete_blobs(repo_type: str, repo_id: str, manifest
|
|||
if not expected_hashes:
|
||||
return has_active_incomplete_blobs(repo_type, repo_id)
|
||||
return bool(
|
||||
incomplete_blob_hashes(repo_type, repo_id, active_only = True).intersection(expected_hashes)
|
||||
incomplete_blob_hashes(repo_type, repo_id, active_only = True).intersection(
|
||||
expected_hashes
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -398,7 +406,9 @@ def _is_transport_marker_file(path: Path) -> bool:
|
|||
# Matches ".transport", its tmps, and variant-scoped ".transport.gguf-*".
|
||||
# Real HF cache entries (blobs/refs/snapshots/.no_exist) never start with
|
||||
# ".transport.".
|
||||
return path.name == TRANSPORT_MARKER_NAME or path.name.startswith(f"{TRANSPORT_MARKER_NAME}.")
|
||||
return path.name == TRANSPORT_MARKER_NAME or path.name.startswith(
|
||||
f"{TRANSPORT_MARKER_NAME}."
|
||||
)
|
||||
|
||||
|
||||
def _companion_marker_path(entry: Path) -> Path:
|
||||
|
|
@ -516,9 +526,13 @@ def prepare_cache_for_transport(
|
|||
total_purged += _purge_incomplete_blobs(entry, only_blob_hashes, protected)
|
||||
else:
|
||||
if _read_marker(entry, variant) != mode:
|
||||
total_purged += _purge_incomplete_blobs(entry, only_blob_hashes, protected)
|
||||
total_purged += _purge_incomplete_blobs(
|
||||
entry, only_blob_hashes, protected
|
||||
)
|
||||
if companion_blob_hashes and _read_companion_marker(entry) != mode:
|
||||
total_purged += _purge_incomplete_blobs(entry, companion_blob_hashes, protected)
|
||||
total_purged += _purge_incomplete_blobs(
|
||||
entry, companion_blob_hashes, protected
|
||||
)
|
||||
_write_marker(entry, mode, variant)
|
||||
if has_companion:
|
||||
_write_companion_marker(entry, mode)
|
||||
|
|
@ -562,7 +576,9 @@ def purge_empty_marker_dir(
|
|||
contents = list(entry.iterdir())
|
||||
except OSError:
|
||||
continue
|
||||
if not contents or not all(_is_transport_marker_file(item) for item in contents):
|
||||
if not contents or not all(
|
||||
_is_transport_marker_file(item) for item in contents
|
||||
):
|
||||
continue
|
||||
own_name = _marker_path(entry, variant).name
|
||||
own_markers = [
|
||||
|
|
@ -635,7 +651,9 @@ def incomplete_blob_hashes(
|
|||
return out
|
||||
|
||||
|
||||
def completed_blob_bytes(repo_type: str, repo_id: str, blob_hashes: frozenset[str]) -> int:
|
||||
def completed_blob_bytes(
|
||||
repo_type: str, repo_id: str, blob_hashes: frozenset[str]
|
||||
) -> int:
|
||||
"""Sum finalized blob bytes for *blob_hashes* in the active HF cache root.
|
||||
|
||||
A worker only writes to the active ``HF_HUB_CACHE`` root, so a baseline must
|
||||
|
|
@ -658,7 +676,9 @@ def completed_blob_bytes(repo_type: str, repo_id: str, blob_hashes: frozenset[st
|
|||
return total
|
||||
|
||||
|
||||
def existing_blob_bytes(repo_type: str, repo_id: str, blob_hashes: frozenset[str]) -> int:
|
||||
def existing_blob_bytes(
|
||||
repo_type: str, repo_id: str, blob_hashes: frozenset[str]
|
||||
) -> int:
|
||||
"""Bytes already on disk (finalized + ``.incomplete``) for *blob_hashes* in
|
||||
the active HF cache root. A blob is in exactly one state, so summing both
|
||||
candidate names never double-counts. Used to size what a (possibly resumed)
|
||||
|
|
@ -1046,7 +1066,9 @@ class DownloadRegistry:
|
|||
return (metadata.variant or "").strip().lower() or None
|
||||
return variant_from_key(key)
|
||||
|
||||
def _delete_blocked_by_active_locked(self, repo_id: str, variant: Optional[str]) -> bool:
|
||||
def _delete_blocked_by_active_locked(
|
||||
self, repo_id: str, variant: Optional[str]
|
||||
) -> bool:
|
||||
"""Whether an active download conflicts with deleting *repo_id*/*variant*.
|
||||
|
||||
A whole-repo delete (``variant is None``) conflicts with any active
|
||||
|
|
@ -1107,7 +1129,9 @@ class DownloadRegistry:
|
|||
if repo_key:
|
||||
candidate_keys = list(self._repo_active.get(repo_key, set()))
|
||||
else:
|
||||
candidate_keys = [key for active in self._repo_active.values() for key in active]
|
||||
candidate_keys = [
|
||||
key for active in self._repo_active.values() for key in active
|
||||
]
|
||||
refs: list[ActiveDownloadRef] = []
|
||||
for key in candidate_keys:
|
||||
job = self._jobs.get(key)
|
||||
|
|
@ -1231,7 +1255,9 @@ class DownloadRegistry:
|
|||
try:
|
||||
proc.wait(timeout = max(0.0, deadline - time.monotonic()))
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.warning(f"shutdown: {kind} worker for {key} did not exit after kill")
|
||||
logger.warning(
|
||||
f"shutdown: {kind} worker for {key} did not exit after kill"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
# Mark only genuinely interrupted workers (rc != 0, or None on wait
|
||||
|
|
|
|||
|
|
@ -143,7 +143,9 @@ def pick_best_gguf(filenames: list[str]) -> Optional[str]:
|
|||
gguf_files = [
|
||||
name
|
||||
for name in filenames
|
||||
if is_gguf_filename(name) and not is_mmproj_filename(name) and not is_mtp_drafter_path(name)
|
||||
if is_gguf_filename(name)
|
||||
and not is_mmproj_filename(name)
|
||||
and not is_mtp_drafter_path(name)
|
||||
]
|
||||
if not gguf_files:
|
||||
return None
|
||||
|
|
@ -246,7 +248,9 @@ def iter_hf_cache_snapshots(repo_id: str):
|
|||
yield from snapshots
|
||||
|
||||
|
||||
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]]:
|
||||
for snapshot in iter_hf_cache_snapshots(repo_id):
|
||||
variants, has_vision = list_local_gguf_variants(str(snapshot))
|
||||
if variants or has_vision:
|
||||
|
|
@ -372,7 +376,9 @@ def list_gguf_variants(
|
|||
has_vision = True
|
||||
continue
|
||||
quant = extract_quant_label(filename)
|
||||
quant_totals[quant] = quant_totals.get(quant, 0) + int(getattr(sibling, "size", 0) or 0)
|
||||
quant_totals[quant] = quant_totals.get(quant, 0) + int(
|
||||
getattr(sibling, "size", 0) or 0
|
||||
)
|
||||
quant_first_file.setdefault(quant, filename)
|
||||
|
||||
for quant, total_size in quant_totals.items():
|
||||
|
|
|
|||
|
|
@ -60,7 +60,9 @@ def expected_file_from_sibling(sibling) -> Optional[ExpectedFile]:
|
|||
def is_companion_gguf_path(path: str) -> bool:
|
||||
"""Companion (non-main) GGUF downloaded alongside a variant: the vision
|
||||
mmproj or the separate MTP drafter (Gemma 4)."""
|
||||
return is_gguf_filename(path) and (is_mmproj_filename(path) or is_mtp_drafter_path(path))
|
||||
return is_gguf_filename(path) and (
|
||||
is_mmproj_filename(path) or is_mtp_drafter_path(path)
|
||||
)
|
||||
|
||||
|
||||
def is_main_gguf_variant_path(path: str, variant: str) -> bool:
|
||||
|
|
@ -81,7 +83,9 @@ def _gguf_rfilename(sibling) -> Optional[str]:
|
|||
|
||||
|
||||
def mmproj_siblings(siblings: Sequence) -> list:
|
||||
return [s for s in siblings if (name := _gguf_rfilename(s)) and is_mmproj_filename(name)]
|
||||
return [
|
||||
s for s in siblings if (name := _gguf_rfilename(s)) and is_mmproj_filename(name)
|
||||
]
|
||||
|
||||
|
||||
def preferred_mmproj_sibling(siblings: Sequence) -> Optional[object]:
|
||||
|
|
@ -89,7 +93,11 @@ def preferred_mmproj_sibling(siblings: Sequence) -> Optional[object]:
|
|||
if not candidates:
|
||||
return None
|
||||
return next(
|
||||
(s for s in candidates if extract_quant_label(getattr(s, "rfilename")).upper() == "F16"),
|
||||
(
|
||||
s
|
||||
for s in candidates
|
||||
if extract_quant_label(getattr(s, "rfilename")).upper() == "F16"
|
||||
),
|
||||
candidates[0],
|
||||
)
|
||||
|
||||
|
|
@ -106,7 +114,8 @@ def preferred_mtp_sibling(siblings: Sequence) -> Optional[object]:
|
|||
(
|
||||
s
|
||||
for s in siblings
|
||||
if (name := _gguf_rfilename(s)) and name.lower().rsplit("/", 1)[-1].startswith("mtp-")
|
||||
if (name := _gguf_rfilename(s))
|
||||
and name.lower().rsplit("/", 1)[-1].startswith("mtp-")
|
||||
),
|
||||
key = lambda s: getattr(s, "rfilename"),
|
||||
)
|
||||
|
|
@ -121,11 +130,17 @@ def build_gguf_variant_plans(siblings: Sequence) -> dict[str, GgufVariantPlan]:
|
|||
for s in all_mmproj
|
||||
if isinstance(getattr(s, "rfilename", None), str)
|
||||
)
|
||||
all_mmproj_hashes = frozenset(h for h in (sibling_sha256(s) for s in all_mmproj) if h)
|
||||
all_mmproj_hashes = frozenset(
|
||||
h for h in (sibling_sha256(s) for s in all_mmproj) if h
|
||||
)
|
||||
companion = preferred_mmproj_sibling(siblings)
|
||||
companion_expected = expected_file_from_sibling(companion) if companion is not None else None
|
||||
companion_expected = (
|
||||
expected_file_from_sibling(companion) if companion is not None else None
|
||||
)
|
||||
mtp_sibling = preferred_mtp_sibling(siblings)
|
||||
mtp_expected = expected_file_from_sibling(mtp_sibling) if mtp_sibling is not None else None
|
||||
mtp_expected = (
|
||||
expected_file_from_sibling(mtp_sibling) if mtp_sibling is not None else None
|
||||
)
|
||||
companions_expected = tuple(
|
||||
file for file in (companion_expected, mtp_expected) if file is not None
|
||||
)
|
||||
|
|
@ -167,11 +182,17 @@ def plan_from_expected_files(
|
|||
all_mmproj_hashes: frozenset[str] | None = None,
|
||||
) -> GgufVariantPlan:
|
||||
expected = tuple(expected_files)
|
||||
main_files = tuple(file for file in expected if is_main_gguf_variant_path(file.path, variant))
|
||||
companion_files = tuple(file for file in expected if is_companion_gguf_path(file.path))
|
||||
main_files = tuple(
|
||||
file for file in expected if is_main_gguf_variant_path(file.path, variant)
|
||||
)
|
||||
companion_files = tuple(
|
||||
file for file in expected if is_companion_gguf_path(file.path)
|
||||
)
|
||||
# Manifest-resume fallback for the mmproj fields below: companion_files
|
||||
# also holds the MTP drafter, so keep an mmproj-only view.
|
||||
mmproj_files = tuple(file for file in companion_files if is_mmproj_filename(file.path))
|
||||
mmproj_files = tuple(
|
||||
file for file in companion_files if is_mmproj_filename(file.path)
|
||||
)
|
||||
main_hashes = frozenset(file.sha256 for file in main_files if file.sha256)
|
||||
companion_hashes = frozenset(file.sha256 for file in companion_files if file.sha256)
|
||||
required_hashes = frozenset(file.sha256 for file in expected if file.sha256)
|
||||
|
|
|
|||
|
|
@ -66,7 +66,9 @@ def repo_cache_dir_name(repo_type: str, repo_id: str) -> str:
|
|||
return f"{repo_type}s--{repo_id.replace('/', '--')}"
|
||||
|
||||
|
||||
def resolve_destructive_case_matches(target: str, candidates: Iterable[str]) -> Optional[set[str]]:
|
||||
def resolve_destructive_case_matches(
|
||||
target: str, candidates: Iterable[str]
|
||||
) -> Optional[set[str]]:
|
||||
values = list(candidates)
|
||||
exact = {candidate for candidate in values if candidate == target}
|
||||
if exact:
|
||||
|
|
@ -176,7 +178,9 @@ def iter_destructive_repo_cache_dirs(repo_type: str, repo_id: str) -> Iterator[P
|
|||
folded_target = target.lower()
|
||||
for root in hf_cache_roots():
|
||||
try:
|
||||
entries = [entry for entry in root.iterdir() if entry.name.lower() == folded_target]
|
||||
entries = [
|
||||
entry for entry in root.iterdir() if entry.name.lower() == folded_target
|
||||
]
|
||||
except OSError:
|
||||
continue
|
||||
matched_names = resolve_destructive_case_matches(
|
||||
|
|
|
|||
|
|
@ -346,7 +346,11 @@ def _completed_gguf_variants(snapshot_dir: Optional[Path]) -> set[str]:
|
|||
except OSError:
|
||||
continue
|
||||
rel = path.relative_to(snapshot_dir).as_posix()
|
||||
if not is_gguf_filename(rel) or is_mmproj_filename(rel) or is_mtp_drafter_path(rel):
|
||||
if (
|
||||
not is_gguf_filename(rel)
|
||||
or is_mmproj_filename(rel)
|
||||
or is_mtp_drafter_path(rel)
|
||||
):
|
||||
continue
|
||||
quant = extract_quant_label(rel)
|
||||
split = _GGUF_SPLIT_RE.search(path.name)
|
||||
|
|
@ -409,7 +413,8 @@ def is_snapshot_partial(
|
|||
|
||||
state_applies = _state_applies_to_repo_cache_dir(repo_cache_dir)
|
||||
return _compose_partial(
|
||||
lambda: state_applies and download_manifest.has_cancel_marker(repo_type, repo_id, None),
|
||||
lambda: state_applies
|
||||
and download_manifest.has_cancel_marker(repo_type, repo_id, None),
|
||||
lambda: _snapshot_legacy_partial(repo_type, repo_id, repo_cache_dir),
|
||||
lambda: _manifest_partial(
|
||||
repo_type,
|
||||
|
|
@ -441,7 +446,8 @@ def is_variant_partial(
|
|||
|
||||
state_applies = _state_applies_to_repo_cache_dir(repo_cache_dir)
|
||||
return _compose_partial(
|
||||
lambda: state_applies and download_manifest.has_cancel_marker("model", repo_id, variant),
|
||||
lambda: state_applies
|
||||
and download_manifest.has_cancel_marker("model", repo_id, variant),
|
||||
lambda: bool(
|
||||
incomplete_blob_hashes
|
||||
and variant_blob_hashes
|
||||
|
|
|
|||
|
|
@ -61,7 +61,9 @@ def _parse_json_response(text: str) -> Optional[dict[str, Any]]:
|
|||
return parsed if isinstance(parsed, dict) else None
|
||||
|
||||
|
||||
def _generate_with_backend(backend, messages: list[dict[str, str]], max_tokens: int) -> str:
|
||||
def _generate_with_backend(
|
||||
backend, messages: list[dict[str, str]], max_tokens: int
|
||||
) -> str:
|
||||
cumulative = ""
|
||||
for chunk in backend.generate_chat_completion(
|
||||
messages = messages,
|
||||
|
|
@ -159,7 +161,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:
|
||||
from core.inference.llama_cpp import LlamaCppBackend
|
||||
|
|
@ -179,7 +183,9 @@ def _run_multi_pass_advisor(
|
|||
|
||||
samples_text = _sample_text(columns, samples)
|
||||
metadata_text = (
|
||||
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"
|
||||
hints = _target_hints(model_name, model_type)
|
||||
|
|
@ -284,7 +290,9 @@ def _run_multi_pass_advisor(
|
|||
system_prompt = ""
|
||||
if not pass1.get("is_conversational"):
|
||||
user_cols = [col for col, role in column_roles.items() if role == "user"]
|
||||
assistant_cols = [col for col, role in column_roles.items() if role == "assistant"]
|
||||
assistant_cols = [
|
||||
col for col, role in column_roles.items() if role == "assistant"
|
||||
]
|
||||
prompt_raw = _generate_with_backend(
|
||||
backend,
|
||||
[
|
||||
|
|
|
|||
|
|
@ -189,7 +189,8 @@ def is_valid_repo_id(repo_id: str) -> bool:
|
|||
if len(segments) not in (1, 2):
|
||||
return False
|
||||
return all(
|
||||
segment not in ("", ".", "..") and _VALID_REPO_ID_SEGMENT.fullmatch(segment) is not None
|
||||
segment not in ("", ".", "..")
|
||||
and _VALID_REPO_ID_SEGMENT.fullmatch(segment) is not None
|
||||
for segment in segments
|
||||
)
|
||||
|
||||
|
|
@ -276,7 +277,9 @@ def _hf_hub_cache_dir() -> Path:
|
|||
from huggingface_hub.constants import HF_HUB_CACHE
|
||||
return Path(HF_HUB_CACHE)
|
||||
except Exception as exc:
|
||||
logger.debug("Could not read huggingface_hub HF_HUB_CACHE, using default: %s", exc)
|
||||
logger.debug(
|
||||
"Could not read huggingface_hub HF_HUB_CACHE, using default: %s", exc
|
||||
)
|
||||
return Path.home() / ".cache" / "huggingface" / "hub"
|
||||
|
||||
|
||||
|
|
@ -403,7 +406,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(normalized).parts if part not in ("", ".")]
|
||||
if parts[:2] == ["assets", "datasets"]:
|
||||
|
|
|
|||
|
|
@ -80,7 +80,9 @@ def repo_cache_basename(repo_type: RepoType, repo_id: str) -> str:
|
|||
# wrong filename and a misclassified scanner row (the Literal only guards
|
||||
# statically; dynamic/JSON-sourced values slip past it).
|
||||
if repo_type not in _VALID_REPO_TYPES:
|
||||
raise ValueError(f"repo_type must be one of {_VALID_REPO_TYPES}, got {repo_type!r}")
|
||||
raise ValueError(
|
||||
f"repo_type must be one of {_VALID_REPO_TYPES}, got {repo_type!r}"
|
||||
)
|
||||
return f"{repo_type}s--{repo_id.replace('/', '--')}".lower()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -183,14 +183,17 @@ def _hf_token_arg(hf_token: str | None) -> HfTokenArg:
|
|||
|
||||
|
||||
def _retry_metadata_fetch(repo_id: str, fetch, *, label: str):
|
||||
for attempt, timeout in enumerate((_METADATA_REQUEST_TIMEOUT, _METADATA_RETRY_TIMEOUT)):
|
||||
for attempt, timeout in enumerate(
|
||||
(_METADATA_REQUEST_TIMEOUT, _METADATA_RETRY_TIMEOUT)
|
||||
):
|
||||
try:
|
||||
return fetch(timeout)
|
||||
except Exception as e:
|
||||
if attempt == 1:
|
||||
raise
|
||||
print(
|
||||
f"{label} request failed for {repo_id} " f"({type(e).__name__}: {e}); retrying.",
|
||||
f"{label} request failed for {repo_id} "
|
||||
f"({type(e).__name__}: {e}); retrying.",
|
||||
file = sys.stderr,
|
||||
)
|
||||
time.sleep(_METADATA_RETRY_DELAY)
|
||||
|
|
@ -439,7 +442,9 @@ def _recover_manifest_after_download(
|
|||
)
|
||||
sys.exit(1)
|
||||
|
||||
fallback_files = download_manifest.expected_files_from_snapshot_dir(Path(snapshot_path))
|
||||
fallback_files = download_manifest.expected_files_from_snapshot_dir(
|
||||
Path(snapshot_path)
|
||||
)
|
||||
if fallback_files and download_manifest.write_manifest(
|
||||
repo_type,
|
||||
repo_id,
|
||||
|
|
@ -515,7 +520,9 @@ def _download_snapshot(repo_id: str, hf_token: str | None, mode: str) -> None:
|
|||
snapshot_path,
|
||||
mode,
|
||||
fetch_info = lambda: _model_info_with_retry(repo_id, hf_token),
|
||||
expected_files_from_info = lambda recovered: _snapshot_download_plan(recovered)[1],
|
||||
expected_files_from_info = lambda recovered: _snapshot_download_plan(
|
||||
recovered
|
||||
)[1],
|
||||
)
|
||||
_verify_completed_download(
|
||||
"model",
|
||||
|
|
@ -538,12 +545,15 @@ def _gguf_variant_target_plan(
|
|||
file = sys.stderr,
|
||||
)
|
||||
raise RuntimeError(
|
||||
f"Metadata unavailable while resolving GGUF variant '{variant}' " f"for {repo_id}"
|
||||
f"Metadata unavailable while resolving GGUF variant '{variant}' "
|
||||
f"for {repo_id}"
|
||||
) from e
|
||||
return build_gguf_variant_plans(list(info.siblings)).get(variant.lower())
|
||||
|
||||
|
||||
def _download_gguf_variant(repo_id: str, variant: str, hf_token: str | None, mode: str) -> None:
|
||||
def _download_gguf_variant(
|
||||
repo_id: str, variant: str, hf_token: str | None, mode: str
|
||||
) -> None:
|
||||
from huggingface_hub import snapshot_download
|
||||
from hub.utils.download_registry import prepare_cache_for_transport
|
||||
from hub.utils.hf_cache_state import has_active_incomplete_blobs
|
||||
|
|
|
|||
|
|
@ -47,7 +47,9 @@ if sys.platform == "win32":
|
|||
|
||||
try:
|
||||
if os.path.isdir(_default_root):
|
||||
for _ver in sorted(os.listdir(_default_root), key = _ver_key, reverse = True):
|
||||
for _ver in sorted(
|
||||
os.listdir(_default_root), key = _ver_key, reverse = True
|
||||
):
|
||||
_bin = os.path.join(_default_root, _ver, "bin")
|
||||
if os.path.isdir(_bin):
|
||||
candidates.append(_bin)
|
||||
|
|
@ -106,7 +108,9 @@ if sys.platform == "win32":
|
|||
|
||||
_all_vers_main: list[str] = []
|
||||
for _pkg_dir in _bnb_spec.submodule_search_locations:
|
||||
for _dll in _glob.glob(os.path.join(_pkg_dir, "libbitsandbytes_rocm*.dll")):
|
||||
for _dll in _glob.glob(
|
||||
os.path.join(_pkg_dir, "libbitsandbytes_rocm*.dll")
|
||||
):
|
||||
_found_rocm_bnb = True
|
||||
_km = _re_bnb.search(
|
||||
r"libbitsandbytes_rocm(\d+)\.dll", os.path.basename(_dll)
|
||||
|
|
@ -124,7 +128,9 @@ if sys.platform == "win32":
|
|||
# (HIP SDK on a CUDA/CPU box) must not force a ROCm backend onto a
|
||||
# non-ROCm bitsandbytes, which raises at import. DLL unparsable -> "72".
|
||||
if _found_rocm_bnb:
|
||||
_bnb_rocm_ver_final = _bnb_rocm_ver or os.environ.get("BNB_ROCM_VERSION") or "72"
|
||||
_bnb_rocm_ver_final = (
|
||||
_bnb_rocm_ver or os.environ.get("BNB_ROCM_VERSION") or "72"
|
||||
)
|
||||
os.environ["BNB_ROCM_VERSION"] = _bnb_rocm_ver_final
|
||||
os.environ["UNSLOTH_BNB_ROCM_VERSION_SOURCE"] = "detected"
|
||||
_logging.getLogger(__name__).info(
|
||||
|
|
@ -167,7 +173,9 @@ try:
|
|||
configure_cpu_threads()
|
||||
except ValueError as exc:
|
||||
_raw = os.environ.get("UNSLOTH_CPU_THREADS")
|
||||
raise SystemExit(f"Error: Invalid UNSLOTH_CPU_THREADS value {_raw!r}: {exc}") from None
|
||||
raise SystemExit(
|
||||
f"Error: Invalid UNSLOTH_CPU_THREADS value {_raw!r}: {exc}"
|
||||
) from None
|
||||
|
||||
# Anaconda/conda-forge Python: seed platform._sys_version_cache before any
|
||||
# library import triggers attrs -> rich -> structlog -> platform crash.
|
||||
|
|
@ -219,7 +227,9 @@ def _read_studio_install_id() -> str:
|
|||
/api/health emits "" and the launcher accepts any healthy backend.
|
||||
Carries no install-path info (matters when Studio runs -H 0.0.0.0)."""
|
||||
try:
|
||||
token = (_STUDIO_ROOT_RESOLVED / "share" / "studio_install_id").read_text().strip()
|
||||
token = (
|
||||
(_STUDIO_ROOT_RESOLVED / "share" / "studio_install_id").read_text().strip()
|
||||
)
|
||||
except (OSError, ValueError):
|
||||
return ""
|
||||
return token if _STUDIO_INSTALL_ID_RE.fullmatch(token) else ""
|
||||
|
|
@ -311,7 +321,9 @@ def get_unsloth_version() -> str:
|
|||
except PackageNotFoundError:
|
||||
pass
|
||||
|
||||
version_file = _Path(__file__).resolve().parents[2] / "unsloth" / "models" / "_utils.py"
|
||||
version_file = (
|
||||
_Path(__file__).resolve().parents[2] / "unsloth" / "models" / "_utils.py"
|
||||
)
|
||||
try:
|
||||
for line in version_file.read_text(encoding = "utf-8").splitlines():
|
||||
if line.startswith("__version__ = "):
|
||||
|
|
@ -417,7 +429,9 @@ async def lifespan(app: FastAPI):
|
|||
print(f"WARNING: {_msg}", flush = True)
|
||||
except Exception as _probe_exc:
|
||||
import structlog as _structlog
|
||||
_structlog.get_logger(__name__).debug("llama.cpp startup probes failed: %s", _probe_exc)
|
||||
_structlog.get_logger(__name__).debug(
|
||||
"llama.cpp startup probes failed: %s", _probe_exc
|
||||
)
|
||||
|
||||
from storage.studio_db import cleanup_orphaned_runs
|
||||
|
||||
|
|
@ -425,7 +439,9 @@ async def lifespan(app: FastAPI):
|
|||
cleanup_orphaned_runs()
|
||||
except Exception as exc:
|
||||
import structlog
|
||||
structlog.get_logger(__name__).warning("cleanup_orphaned_runs failed at startup: %s", exc)
|
||||
structlog.get_logger(__name__).warning(
|
||||
"cleanup_orphaned_runs failed at startup: %s", exc
|
||||
)
|
||||
|
||||
_start_helper_precache_if_enabled()
|
||||
|
||||
|
|
@ -528,7 +544,9 @@ def _build_csp(script_nonce: "str | None" = None) -> str:
|
|||
"https://*.googleusercontent.com wss://*.googleusercontent.com"
|
||||
)
|
||||
else:
|
||||
connect_src = "'self' https://huggingface.co https://datasets-server.huggingface.co"
|
||||
connect_src = (
|
||||
"'self' https://huggingface.co https://datasets-server.huggingface.co"
|
||||
)
|
||||
|
||||
return (
|
||||
"default-src 'self'; "
|
||||
|
|
@ -630,7 +648,11 @@ async def _send_411(send) -> None:
|
|||
|
||||
async def _send_413(send, total_bytes: int, max_bytes: int) -> None:
|
||||
payload = _json_for_413.dumps(
|
||||
{"detail": (f"Request body too large ({total_bytes:,} bytes; max {max_bytes:,}).")},
|
||||
{
|
||||
"detail": (
|
||||
f"Request body too large ({total_bytes:,} bytes; max {max_bytes:,})."
|
||||
)
|
||||
},
|
||||
).encode("utf-8")
|
||||
await send(
|
||||
{
|
||||
|
|
@ -814,7 +836,9 @@ app.include_router(data_recipe_router, prefix = "/api/data-recipe", tags = ["dat
|
|||
app.include_router(llama_router, prefix = "/api/llama", tags = ["llama"])
|
||||
app.include_router(export_router, prefix = "/api/export", tags = ["export"])
|
||||
app.include_router(rag_router, prefix = "/api/rag", tags = ["rag"])
|
||||
app.include_router(training_history_router, prefix = "/api/train", tags = ["training-history"])
|
||||
app.include_router(
|
||||
training_history_router, prefix = "/api/train", tags = ["training-history"]
|
||||
)
|
||||
app.include_router(hub_inventory_router, prefix = "/api/hub", tags = ["hub"])
|
||||
app.include_router(hub_datasets_router, prefix = "/api/hub/datasets", tags = ["hub"])
|
||||
|
||||
|
|
@ -856,7 +880,9 @@ async def health_check(request: Request):
|
|||
from auth.authentication import get_current_subject as _gcs
|
||||
from fastapi.security import HTTPAuthorizationCredentials
|
||||
|
||||
creds = HTTPAuthorizationCredentials(scheme = "Bearer", credentials = auth.split(" ", 1)[1])
|
||||
creds = HTTPAuthorizationCredentials(
|
||||
scheme = "Bearer", credentials = auth.split(" ", 1)[1]
|
||||
)
|
||||
# Must await: a bare coroutine is truthy and would skip the auth check
|
||||
subject = await _gcs(creds)
|
||||
except HTTPException:
|
||||
|
|
@ -892,12 +918,16 @@ def studio_update_status(_current_subject: str = Depends(get_current_subject)):
|
|||
"/api/studio/download-transport-capabilities",
|
||||
response_model = TransportCapabilities,
|
||||
)
|
||||
def studio_download_transport_capabilities(_current_subject: str = Depends(get_current_subject)):
|
||||
def studio_download_transport_capabilities(
|
||||
_current_subject: str = Depends(get_current_subject),
|
||||
):
|
||||
return asdict(get_download_transport_capabilities())
|
||||
|
||||
|
||||
@app.post("/api/shutdown")
|
||||
async def shutdown_server(request: Request, current_subject: str = Depends(get_current_subject)):
|
||||
async def shutdown_server(
|
||||
request: Request, current_subject: str = Depends(get_current_subject)
|
||||
):
|
||||
"""Gracefully shut down the Unsloth Studio server.
|
||||
|
||||
Called by the frontend quit dialog so users can stop the server from the UI
|
||||
|
|
|
|||
|
|
@ -24,13 +24,17 @@ class DesktopLoginRequest(BaseModel):
|
|||
class RefreshTokenRequest(BaseModel):
|
||||
"""Refresh token payload to obtain new access + refresh tokens."""
|
||||
|
||||
refresh_token: str = Field(..., description = "Refresh token from a previous login or refresh")
|
||||
refresh_token: str = Field(
|
||||
..., description = "Refresh token from a previous login or refresh"
|
||||
)
|
||||
|
||||
|
||||
class AuthStatusResponse(BaseModel):
|
||||
"""Indicate whether the seeded admin auth flow is ready."""
|
||||
|
||||
initialized: bool = Field(..., description = "True if the auth database contains a login user")
|
||||
initialized: bool = Field(
|
||||
..., description = "True if the auth database contains a login user"
|
||||
)
|
||||
default_username: str = Field(
|
||||
"unsloth",
|
||||
description = "Default admin username for first-boot UI prefill.",
|
||||
|
|
@ -71,7 +75,9 @@ class ApiKeyResponse(BaseModel):
|
|||
|
||||
id: int
|
||||
name: str
|
||||
key_prefix: str = Field(..., description = "First 8 characters after sk-unsloth- for display")
|
||||
key_prefix: str = Field(
|
||||
..., description = "First 8 characters after sk-unsloth- for display"
|
||||
)
|
||||
created_at: str
|
||||
last_used_at: Optional[str] = None
|
||||
expires_at: Optional[str] = None
|
||||
|
|
|
|||
|
|
@ -101,7 +101,9 @@ class SeedInspectUploadRequest(BaseModel):
|
|||
if not self.block_id:
|
||||
raise ValueError("block_id is required when using file_ids")
|
||||
if self.file_names is None or len(self.file_ids) != len(self.file_names):
|
||||
raise ValueError("file_names must be provided and same length as file_ids")
|
||||
raise ValueError(
|
||||
"file_names must be provided and same length as file_ids"
|
||||
)
|
||||
if has_legacy:
|
||||
if not self.filename:
|
||||
raise ValueError("filename is required when using content_base64")
|
||||
|
|
|
|||
|
|
@ -21,7 +21,11 @@ def _validate_save_directory(value: str) -> str:
|
|||
if any(ch in raw for ch in ("\r", "\n")):
|
||||
raise ValueError("save_directory may not contain control characters")
|
||||
path = Path(raw).expanduser()
|
||||
path_parts = (*path.parts, *PureWindowsPath(raw).parts, *raw.replace("\\", "/").split("/"))
|
||||
path_parts = (
|
||||
*path.parts,
|
||||
*PureWindowsPath(raw).parts,
|
||||
*raw.replace("\\", "/").split("/"),
|
||||
)
|
||||
if any(len(part) > 255 for part in path_parts if part not in ("", ".", "/", "\\")):
|
||||
raise ValueError("save_directory path components must be <= 255 characters")
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ class LoadRequest(BaseModel):
|
|||
native_path_lease: Optional[str] = Field(
|
||||
None, description = "Frontend-visible signed native path grant"
|
||||
)
|
||||
hf_token: Optional[str] = Field(None, description = "HuggingFace token for gated models")
|
||||
hf_token: Optional[str] = Field(
|
||||
None, description = "HuggingFace token for gated models"
|
||||
)
|
||||
max_seq_length: int = Field(
|
||||
0,
|
||||
ge = 0,
|
||||
|
|
@ -49,7 +51,9 @@ class LoadRequest(BaseModel):
|
|||
|
||||
@field_validator("chat_template_override")
|
||||
@classmethod
|
||||
def normalize_blank_chat_template_override(cls, value: Optional[str]) -> Optional[str]:
|
||||
def normalize_blank_chat_template_override(
|
||||
cls, value: Optional[str]
|
||||
) -> Optional[str]:
|
||||
if value is not None and value.strip() == "":
|
||||
return None
|
||||
return value
|
||||
|
|
@ -121,7 +125,9 @@ class ValidateModelRequest(BaseModel):
|
|||
native_path_lease: Optional[str] = Field(
|
||||
None, description = "Frontend-visible signed native path grant"
|
||||
)
|
||||
hf_token: Optional[str] = Field(None, description = "HuggingFace token for gated models")
|
||||
hf_token: Optional[str] = Field(
|
||||
None, description = "HuggingFace token for gated models"
|
||||
)
|
||||
gguf_variant: Optional[str] = Field(
|
||||
None, description = "GGUF quantization variant (e.g. 'Q4_K_M')"
|
||||
)
|
||||
|
|
@ -136,7 +142,9 @@ class ValidateModelResponse(BaseModel):
|
|||
valid: bool = Field(..., description = "Whether the model identifier looks valid")
|
||||
message: str = Field(..., description = "Human-readable validation message")
|
||||
identifier: Optional[str] = Field(None, description = "Resolved model identifier")
|
||||
display_name: Optional[str] = Field(None, description = "Display name derived from identifier")
|
||||
display_name: Optional[str] = Field(
|
||||
None, description = "Display name derived from identifier"
|
||||
)
|
||||
is_gguf: bool = Field(False, description = "Whether this is a GGUF model (llama.cpp)")
|
||||
is_lora: bool = Field(False, description = "Whether this is a LoRA adapter")
|
||||
is_vision: bool = Field(False, description = "Whether this is a vision-capable model")
|
||||
|
|
@ -154,10 +162,16 @@ class GenerateRequest(BaseModel):
|
|||
temperature: float = Field(0.6, ge = 0.0, le = 2.0, description = "Sampling temperature")
|
||||
top_p: float = Field(0.95, ge = 0.0, le = 1.0, description = "Top-p sampling")
|
||||
top_k: int = Field(20, ge = -1, le = 100, description = "Top-k sampling")
|
||||
max_new_tokens: int = Field(2048, ge = 1, le = 4096, description = "Maximum tokens to generate")
|
||||
repetition_penalty: float = Field(1.0, ge = 1.0, le = 2.0, description = "Repetition penalty")
|
||||
max_new_tokens: int = Field(
|
||||
2048, ge = 1, le = 4096, description = "Maximum tokens to generate"
|
||||
)
|
||||
repetition_penalty: float = Field(
|
||||
1.0, ge = 1.0, le = 2.0, description = "Repetition penalty"
|
||||
)
|
||||
presence_penalty: float = Field(0.0, ge = 0.0, le = 2.0, description = "Presence penalty")
|
||||
image_base64: Optional[str] = Field(None, description = "Base64 encoded image for vision models")
|
||||
image_base64: Optional[str] = Field(
|
||||
None, description = "Base64 encoded image for vision models"
|
||||
)
|
||||
|
||||
|
||||
class LoadResponse(BaseModel):
|
||||
|
|
@ -168,13 +182,19 @@ class LoadResponse(BaseModel):
|
|||
display_name: str = Field(..., description = "Display name of the model")
|
||||
is_vision: bool = Field(False, description = "Whether model is a vision model")
|
||||
is_lora: bool = Field(False, description = "Whether model is a LoRA adapter")
|
||||
is_gguf: bool = Field(False, description = "Whether model is a GGUF model (llama.cpp)")
|
||||
is_gguf: bool = Field(
|
||||
False, description = "Whether model is a GGUF model (llama.cpp)"
|
||||
)
|
||||
is_diffusion: bool = Field(
|
||||
False, description = "Whether model is a block-diffusion model (DiffusionGemma)"
|
||||
)
|
||||
is_audio: bool = Field(False, description = "Whether model is a TTS audio model")
|
||||
audio_type: Optional[str] = Field(None, description = "Audio codec type: snac, csm, bicodec, dac")
|
||||
has_audio_input: bool = Field(False, description = "Whether model accepts audio input (ASR)")
|
||||
audio_type: Optional[str] = Field(
|
||||
None, description = "Audio codec type: snac, csm, bicodec, dac"
|
||||
)
|
||||
has_audio_input: bool = Field(
|
||||
False, description = "Whether model accepts audio input (ASR)"
|
||||
)
|
||||
inference: dict = Field(
|
||||
..., description = "Inference parameters (temperature, top_p, top_k, min_p)"
|
||||
)
|
||||
|
|
@ -274,7 +294,9 @@ class LoadProgressResponse(BaseModel):
|
|||
0,
|
||||
description = "Total bytes across all GGUF shards for the active model.",
|
||||
)
|
||||
fraction: float = Field(0.0, description = "bytes_loaded / bytes_total, clamped to 0..1.")
|
||||
fraction: float = Field(
|
||||
0.0, description = "bytes_loaded / bytes_total, clamped to 0..1."
|
||||
)
|
||||
|
||||
|
||||
class InferenceStatusResponse(BaseModel):
|
||||
|
|
@ -287,17 +309,34 @@ class InferenceStatusResponse(BaseModel):
|
|||
None,
|
||||
description = "Loadable identifier for the active model.",
|
||||
)
|
||||
is_vision: bool = Field(False, description = "Whether the active model is a vision model")
|
||||
is_gguf: bool = Field(False, description = "Whether the active model is a GGUF model (llama.cpp)")
|
||||
is_diffusion: bool = Field(
|
||||
False, description = "Whether the active model is a block-diffusion model (DiffusionGemma)"
|
||||
is_vision: bool = Field(
|
||||
False, description = "Whether the active model is a vision model"
|
||||
)
|
||||
is_gguf: bool = Field(
|
||||
False, description = "Whether the active model is a GGUF model (llama.cpp)"
|
||||
)
|
||||
is_diffusion: bool = Field(
|
||||
False,
|
||||
description = "Whether the active model is a block-diffusion model (DiffusionGemma)",
|
||||
)
|
||||
gguf_variant: Optional[str] = Field(
|
||||
None, description = "GGUF quantization variant (e.g. Q4_K_M)"
|
||||
)
|
||||
is_audio: bool = Field(
|
||||
False, description = "Whether the active model is a TTS audio model"
|
||||
)
|
||||
audio_type: Optional[str] = Field(
|
||||
None, description = "Audio codec type: snac, csm, bicodec, dac"
|
||||
)
|
||||
has_audio_input: bool = Field(
|
||||
False, description = "Whether model accepts audio input (ASR)"
|
||||
)
|
||||
loading: List[str] = Field(
|
||||
default_factory = list, description = "Models currently being loaded"
|
||||
)
|
||||
loaded: List[str] = Field(
|
||||
default_factory = list, description = "Models currently loaded"
|
||||
)
|
||||
gguf_variant: Optional[str] = Field(None, description = "GGUF quantization variant (e.g. Q4_K_M)")
|
||||
is_audio: bool = Field(False, description = "Whether the active model is a TTS audio model")
|
||||
audio_type: Optional[str] = Field(None, description = "Audio codec type: snac, csm, bicodec, dac")
|
||||
has_audio_input: bool = Field(False, description = "Whether model accepts audio input (ASR)")
|
||||
loading: List[str] = Field(default_factory = list, description = "Models currently being loaded")
|
||||
loaded: List[str] = Field(default_factory = list, description = "Models currently loaded")
|
||||
inference: Optional[Dict[str, Any]] = Field(
|
||||
None, description = "Recommended inference parameters for the active model"
|
||||
)
|
||||
|
|
@ -322,7 +361,9 @@ class InferenceStatusResponse(BaseModel):
|
|||
supports_tools: bool = Field(
|
||||
False, description = "Whether the active model supports tool calling"
|
||||
)
|
||||
context_length: Optional[int] = Field(None, description = "Context length of the active model")
|
||||
context_length: Optional[int] = Field(
|
||||
None, description = "Context length of the active model"
|
||||
)
|
||||
max_context_length: Optional[int] = Field(
|
||||
None,
|
||||
description = "Maximum context length currently available for the active model",
|
||||
|
|
@ -651,7 +692,9 @@ class ChatCompletionRequest(BaseModel):
|
|||
parallel_tool_calls: Optional[bool] = Field(
|
||||
None, description = "Whether to enable parallel function calling during tool use."
|
||||
)
|
||||
seed: Optional[int] = Field(None, description = "Best-effort deterministic sampling seed.")
|
||||
seed: Optional[int] = Field(
|
||||
None, description = "Best-effort deterministic sampling seed."
|
||||
)
|
||||
stream_options: Optional[dict] = Field(
|
||||
None,
|
||||
description = 'Streaming options, e.g. {"include_usage": true} to emit a final usage chunk.',
|
||||
|
|
@ -659,7 +702,9 @@ class ChatCompletionRequest(BaseModel):
|
|||
|
||||
# ── Unsloth extensions (ignored by standard OpenAI clients) ──
|
||||
top_k: int = Field(20, ge = -1, le = 100, description = "[x-unsloth] Top-k sampling")
|
||||
min_p: float = Field(0.01, ge = 0.0, le = 1.0, description = "[x-unsloth] Min-p sampling threshold")
|
||||
min_p: float = Field(
|
||||
0.01, ge = 0.0, le = 1.0, description = "[x-unsloth] Min-p sampling threshold"
|
||||
)
|
||||
repetition_penalty: float = Field(
|
||||
1.0, ge = 1.0, le = 2.0, description = "[x-unsloth] Repetition penalty"
|
||||
)
|
||||
|
|
@ -935,7 +980,9 @@ class ChatCompletionRequest(BaseModel):
|
|||
if not tc_id:
|
||||
continue
|
||||
function = tc.get("function")
|
||||
function_name = function.get("name") if isinstance(function, dict) else None
|
||||
function_name = (
|
||||
function.get("name") if isinstance(function, dict) else None
|
||||
)
|
||||
if msg.name and function_name == msg.name:
|
||||
name_match = (tc_id, asst_idx, tc_idx)
|
||||
break
|
||||
|
|
@ -1030,7 +1077,9 @@ class ChoiceDelta(BaseModel):
|
|||
content: Optional[str] = None
|
||||
|
||||
|
||||
OpenAIFinishReason = Literal["stop", "length", "tool_calls", "content_filter", "function_call"]
|
||||
OpenAIFinishReason = Literal[
|
||||
"stop", "length", "tool_calls", "content_filter", "function_call"
|
||||
]
|
||||
|
||||
|
||||
class ChunkChoice(BaseModel):
|
||||
|
|
@ -1183,13 +1232,17 @@ class ResponsesFunctionCallInputItem(BaseModel):
|
|||
"""
|
||||
|
||||
type: Literal["function_call"]
|
||||
id: Optional[str] = Field(None, description = "Item id assigned by the server (e.g. fc_...)")
|
||||
id: Optional[str] = Field(
|
||||
None, description = "Item id assigned by the server (e.g. fc_...)"
|
||||
)
|
||||
call_id: str = Field(
|
||||
...,
|
||||
description = "Correlation id matching a function_call_output on the next turn.",
|
||||
)
|
||||
name: str
|
||||
arguments: str = Field(..., description = "JSON string of the arguments the model produced.")
|
||||
arguments: str = Field(
|
||||
..., description = "JSON string of the arguments the model produced."
|
||||
)
|
||||
status: Optional[Literal["in_progress", "completed", "incomplete"]] = None
|
||||
|
||||
|
||||
|
|
@ -1276,7 +1329,9 @@ class ResponsesRequest(BaseModel):
|
|||
default = [],
|
||||
description = "Input text or list of messages / function_call / function_call_output items",
|
||||
)
|
||||
instructions: Optional[str] = Field(None, description = "System / developer instructions")
|
||||
instructions: Optional[str] = Field(
|
||||
None, description = "System / developer instructions"
|
||||
)
|
||||
temperature: Optional[float] = Field(None, ge = 0.0, le = 2.0)
|
||||
top_p: Optional[float] = Field(None, ge = 0.0, le = 1.0)
|
||||
max_output_tokens: Optional[int] = Field(None, ge = 1)
|
||||
|
|
@ -1364,7 +1419,9 @@ class ResponsesOutputFunctionCall(BaseModel):
|
|||
id: str = Field(default_factory = lambda: f"fc_{uuid.uuid4().hex[:12]}")
|
||||
call_id: str
|
||||
name: str
|
||||
arguments: str = Field(..., description = "JSON string of the arguments the model produced.")
|
||||
arguments: str = Field(
|
||||
..., description = "JSON string of the arguments the model produced."
|
||||
)
|
||||
status: Literal["completed", "in_progress", "incomplete"] = "completed"
|
||||
|
||||
|
||||
|
|
@ -1477,12 +1534,16 @@ def _merge_anthropic_system(system: Any, additions: list[str]) -> Any:
|
|||
if not additions:
|
||||
return system
|
||||
|
||||
addition_blocks = [{"type": "text", "text": text} for text in additions if text.strip()]
|
||||
addition_blocks = [
|
||||
{"type": "text", "text": text} for text in additions if text.strip()
|
||||
]
|
||||
if not addition_blocks:
|
||||
return system
|
||||
|
||||
if system is None:
|
||||
return addition_blocks[0]["text"] if len(addition_blocks) == 1 else addition_blocks
|
||||
return (
|
||||
addition_blocks[0]["text"] if len(addition_blocks) == 1 else addition_blocks
|
||||
)
|
||||
if isinstance(system, str):
|
||||
return "\n\n".join([system, *[block["text"] for block in addition_blocks]])
|
||||
if isinstance(system, list):
|
||||
|
|
@ -1561,7 +1622,9 @@ class AnthropicMessagesRequest(BaseModel):
|
|||
|
||||
normalized = dict(data)
|
||||
normalized["messages"] = normalized_messages
|
||||
normalized["system"] = _merge_anthropic_system(normalized.get("system"), system_additions)
|
||||
normalized["system"] = _merge_anthropic_system(
|
||||
normalized.get("system"), system_additions
|
||||
)
|
||||
return normalized
|
||||
|
||||
|
||||
|
|
@ -1587,7 +1650,9 @@ class AnthropicResponseToolUseBlock(BaseModel):
|
|||
input: dict
|
||||
|
||||
|
||||
AnthropicResponseBlock = Union[AnthropicResponseTextBlock, AnthropicResponseToolUseBlock]
|
||||
AnthropicResponseBlock = Union[
|
||||
AnthropicResponseTextBlock, AnthropicResponseToolUseBlock
|
||||
]
|
||||
|
||||
|
||||
class AnthropicMessagesResponse(BaseModel):
|
||||
|
|
|
|||
|
|
@ -53,5 +53,7 @@ class McpServerImportRequest(BaseModel):
|
|||
|
||||
class McpServerImportResult(BaseModel):
|
||||
created: list[McpServerResponse] = Field(default_factory = list)
|
||||
skipped: list[str] = Field(default_factory = list) # display names skipped as duplicates
|
||||
skipped: list[str] = Field(
|
||||
default_factory = list
|
||||
) # display names skipped as duplicates
|
||||
errors: list[str] = Field(default_factory = list)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ ModelType = Literal["text", "vision", "audio", "embeddings"]
|
|||
class CheckpointInfo(BaseModel):
|
||||
"""Information about a discovered checkpoint directory."""
|
||||
|
||||
display_name: str = Field(..., description = "User-friendly checkpoint name (folder name)")
|
||||
display_name: str = Field(
|
||||
..., description = "User-friendly checkpoint name (folder name)"
|
||||
)
|
||||
path: str = Field(..., description = "Full path to the checkpoint directory")
|
||||
loss: Optional[float] = Field(None, description = "Training loss at this checkpoint")
|
||||
|
||||
|
|
@ -61,23 +63,33 @@ class ModelDetails(BaseModel):
|
|||
None, description = "Model identifier (alias for id, for backward compatibility)"
|
||||
)
|
||||
name: Optional[str] = Field(None, description = "Display name for the model")
|
||||
config: Optional[Dict[str, Any]] = Field(None, description = "Model configuration dictionary")
|
||||
config: Optional[Dict[str, Any]] = Field(
|
||||
None, description = "Model configuration dictionary"
|
||||
)
|
||||
is_vision: bool = Field(False, description = "Whether model is a vision model")
|
||||
is_embedding: bool = Field(
|
||||
False, description = "Whether model is an embedding/sentence-transformer model"
|
||||
)
|
||||
is_lora: bool = Field(False, description = "Whether model is a LoRA adapter")
|
||||
is_gguf: bool = Field(False, description = "Whether model is a GGUF model (llama.cpp format)")
|
||||
is_gguf: bool = Field(
|
||||
False, description = "Whether model is a GGUF model (llama.cpp format)"
|
||||
)
|
||||
is_mlx: bool = Field(
|
||||
False, description = "Whether model is served via the MLX backend (Apple Silicon)"
|
||||
)
|
||||
is_audio: bool = Field(False, description = "Whether model is a TTS audio model")
|
||||
audio_type: Optional[str] = Field(None, description = "Audio codec type: snac, csm, bicodec, dac")
|
||||
has_audio_input: bool = Field(False, description = "Whether model accepts audio input (ASR)")
|
||||
audio_type: Optional[str] = Field(
|
||||
None, description = "Audio codec type: snac, csm, bicodec, dac"
|
||||
)
|
||||
has_audio_input: bool = Field(
|
||||
False, description = "Whether model accepts audio input (ASR)"
|
||||
)
|
||||
model_type: Optional[ModelType] = Field(
|
||||
None, description = "Collapsed model modality: text, vision, audio, or embeddings"
|
||||
)
|
||||
base_model: Optional[str] = Field(None, description = "Base model if this is a LoRA adapter")
|
||||
base_model: Optional[str] = Field(
|
||||
None, description = "Base model if this is a LoRA adapter"
|
||||
)
|
||||
max_position_embeddings: Optional[int] = Field(
|
||||
None, description = "Maximum context length supported by the model"
|
||||
)
|
||||
|
|
@ -90,7 +102,9 @@ class LoRAInfo(BaseModel):
|
|||
"""LoRA adapter or exported model information"""
|
||||
|
||||
display_name: str = Field(..., description = "Display name for the LoRA")
|
||||
adapter_path: str = Field(..., description = "Path to the LoRA adapter or exported model")
|
||||
adapter_path: str = Field(
|
||||
..., description = "Path to the LoRA adapter or exported model"
|
||||
)
|
||||
base_model: Optional[str] = Field(None, description = "Base model identifier")
|
||||
source: Optional[str] = Field(None, description = "'training' or 'exported'")
|
||||
export_type: Optional[str] = Field(
|
||||
|
|
@ -101,21 +115,29 @@ class LoRAInfo(BaseModel):
|
|||
class LoRAScanResponse(BaseModel):
|
||||
"""Response schema for scanning trained LoRA adapters"""
|
||||
|
||||
loras: List[LoRAInfo] = Field(default_factory = list, description = "List of found LoRA adapters")
|
||||
loras: List[LoRAInfo] = Field(
|
||||
default_factory = list, description = "List of found LoRA adapters"
|
||||
)
|
||||
outputs_dir: str = Field(..., description = "Directory that was scanned")
|
||||
|
||||
|
||||
class ModelListResponse(BaseModel):
|
||||
"""Response schema for listing models"""
|
||||
|
||||
models: List[ModelDetails] = Field(default_factory = list, description = "List of models")
|
||||
default_models: List[str] = Field(default_factory = list, description = "List of default model IDs")
|
||||
models: List[ModelDetails] = Field(
|
||||
default_factory = list, description = "List of models"
|
||||
)
|
||||
default_models: List[str] = Field(
|
||||
default_factory = list, description = "List of default model IDs"
|
||||
)
|
||||
|
||||
|
||||
class GgufVariantDetail(BaseModel):
|
||||
"""A single GGUF quantization variant in a HuggingFace repo."""
|
||||
|
||||
filename: str = Field(..., description = "GGUF filename (e.g., 'gemma-3-4b-it-Q4_K_M.gguf')")
|
||||
filename: str = Field(
|
||||
..., description = "GGUF filename (e.g., 'gemma-3-4b-it-Q4_K_M.gguf')"
|
||||
)
|
||||
quant: str = Field(..., description = "Quantization label (e.g., 'Q4_K_M')")
|
||||
size_bytes: int = Field(0, description = "File size in bytes")
|
||||
downloaded: bool = Field(
|
||||
|
|
@ -161,7 +183,9 @@ class LocalModelInfo(BaseModel):
|
|||
class LocalModelListResponse(BaseModel):
|
||||
"""Response schema for listing local/cached models."""
|
||||
|
||||
models_dir: str = Field(..., description = "Directory scanned for custom local models")
|
||||
models_dir: str = Field(
|
||||
..., description = "Directory scanned for custom local models"
|
||||
)
|
||||
hf_cache_dir: Optional[str] = Field(
|
||||
None,
|
||||
description = "HF cache root that was scanned",
|
||||
|
|
@ -179,7 +203,9 @@ class LocalModelListResponse(BaseModel):
|
|||
class AddScanFolderRequest(BaseModel):
|
||||
"""Request body for adding a custom scan folder."""
|
||||
|
||||
path: str = Field(..., description = "Absolute or relative directory path to scan for models")
|
||||
path: str = Field(
|
||||
..., description = "Absolute or relative directory path to scan for models"
|
||||
)
|
||||
|
||||
|
||||
class ScanFolderInfo(BaseModel):
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ from pydantic import BaseModel, Field
|
|||
class ProviderRegistryEntry(BaseModel):
|
||||
"""A supported provider type with its default configuration."""
|
||||
|
||||
provider_type: str = Field(..., description = "Provider identifier (e.g. 'openai', 'mistral')")
|
||||
provider_type: str = Field(
|
||||
..., description = "Provider identifier (e.g. 'openai', 'mistral')"
|
||||
)
|
||||
display_name: str = Field(..., description = "Human-readable provider name")
|
||||
base_url: str = Field(..., description = "Default API base URL")
|
||||
default_models: list[str] = Field(
|
||||
|
|
@ -42,7 +44,9 @@ class ProviderCreate(BaseModel):
|
|||
"""Request to create a saved provider configuration."""
|
||||
|
||||
provider_type: str = Field(..., description = "Provider type from the registry")
|
||||
display_name: str = Field(..., description = "User-chosen label (e.g. 'My OpenAI Key')")
|
||||
display_name: str = Field(
|
||||
..., description = "User-chosen label (e.g. 'My OpenAI Key')"
|
||||
)
|
||||
base_url: Optional[str] = Field(
|
||||
None,
|
||||
description = "Custom base URL (overrides registry default). Omit to use the default.",
|
||||
|
|
@ -54,7 +58,9 @@ class ProviderUpdate(BaseModel):
|
|||
|
||||
display_name: Optional[str] = Field(None, description = "New display name")
|
||||
base_url: Optional[str] = Field(None, description = "New base URL")
|
||||
is_enabled: Optional[bool] = Field(None, description = "Enable or disable this provider")
|
||||
is_enabled: Optional[bool] = Field(
|
||||
None, description = "Enable or disable this provider"
|
||||
)
|
||||
|
||||
|
||||
class ProviderResponse(BaseModel):
|
||||
|
|
@ -77,7 +83,9 @@ class ProviderModelInfo(BaseModel):
|
|||
|
||||
id: str = Field(..., description = "Model ID as expected by the provider API")
|
||||
display_name: str = Field("", description = "Human-readable model name")
|
||||
context_length: Optional[int] = Field(None, description = "Maximum context length in tokens")
|
||||
context_length: Optional[int] = Field(
|
||||
None, description = "Maximum context length in tokens"
|
||||
)
|
||||
owned_by: Optional[str] = Field(None, description = "Model owner/organization")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -21,10 +21,16 @@ class TrainingStopResponse(BaseModel):
|
|||
class TrainingMetricsResponse(BaseModel):
|
||||
"""Response for training metrics history"""
|
||||
|
||||
loss_history: List[float] = Field(default_factory = list, description = "Loss values per step")
|
||||
lr_history: List[float] = Field(default_factory = list, description = "Learning rate per step")
|
||||
loss_history: List[float] = Field(
|
||||
default_factory = list, description = "Loss values per step"
|
||||
)
|
||||
lr_history: List[float] = Field(
|
||||
default_factory = list, description = "Learning rate per step"
|
||||
)
|
||||
step_history: List[int] = Field(default_factory = list, description = "Step numbers")
|
||||
grad_norm_history: List[float] = Field(default_factory = list, description = "Gradient norm values")
|
||||
grad_norm_history: List[float] = Field(
|
||||
default_factory = list, description = "Gradient norm values"
|
||||
)
|
||||
grad_norm_step_history: List[int] = Field(
|
||||
default_factory = list, description = "Step numbers for gradient norm values"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@ class S3Config(BaseModel):
|
|||
|
||||
bucket: str = Field(..., description = "S3 bucket name")
|
||||
region: str = Field("us-east-1", description = "AWS region")
|
||||
prefix: Optional[str] = Field(None, description = "Optional path prefix within bucket")
|
||||
prefix: Optional[str] = Field(
|
||||
None, description = "Optional path prefix within bucket"
|
||||
)
|
||||
access_key_id: Optional[str] = Field(
|
||||
None,
|
||||
alias = "accessKeyId",
|
||||
|
|
@ -58,7 +60,9 @@ class S3Config(BaseModel):
|
|||
def _check_credentials(self) -> "S3Config":
|
||||
# Require either IAM role auth or a full key pair so credentials are
|
||||
# never half-configured.
|
||||
if not self.use_iam_role and not (self.access_key_id and self.secret_access_key):
|
||||
if not self.use_iam_role and not (
|
||||
self.access_key_id and self.secret_access_key
|
||||
):
|
||||
raise ValueError(
|
||||
"s3_config requires either use_iam_role=True or both "
|
||||
"access_key_id and secret_access_key"
|
||||
|
|
@ -77,7 +81,9 @@ def _parse_lr(v: Any) -> float:
|
|||
except (TypeError, ValueError):
|
||||
raise ValueError(f"learning_rate must be parseable as float (got {v!r})")
|
||||
if not (lr > 0.0):
|
||||
raise ValueError(f"learning_rate must be > 0 (got {lr!r}); typical range is 1e-6 .. 1e-3")
|
||||
raise ValueError(
|
||||
f"learning_rate must be > 0 (got {lr!r}); typical range is 1e-6 .. 1e-3"
|
||||
)
|
||||
if lr >= _MAX_LR_VALUE:
|
||||
raise ValueError(
|
||||
f"learning_rate must be < 1.0 (got {lr!r}); "
|
||||
|
|
@ -93,9 +99,11 @@ class TrainingStartRequest(BaseModel):
|
|||
model_name: str = Field(
|
||||
..., description = "Model identifier (e.g., 'unsloth/llama-3-8b-bnb-4bit')"
|
||||
)
|
||||
training_type: Literal["LoRA/QLoRA", "Full Finetuning", "Continued Pretraining"] = Field(
|
||||
...,
|
||||
description = "Training type: 'LoRA/QLoRA', 'Full Finetuning', or 'Continued Pretraining'",
|
||||
training_type: Literal["LoRA/QLoRA", "Full Finetuning", "Continued Pretraining"] = (
|
||||
Field(
|
||||
...,
|
||||
description = "Training type: 'LoRA/QLoRA', 'Full Finetuning', or 'Continued Pretraining'",
|
||||
)
|
||||
)
|
||||
hf_token: Optional[str] = Field(None, description = "HuggingFace token")
|
||||
load_in_4bit: bool = Field(True, description = "Load model in 4-bit quantization")
|
||||
|
|
@ -110,7 +118,9 @@ class TrainingStartRequest(BaseModel):
|
|||
)
|
||||
|
||||
# Dataset parameters
|
||||
hf_dataset: Optional[str] = Field(None, description = "HuggingFace dataset identifier")
|
||||
hf_dataset: Optional[str] = Field(
|
||||
None, description = "HuggingFace dataset identifier"
|
||||
)
|
||||
local_datasets: List[str] = Field(
|
||||
default_factory = list, description = "List of local dataset paths"
|
||||
)
|
||||
|
|
@ -120,8 +130,12 @@ class TrainingStartRequest(BaseModel):
|
|||
format_type: str = Field(..., description = "Dataset format type")
|
||||
subset: Optional[str] = None
|
||||
train_split: Optional[str] = Field("train", description = "Training split name")
|
||||
eval_split: Optional[str] = Field(None, description = "Eval split name. None = auto-detect")
|
||||
eval_steps: float = Field(0.00, description = "Fraction of total steps between evals (0-1)")
|
||||
eval_split: Optional[str] = Field(
|
||||
None, description = "Eval split name. None = auto-detect"
|
||||
)
|
||||
eval_steps: float = Field(
|
||||
0.00, description = "Fraction of total steps between evals (0-1)"
|
||||
)
|
||||
dataset_slice_start: Optional[int] = Field(
|
||||
None, description = "Inclusive start row index for dataset slicing"
|
||||
)
|
||||
|
|
@ -150,7 +164,9 @@ class TrainingStartRequest(BaseModel):
|
|||
if v is None:
|
||||
raise ValueError("batch_size is required")
|
||||
if v < 1 or v > _MAX_BATCH_SIZE:
|
||||
raise ValueError(f"batch_size must be in [1, {_MAX_BATCH_SIZE}] (got {v!r})")
|
||||
raise ValueError(
|
||||
f"batch_size must be in [1, {_MAX_BATCH_SIZE}] (got {v!r})"
|
||||
)
|
||||
return v
|
||||
|
||||
@field_validator("gradient_accumulation_steps")
|
||||
|
|
@ -160,7 +176,8 @@ class TrainingStartRequest(BaseModel):
|
|||
return 1
|
||||
if v < 1 or v > _MAX_GRAD_ACCUM:
|
||||
raise ValueError(
|
||||
f"gradient_accumulation_steps must be in [1, {_MAX_GRAD_ACCUM}] " f"(got {v!r})"
|
||||
f"gradient_accumulation_steps must be in [1, {_MAX_GRAD_ACCUM}] "
|
||||
f"(got {v!r})"
|
||||
)
|
||||
return v
|
||||
|
||||
|
|
@ -181,14 +198,18 @@ class TrainingStartRequest(BaseModel):
|
|||
if v is None:
|
||||
return v
|
||||
if not isinstance(v, int) or v < 0 or v > _MAX_STEPS:
|
||||
raise ValueError(f"max_steps must be a non-negative int <= {_MAX_STEPS} (got {v!r})")
|
||||
raise ValueError(
|
||||
f"max_steps must be a non-negative int <= {_MAX_STEPS} (got {v!r})"
|
||||
)
|
||||
return v
|
||||
|
||||
@field_validator("max_seq_length")
|
||||
@classmethod
|
||||
def _check_max_seq_length(cls, v: int) -> int:
|
||||
if v is None or v < 1 or v > _MAX_SEQ_LENGTH:
|
||||
raise ValueError(f"max_seq_length must be in [1, {_MAX_SEQ_LENGTH}] (got {v!r})")
|
||||
raise ValueError(
|
||||
f"max_seq_length must be in [1, {_MAX_SEQ_LENGTH}] (got {v!r})"
|
||||
)
|
||||
return v
|
||||
|
||||
@field_validator("vision_image_size", mode = "before")
|
||||
|
|
@ -231,7 +252,8 @@ class TrainingStartRequest(BaseModel):
|
|||
return v
|
||||
if not isinstance(v, int) or v < 0 or v > _MAX_STEPS:
|
||||
raise ValueError(
|
||||
f"warmup_steps must be a non-negative int <= {_MAX_STEPS} " f"(got {v!r})"
|
||||
f"warmup_steps must be a non-negative int <= {_MAX_STEPS} "
|
||||
f"(got {v!r})"
|
||||
)
|
||||
return v
|
||||
|
||||
|
|
@ -267,7 +289,9 @@ class TrainingStartRequest(BaseModel):
|
|||
except (TypeError, ValueError):
|
||||
raise ValueError(f"weight_decay must be a number (got {v!r})")
|
||||
if wd < 0 or wd > 10.0:
|
||||
raise ValueError(f"weight_decay must be in [0, 10] (got {wd!r}); typical 0..0.1")
|
||||
raise ValueError(
|
||||
f"weight_decay must be in [0, 10] (got {wd!r}); typical 0..0.1"
|
||||
)
|
||||
return wd
|
||||
|
||||
@field_validator("lora_r")
|
||||
|
|
@ -285,7 +309,9 @@ class TrainingStartRequest(BaseModel):
|
|||
if v is None:
|
||||
return 16
|
||||
if v < 1 or v > _MAX_LORA_ALPHA:
|
||||
raise ValueError(f"lora_alpha must be in [1, {_MAX_LORA_ALPHA}] (got {v!r})")
|
||||
raise ValueError(
|
||||
f"lora_alpha must be in [1, {_MAX_LORA_ALPHA}] (got {v!r})"
|
||||
)
|
||||
return v
|
||||
|
||||
@field_validator("lora_dropout")
|
||||
|
|
@ -314,7 +340,9 @@ class TrainingStartRequest(BaseModel):
|
|||
num_epochs: int = Field(1, description = "Number of training epochs")
|
||||
learning_rate: str = Field("2e-4", description = "Learning rate")
|
||||
batch_size: int = Field(1, description = "Batch size")
|
||||
gradient_accumulation_steps: int = Field(1, description = "Gradient accumulation steps")
|
||||
gradient_accumulation_steps: int = Field(
|
||||
1, description = "Gradient accumulation steps"
|
||||
)
|
||||
warmup_steps: Optional[int] = Field(None, description = "Warmup steps")
|
||||
warmup_ratio: Optional[float] = Field(None, description = "Warmup ratio")
|
||||
max_steps: Optional[int] = Field(None, description = "Maximum training steps")
|
||||
|
|
@ -342,19 +370,31 @@ class TrainingStartRequest(BaseModel):
|
|||
lora_r: int = Field(16, description = "LoRA rank")
|
||||
lora_alpha: int = Field(16, description = "LoRA alpha")
|
||||
lora_dropout: float = Field(0.0, description = "LoRA dropout")
|
||||
target_modules: List[str] = Field(default_factory = list, description = "Target modules for LoRA")
|
||||
gradient_checkpointing: str = Field("", description = "Gradient checkpointing setting")
|
||||
target_modules: List[str] = Field(
|
||||
default_factory = list, description = "Target modules for LoRA"
|
||||
)
|
||||
gradient_checkpointing: str = Field(
|
||||
"", description = "Gradient checkpointing setting"
|
||||
)
|
||||
use_rslora: bool = Field(False, description = "Use RSLoRA")
|
||||
use_loftq: bool = Field(False, description = "Use LoftQ")
|
||||
train_on_completions: bool = Field(False, description = "Train on completions only")
|
||||
|
||||
# Vision-specific LoRA parameters
|
||||
finetune_vision_layers: bool = Field(False, description = "Finetune vision layers")
|
||||
finetune_language_layers: bool = Field(False, description = "Finetune language layers")
|
||||
finetune_attention_modules: bool = Field(False, description = "Finetune attention modules")
|
||||
finetune_language_layers: bool = Field(
|
||||
False, description = "Finetune language layers"
|
||||
)
|
||||
finetune_attention_modules: bool = Field(
|
||||
False, description = "Finetune attention modules"
|
||||
)
|
||||
finetune_mlp_modules: bool = Field(False, description = "Finetune MLP modules")
|
||||
is_dataset_image: bool = Field(False, description = "Whether the dataset contains image data")
|
||||
is_dataset_audio: bool = Field(False, description = "Whether the dataset contains audio data")
|
||||
is_dataset_image: bool = Field(
|
||||
False, description = "Whether the dataset contains image data"
|
||||
)
|
||||
is_dataset_audio: bool = Field(
|
||||
False, description = "Whether the dataset contains audio data"
|
||||
)
|
||||
is_embedding: bool = Field(
|
||||
False, description = "Whether model is an embedding/sentence-transformer model"
|
||||
)
|
||||
|
|
@ -385,7 +425,9 @@ class TrainingStartRequest(BaseModel):
|
|||
def _check_steps_or_epochs(self) -> "TrainingStartRequest":
|
||||
# Each accepts 0 as "use the other"; both 0 means nothing to train.
|
||||
if (self.max_steps is None or self.max_steps == 0) and self.num_epochs == 0:
|
||||
raise ValueError("Either num_epochs or max_steps must be > 0; both cannot be 0.")
|
||||
raise ValueError(
|
||||
"Either num_epochs or max_steps must be > 0; both cannot be 0."
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
|
|
@ -412,7 +454,9 @@ class TrainingStatus(BaseModel):
|
|||
"error",
|
||||
"stopped",
|
||||
] = Field(..., description = "Current phase of training pipeline")
|
||||
is_training_running: bool = Field(..., description = "True if training loop is actively running")
|
||||
is_training_running: bool = Field(
|
||||
..., description = "True if training loop is actively running"
|
||||
)
|
||||
eval_enabled: bool = Field(
|
||||
False,
|
||||
description = "True if evaluation dataset is configured for this training run",
|
||||
|
|
@ -437,7 +481,9 @@ class TrainingProgress(BaseModel):
|
|||
total_steps: int = Field(..., description = "Total training steps")
|
||||
loss: Optional[float] = Field(None, description = "Current loss value")
|
||||
learning_rate: Optional[float] = Field(None, description = "Current learning rate")
|
||||
progress_percent: float = Field(..., description = "Progress percentage (0.0 to 100.0)")
|
||||
progress_percent: float = Field(
|
||||
..., description = "Progress percentage (0.0 to 100.0)"
|
||||
)
|
||||
epoch: Optional[float] = Field(None, description = "Current epoch")
|
||||
elapsed_seconds: Optional[float] = Field(
|
||||
None, description = "Time elapsed since training started"
|
||||
|
|
@ -446,7 +492,9 @@ class TrainingProgress(BaseModel):
|
|||
grad_norm: Optional[float] = Field(
|
||||
None, description = "L2 norm of gradients, computed before gradient clipping"
|
||||
)
|
||||
num_tokens: Optional[int] = Field(None, description = "Total number of tokens processed so far")
|
||||
num_tokens: Optional[int] = Field(
|
||||
None, description = "Total number of tokens processed so far"
|
||||
)
|
||||
eval_loss: Optional[float] = Field(
|
||||
None, description = "Eval loss from the most recent evaluation step"
|
||||
)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue