Skip to content

Commit bcb599b

Browse files
authored
Merge pull request #5 from hfg-gmuend/dev
Merge dev 0.8
2 parents 81e1a3a + 141ff39 commit bcb599b

File tree

4 files changed

+49
-65
lines changed

4 files changed

+49
-65
lines changed

README.md

+11-14
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@ Zoomaker is a command-line tool that helps install AI models, git repositories a
88
- **single source of truth**: all resources are neatly defined in the `zoo.yaml` file
99
- **freeze versions**: know exactly which revision of a resources is installed at any time
1010
- **only download once**: optimize bandwidth and cache your models locally
11-
- **optimize disk usage**: downloaded models are symlinked to the installation folder (small files <5MB are duplicate)
11+
- **optimize disk usage**: downloaded models are cached
1212

1313
## 😻 TL;DR
1414

1515
1. Install Zoomaker `pip install zoomaker`
1616
2. Define your resources in the `zoo.yaml` file
1717
3. Run `zoomaker install` to install them
18-
(on Windows: `zoomaker install --no-symlinks`, see [hints](https://github.com/hfg-gmuend/zoomaker#%EF%B8%8F-limitations-on-windows) below)
1918

2019

2120
## 📦 Installation
@@ -121,7 +120,7 @@ scripts:
121120
start_webui: |
122121
conda activate automatic1111
123122
cd /home/$(whoami)/stable-diffusion-webui/
124-
./webui.sh --theme dark --xformers --no-half
123+
./webui.sh --xformers --no-half
125124
```
126125
</details>
127126

@@ -138,7 +137,7 @@ resources:
138137
rename_to: analog-diffusion-v1.safetensors
139138
```
140139
Please note:
141-
The resource `type: download` can be seen as the last resort. Currently there is no caching or symlinking of web downloads. Recommended to avoid it :)
140+
The resource `type: download` can be seen as the last resort. Existing web downloads are skipped, but no other caching. It is recommended to avoid web downloads :)
142141
</details>
143142

144143
## 🧮 zoo.yaml Structure
@@ -171,25 +170,23 @@ All commands are run from the root of the project, where also your `zoo.yaml` fi
171170
| `zoomaker run <script_name>` | Run CLI scripts as defined in `zoo.yaml` |
172171
| `zoomaker --help` | Get help using the Zoomaker CLI |
173172
| `zoomaker --version` | Show current Zoomaker version |
174-
| `zoomaker --no-symlinks` | Do not use symlinks for installing resources |
175173

176-
## ⚠️ Limitations on Windows
177-
Symlinks are not widely supported on Windows, which limits the caching mechanism used by Zoomaker. To work around this limitation, you can disable symlinks by using the `--no-symlinks` flag with the install command:
178174

179-
```bash
180-
zoomaker install --no-symlinks
181-
```
182-
183-
This will still use the cache directory for checking if files are already cached, but if not, they will be downloaded and duplicated directly to the installation directory, saving bandwidth but increasing disk usage. Alternatively, you can use the [Windows Subsystem for Linux "WSL"](https://docs.microsoft.com/en-us/windows/wsl/install-win10) (don't forget to [enable developer mode](https://docs.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development)) or run Zoomaker as an administrator to enable symlink support on Windows.
184-
185-
## 🤗 Hugging Face Access Token
175+
## 🤗 Hugging Face Access Token and Custom Cache Location
186176

187177
You might be asked for a [Hugging Face Access Token](https://huggingface.co/docs/hub/security-tokens) during `zoomaker install`. Some resources on Hugging Face require accepting the terms of use of the model. You can set your access token by running this command in a terminal. The command `huggingface-cli` is automatically shipped alongside zoomaker.
188178

189179
```bash
190180
huggingface-cli login
191181
```
192182

183+
You can specify a custom cache location by setting the HF_HOME environment variable. The default cache location is `~/.cache/huggingface/`.
184+
185+
```bash
186+
export HF_HOME=/path/to/your/cache
187+
zoomaker install
188+
```
189+
193190
## 🙏 Acknowledgements
194191
- Most of the internal heavy lifting is done be the [huggingface_hub library](https://huggingface.co/docs/huggingface_hub/guides/download) by Hugging Face. Thanks!
195192
- "Zoomaker Safari Hacker Cat" cover image by Alia Tasler, based on this [OpenMoji](https://openmoji.org/library/emoji-1F431-200D-1F4BB/). Thanks!

pyproject.toml

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "zoomaker"
3-
version = "0.7.0"
3+
version = "0.8.0"
44
description = "Zoomaker - Friendly house keeping for your AI model zoo and related resources."
55
authors = ["Benedikt Groß"]
66
readme = "README.md"
@@ -10,8 +10,8 @@ repository = "https://github.com/hfg-gmuend/zoomaker"
1010

1111
[tool.poetry.dependencies]
1212
python = "^3.9"
13-
huggingface-hub = "^0.14.0"
14-
GitPython = "^3.1.31"
13+
huggingface-hub = "^0.23.0"
14+
GitPython = "^3.1.43"
1515

1616
[tool.poetry.scripts]
1717
zoomaker = 'zoomaker:main'

test/tests.py

+8-36
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io
77
import shutil
88
import sys
9+
from huggingface_hub import try_to_load_from_cache, _CACHED_NO_EXIST
910
sys.path.append('..')
1011
from zoomaker import Zoomaker
1112

@@ -67,12 +68,6 @@ def test_install_huggingface(self):
6768
"""
6869
name: test
6970
resources:
70-
models:
71-
- name: analog-diffusion-1.0
72-
src: wavymulder/Analog-Diffusion/analog-diffusion-1.0.safetensors
73-
type: huggingface
74-
install_to: ./tmp/models/Stable-diffusion/
75-
7671
embeddings:
7772
- name: moebius
7873
src: sd-concepts-library/moebius/learned_embeds.bin
@@ -83,39 +78,16 @@ def test_install_huggingface(self):
8378
)
8479
zoomaker = Zoomaker("zoo.yaml")
8580
zoomaker.install()
86-
self.assertTrue(os.path.exists("./tmp/models/Stable-diffusion/analog-diffusion-1.0.safetensors"))
8781
self.assertTrue(os.path.exists("./tmp/embeddings/moebius.bin"))
88-
# smybolik link
89-
self.assertTrue(os.path.islink("./tmp/models/Stable-diffusion/analog-diffusion-1.0.safetensors"))
90-
# no smybolik link, as smaller than 5MB
91-
self.assertFalse(os.path.islink("./tmp/embeddings/moebius.bin"))
9282

93-
def test_install_no_symlinks(self):
94-
create_zoo(
95-
"""
96-
name: test
97-
resources:
98-
models:
99-
- name: analog-diffusion-1.0
100-
src: wavymulder/Analog-Diffusion/analog-diffusion-1.0.safetensors
101-
type: huggingface
102-
install_to: ./tmp/models/Stable-diffusion/
83+
@unittest.skipIf(sys.platform.startswith("win"), "Skipping on Windows")
84+
def test_install_huggingface_cached(self):
85+
filepath = try_to_load_from_cache(
86+
repo_id="sd-concepts-library/moebius", filename="learned_embeds.bin")
10387

104-
embeddings:
105-
- name: moebius
106-
src: sd-concepts-library/moebius/learned_embeds.bin
107-
type: huggingface
108-
install_to: ./tmp/embeddings/
109-
rename_to: moebius.bin
110-
"""
111-
)
112-
zoomaker = Zoomaker("zoo.yaml")
113-
no_symlinks = True
114-
zoomaker.install(no_symlinks)
115-
self.assertTrue(os.path.exists("./tmp/models/Stable-diffusion/analog-diffusion-1.0.safetensors"))
116-
self.assertTrue(os.path.exists("./tmp/embeddings/moebius.bin"))
117-
self.assertFalse(os.path.islink("./tmp/models/Stable-diffusion/analog-diffusion-1.0.safetensors"))
118-
self.assertFalse(os.path.islink("./tmp/embeddings/moebius.bin"))
88+
self.assertTrue(isinstance(filepath, str))
89+
self.assertTrue(os.path.exists(filepath))
90+
self.assertFalse(filepath == _CACHED_NO_EXIST)
11991

12092
def test_install_git(self):
12193
create_zoo(

zoomaker.py

+27-12
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,10 @@ def _check_yaml(self):
3535
if type not in ["huggingface", "git", "download"]:
3636
raise Exception(f"❌ Unknown resource type: {type}")
3737

38-
def install(self, no_symlinks: bool = False):
38+
def install(self):
3939
print(f"👋 ===> {self.yaml_file} <===")
4040
print(f"name: {self.data.get('name', 'N/A')}")
4141
print(f"version: {self.data.get('version', 'N/A')}\n")
42-
if no_symlinks:
43-
print(f"⛔️ installing resources without symlinks ...")
4442
print(f"👇 installing resources ...")
4543
counter = 0;
4644
for group, resources in self.data["resources"].items():
@@ -61,7 +59,8 @@ def install(self, no_symlinks: bool = False):
6159
if type == "huggingface":
6260
repo_id = "/".join(src.split("/")[0:2])
6361
repo_filepath = "/".join(src.split("/")[2:])
64-
downloaded = hf_hub_download(repo_id=repo_id, filename=repo_filepath, local_dir=install_to, revision=revision, local_dir_use_symlinks=False if no_symlinks else "auto")
62+
downloaded = hf_hub_download(repo_id=repo_id, filename=repo_filepath, local_dir=install_to, revision=revision)
63+
print(f"\t size: {self._get_file_size(downloaded)}")
6564
if rename_to:
6665
self._rename_file(downloaded, os.path.join(install_to, rename_to))
6766
# Git
@@ -90,11 +89,17 @@ def install(self, no_symlinks: bool = False):
9089
# Download
9190
else:
9291
filename = self._slugify(os.path.basename(src))
93-
downloaded = self._download_file(src, os.path.join(install_to, filename))
94-
if rename_to:
95-
self._rename_file(downloaded, os.path.join(install_to, rename_to))
96-
if revision:
97-
print(f"\trevision is not supported for download. Ignoring revision: {revision}")
92+
destination = os.path.join(install_to, filename)
93+
destinationRenamed = os.path.join(install_to, rename_to)
94+
if os.path.exists(destination) or os.path.exists(destinationRenamed):
95+
print(f"\t ℹ️ Skipping download: '{filename}' already exists")
96+
else:
97+
downloaded = self._download_file(src, destination)
98+
print(f"\t size: {self._get_file_size(downloaded)}")
99+
if rename_to:
100+
self._rename_file(downloaded, destinationRenamed)
101+
if revision:
102+
print(f"\trevision is not supported for download. Ignoring revision: {revision}")
98103

99104
print(f"\n{counter} resources installed.")
100105

@@ -123,6 +128,17 @@ def _rename_file(self, src, dest):
123128
else:
124129
os.rename(src, dest)
125130

131+
def _get_file_size(self, path):
132+
size = os.stat(path).st_size
133+
if size < 1024:
134+
return f"{size} bytes"
135+
elif size < pow(1024, 2):
136+
return f"{round(size/1024, 2)} KB"
137+
elif size < pow(1024, 3):
138+
return f"{round(size/(pow(1024,2)), 2)} MB"
139+
elif size < pow(1024, 4):
140+
return f"{round(size/(pow(1024,3)), 2)} GB"
141+
126142
def _download_file(self, url, filename):
127143
response = requests.get(url, stream=True)
128144
total_size_in_bytes = int(response.headers.get('content-length', 0))
@@ -160,12 +176,11 @@ def main():
160176
parser = argparse.ArgumentParser(description="Install models, git repos and run scripts defined in the zoo.yaml file.")
161177
parser.add_argument("command", nargs="?", choices=["install", "run"], help="The command to execute.")
162178
parser.add_argument("script", nargs="?", help="The script name to execute.")
163-
parser.add_argument("--no-symlinks", action='store_true', help="Do not create symlinks for the installed resources.")
164-
parser.add_argument("-v", "--version", action='version', help="The current version of the zoomaker.", version="0.7.0")
179+
parser.add_argument("-v", "--version", action="version", help="The current version of the zoomaker.", version="0.8.0")
165180
args = parser.parse_args()
166181

167182
if args.command == "install":
168-
Zoomaker("zoo.yaml").install(args.no_symlinks)
183+
Zoomaker("zoo.yaml").install()
169184
elif args.command == "run":
170185
Zoomaker("zoo.yaml").run(args.script)
171186
else:

0 commit comments

Comments
 (0)