diff --git a/README.md b/README.md index d29666dc9..30beda24b 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,17 @@ If you run on Linux and would like to use the GUI, there is now a port of it as ### Runpod Follow the instructions found in this discussion: https://github.com/bmaltais/kohya_ss/discussions/379 +### MacOS +In the terminal, run + +``` +git clone https://github.com/bmaltais/kohya_ss.git +cd kohya_ss +bash macos_setup.sh +``` + +During the accelerate config screen after running the script answer "This machine", "None", "No" for the remaining questions. + ### Ubuntu In the terminal, run @@ -99,7 +110,17 @@ Run the following commands to install: python .\tools\cudann_1.8_install.py ``` -## Upgrading +## Upgrading MacOS + +When a new release comes out, you can upgrade your repo with the following commands in the root directory: + +```bash +upgrade_macos.sh +``` + +Once the commands have completed successfully you should be ready to use the new version. + +## Upgrading Windows When a new release comes out, you can upgrade your repo with the following commands in the root directory: @@ -192,6 +213,12 @@ This will store your a backup file with your current locally installed pip packa ## Change History +* 2023/03/25 (v21.3.4) + - Added untested support for MacOS base on this gist: https://gist.github.com/jstayco/9f5733f05b9dc29de95c4056a023d645 + + Let me know how this work. From the look of it it appear to be well tought out. I modified a few things to make it fit better with the rest of the code in the repo. + - Fix for issue https://github.com/bmaltais/kohya_ss/issues/433 by implementing default of 0. + - Removed non applicable save_model_as choices for LoRA and TI. * 2023/03/24 (v21.3.3) - Add support for custom user gui files. THey will be created at installation time or when upgrading is missing. You will see two files in the root of the folder. One named `gui-user.bat` and the other `gui-user.ps1`. Edit the file based on your prefered terminal. Simply add the parameters you want to pass the gui in there and execute it to start the gui with them. Enjoy! * 2023/03/23 (v21.3.2) diff --git a/gui_macos.sh b/gui_macos.sh new file mode 100755 index 000000000..4a0bfb8e6 --- /dev/null +++ b/gui_macos.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Activate the virtual environment +source venv/bin/activate + +# Validate the requirements and store the exit code +python tools/validate_requirements.py --requirements requirements_macos.txt +exit_code=$? + +# If the exit code is 0, run the kohya_gui.py script with the command-line arguments +if [ $exit_code -eq 0 ]; then + python kohya_gui.py "$@" +fi diff --git a/library/common_gui.py b/library/common_gui.py index 0d37cc5df..8ff6a2704 100644 --- a/library/common_gui.py +++ b/library/common_gui.py @@ -1,4 +1,5 @@ from tkinter import filedialog, Tk +from easygui import msgbox import os import gradio as gr import easygui @@ -60,28 +61,20 @@ def check_if_model_exist(output_name, output_dir, save_model_as): def update_my_data(my_data): - # Update optimizer based on use_8bit_adam flag + # Update the optimizer based on the use_8bit_adam flag use_8bit_adam = my_data.get('use_8bit_adam', False) - if use_8bit_adam: - my_data['optimizer'] = 'AdamW8bit' - elif 'optimizer' not in my_data: - my_data['optimizer'] = 'AdamW' + my_data.setdefault('optimizer', 'AdamW8bit' if use_8bit_adam else 'AdamW') # Update model_list to custom if empty or pretrained_model_name_or_path is not a preset model model_list = my_data.get('model_list', []) - pretrained_model_name_or_path = my_data.get( - 'pretrained_model_name_or_path', '' - ) - if ( - not model_list - or pretrained_model_name_or_path not in ALL_PRESET_MODELS - ): + pretrained_model_name_or_path = my_data.get('pretrained_model_name_or_path', '') + if not model_list or pretrained_model_name_or_path not in ALL_PRESET_MODELS: my_data['model_list'] = 'custom' # Convert epoch and save_every_n_epochs values to int if they are strings for key in ['epoch', 'save_every_n_epochs']: value = my_data.get(key, -1) - if isinstance(value, str) and value: + if isinstance(value, str) and value.isdigit(): my_data[key] = int(value) elif not value: my_data[key] = -1 @@ -90,41 +83,21 @@ def update_my_data(my_data): if my_data.get('LoRA_type', 'Standard') == 'LoCon': my_data['LoRA_type'] = 'LyCORIS/LoCon' - return my_data - - -# def update_my_data(my_data): -# if my_data.get('use_8bit_adam', False) == True: -# my_data['optimizer'] = 'AdamW8bit' -# # my_data['use_8bit_adam'] = False - -# if ( -# my_data.get('optimizer', 'missing') == 'missing' -# and my_data.get('use_8bit_adam', False) == False -# ): -# my_data['optimizer'] = 'AdamW' - -# if my_data.get('model_list', 'custom') == []: -# print('Old config with empty model list. Setting to custom...') -# my_data['model_list'] = 'custom' - -# # If Pretrained model name or path is not one of the preset models then set the preset_model to custom -# if not my_data.get('pretrained_model_name_or_path', '') in ALL_PRESET_MODELS: -# my_data['model_list'] = 'custom' - -# # Fix old config files that contain epoch as str instead of int -# for key in ['epoch', 'save_every_n_epochs']: -# value = my_data.get(key, -1) -# if type(value) == str: -# if value != '': -# my_data[key] = int(value) -# else: -# my_data[key] = -1 - -# if my_data.get('LoRA_type', 'Standard') == 'LoCon': -# my_data['LoRA_type'] = 'LyCORIS/LoCon' + # Update model save choices due to changes for LoRA and TI training + if ( + (my_data.get('LoRA_type') or my_data.get('num_vectors_per_token')) + and my_data.get('save_model_as') not in ['safetensors', 'ckpt'] + ): + message = ( + 'Updating save_model_as to safetensors because the current value in the config file is no longer applicable to {}' + ) + if my_data.get('LoRA_type'): + print(message.format('LoRA')) + if my_data.get('num_vectors_per_token'): + print(message.format('TI')) + my_data['save_model_as'] = 'safetensors' -# return my_data + return my_data def get_dir_and_file(file_path): @@ -604,7 +577,13 @@ def get_pretrained_model_name_or_path_file( set_model_list(model_list, pretrained_model_name_or_path) -def gradio_source_model(): +def gradio_source_model(save_model_as_choices = [ + 'same as source model', + 'ckpt', + 'diffusers', + 'diffusers_safetensors', + 'safetensors', + ]): with gr.Tab('Source model'): # Define the input elements with gr.Row(): @@ -646,13 +625,7 @@ def gradio_source_model(): ) save_model_as = gr.Dropdown( label='Save trained model as', - choices=[ - 'same as source model', - 'ckpt', - 'diffusers', - 'diffusers_safetensors', - 'safetensors', - ], + choices=save_model_as_choices, value='safetensors', ) @@ -954,6 +927,7 @@ def gradio_advanced_training(): max_data_loader_n_workers = gr.Textbox( label='Max num workers for DataLoader', placeholder='(Optional) Override number of epoch. Default: 8', + value="0", ) return ( # use_8bit_adam, diff --git a/lora_gui.py b/lora_gui.py index 5de6a82cc..13223fed2 100644 --- a/lora_gui.py +++ b/lora_gui.py @@ -257,7 +257,8 @@ def open_configuration( with open(file_path, 'r') as f: my_data = json.load(f) print('Loading config...') - # Update values to fix deprecated use_8bit_adam checkbox and set appropriate optimizer if it is set to True + + # Update values to fix deprecated use_8bit_adam checkbox, set appropriate optimizer if it is set to True, etc. my_data = update_my_data(my_data) else: file_path = original_file_path # In case a file_path was provided and the user decide to cancel the open action @@ -648,7 +649,10 @@ def lora_tab( v_parameterization, save_model_as, model_list, - ) = gradio_source_model() + ) = gradio_source_model(save_model_as_choices = [ + 'ckpt', + 'safetensors', + ]) with gr.Tab('Folders'): with gr.Row(): diff --git a/macos_setup.sh b/macos_setup.sh new file mode 100755 index 000000000..d429c656c --- /dev/null +++ b/macos_setup.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# The initial setup script to prep the environment on macOS +# xformers has been omitted as that is for Nvidia GPUs only + +if ! command -v brew >/dev/null; then + echo "Please install homebrew first. This is a requirement for the remaining setup." + echo "You can find that here: https://brew.sh" + exit 1 +fi + +# Install base python packages +echo "Installing Python 3.10 if not found." +brew ls --versions python@3.10 >/dev/null || brew install python@3.10 +echo "Installing Python-TK 3.10 if not found." +brew ls --versions python-tk@3.10 >/dev/null || brew install python-tk@3.10 + +if command -v python3.10 >/dev/null; then + python3.10 -m venv venv + source venv/bin/activate + + # DEBUG ONLY + #pip install pydevd-pycharm~=223.8836.43 + + # Tensorflow installation + if wget https://github.com/apple/tensorflow_macos/releases/download/v0.1alpha3/tensorflow_macos-0.1a3-cp38-cp38-macosx_11_0_arm64.whl /tmp; then + python -m pip install tensorflow==0.1a3 -f https://github.com/apple/tensorflow_macos/releases/download/v0.1alpha3/tensorflow_macos-0.1a3-cp38-cp38-macosx_11_0_arm64.whl + rm -f /tmp/tensorflow_macos-0.1a3-cp38-cp38-macosx_11_0_arm64.whl + fi + + pip install torch==2.0.0 torchvision==0.15.1 -f https://download.pytorch.org/whl/cpu/torch_stable.html + python -m pip install --use-pep517 --upgrade -r requirements_macos.txt + accelerate config + echo -e "Setup finished! Run ./gui.sh to start." +else + echo "Python not found. Please ensure you install Python." + echo "The brew command for Python 3.10 is: brew install python@3.10" + exit 1 +fi \ No newline at end of file diff --git a/textual_inversion_gui.py b/textual_inversion_gui.py index e4117780a..7b8c19cc5 100644 --- a/textual_inversion_gui.py +++ b/textual_inversion_gui.py @@ -570,7 +570,10 @@ def ti_tab( v_parameterization, save_model_as, model_list, - ) = gradio_source_model() + ) = gradio_source_model(save_model_as_choices = [ + 'ckpt', + 'safetensors', + ]) with gr.Tab('Folders'): with gr.Row(): diff --git a/tools/validate_requirements.py b/tools/validate_requirements.py index f158bdc1e..948c21f0c 100644 --- a/tools/validate_requirements.py +++ b/tools/validate_requirements.py @@ -1,11 +1,17 @@ import os import sys import pkg_resources +import argparse + +# Parse command line arguments +parser = argparse.ArgumentParser(description="Validate that requirements are satisfied.") +parser.add_argument('-r', '--requirements', type=str, default='requirements.txt', help="Path to the requirements file.") +args = parser.parse_args() print("Validating that requirements are satisfied.") -# Load the requirements from the requirements.txt file -with open('requirements.txt') as f: +# Load the requirements from the specified requirements file +with open(args.requirements) as f: requirements = f.readlines() # Check each requirement against the installed packages @@ -34,7 +40,7 @@ for requirement, expected_version, actual_version in wrong_version_requirements: print(f" - {requirement} (expected version {expected_version}, found version {actual_version})") upgrade_script = "upgrade.ps1" if os.name == "nt" else "upgrade.sh" - print(f"\nRun \033[33m{upgrade_script}\033[0m or \033[33mpip install -U -r requirements.txt\033[0m to resolve the missing requirements listed above...") + print(f"\nRun \033[33m{upgrade_script}\033[0m or \033[33mpip install -U -r {args.requirements}\033[0m to resolve the missing requirements listed above...") sys.exit(1) diff --git a/upgrade_macos.sh b/upgrade_macos.sh new file mode 100755 index 000000000..2e26c55b1 --- /dev/null +++ b/upgrade_macos.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# Check if there are any changes that need to be committed +if [[ -n $(git status --short) ]]; then + echo "There are changes that need to be committed. Please stash or undo your changes before running this script." >&2 + exit 1 +fi + +# Pull the latest changes from the remote repository +git pull + +# Activate the virtual environment +source venv/bin/activate + +# Upgrade the required packages +pip install --upgrade -r requirements_macos.txt