diff --git a/.gitignore b/.gitignore index fc1384366..c8d67582a 100644 --- a/.gitignore +++ b/.gitignore @@ -41,10 +41,13 @@ test/ft # Temporary requirements requirements_tmp_for_setup.txt -# Version specific -0.13.3 - *.npz presets/*/user_presets/* inputs -outputs \ No newline at end of file +outputs +dataset/** +!dataset/**/ +!dataset/**/.gitkeep +models +data +config.toml \ No newline at end of file diff --git a/.release b/.release index 9236e9182..6c0733cc4 100644 --- a/.release +++ b/.release @@ -1 +1 @@ -v23.0.11 \ No newline at end of file +v23.0.12 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 562a97391..bafee9fed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,21 +54,8 @@ RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ FROM python:3.10-slim as final -ARG UID -ARG VERSION -ARG RELEASE - -LABEL name="bmaltais/kohya_ss" \ - vendor="bmaltais" \ - maintainer="bmaltais" \ - # Dockerfile source repository - url="https://github.com/bmaltais/kohya_ss" \ - version=${VERSION} \ - # This should be a number, incremented with each change - release=${RELEASE} \ - io.k8s.display-name="kohya_ss" \ - summary="Kohya's GUI: This repository provides a Gradio GUI for Kohya's Stable Diffusion trainers(https://github.com/kohya-ss/sd-scripts)." \ - description="The GUI allows you to set the training parameters and generate and run the required CLI commands to train the model. This is the docker image for Kohya's GUI. For more information about this tool, please visit the following website: https://github.com/bmaltais/kohya_ss." +ENV NVIDIA_VISIBLE_DEVICES all +ENV NVIDIA_DRIVER_CAPABILITIES compute,utility # Install runtime dependencies RUN apt-get update && \ @@ -81,6 +68,7 @@ RUN ln -s /usr/lib/x86_64-linux-gnu/libnvinfer.so /usr/lib/x86_64-linux-gnu/libn ln -s /usr/lib/x86_64-linux-gnu/libnvinfer_plugin.so /usr/lib/x86_64-linux-gnu/libnvinfer_plugin.so.7 # Create user +ARG UID RUN groupadd -g $UID $UID && \ useradd -l -u $UID -g $UID -m -s /bin/sh -N $UID @@ -89,21 +77,20 @@ RUN install -d -m 775 -o $UID -g 0 /dataset && \ install -d -m 775 -o $UID -g 0 /licenses && \ install -d -m 775 -o $UID -g 0 /app -# Copy dist and support arbitrary user ids (OpenShift best practice) -COPY --chown=$UID:0 --chmod=775 \ - --from=build /root/.local /home/$UID/.local - -WORKDIR /app -COPY --chown=$UID:0 --chmod=775 . . - # Copy licenses (OpenShift Policy) -COPY --chmod=775 LICENSE.md /licenses/LICENSE.md +COPY --link --chmod=775 LICENSE.md /licenses/LICENSE.md + +# Copy dependencies and code (and support arbitrary uid for OpenShift best practice) +COPY --link --chown=$UID:0 --chmod=775 --from=build /root/.local /home/$UID/.local +COPY --link --chown=$UID:0 --chmod=775 . /app ENV PATH="/home/$UID/.local/bin:$PATH" ENV PYTHONPATH="${PYTHONPATH}:/home/$UID/.local/lib/python3.10/site-packages" ENV LD_PRELOAD=libtcmalloc.so ENV PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python +WORKDIR /app + VOLUME [ "/dataset" ] # 7860: Kohya GUI @@ -116,4 +103,18 @@ STOPSIGNAL SIGINT # Use dumb-init as PID 1 to handle signals properly ENTRYPOINT ["dumb-init", "--"] -CMD ["python3", "kohya_gui.py", "--listen", "0.0.0.0", "--server_port", "7860"] \ No newline at end of file +CMD ["python3", "kohya_gui.py", "--listen", "0.0.0.0", "--server_port", "7860"] + +ARG VERSION +ARG RELEASE +LABEL name="bmaltais/kohya_ss" \ + vendor="bmaltais" \ + maintainer="bmaltais" \ + # Dockerfile source repository + url="https://github.com/bmaltais/kohya_ss" \ + version=${VERSION} \ + # This should be a number, incremented with each change + release=${RELEASE} \ + io.k8s.display-name="kohya_ss" \ + summary="Kohya's GUI: This repository provides a Gradio GUI for Kohya's Stable Diffusion trainers(https://github.com/kohya-ss/sd-scripts)." \ + description="The GUI allows you to set the training parameters and generate and run the required CLI commands to train the model. This is the docker image for Kohya's GUI. For more information about this tool, please visit the following website: https://github.com/bmaltais/kohya_ss." \ No newline at end of file diff --git a/README.md b/README.md index 1891d5a9d..2aa1e0085 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Kohya's GUI -This repository mostly provides a Gradio GUI for [Kohya's Stable Diffusion trainers](https://github.com/kohya-ss/sd-scripts)... but support for Linux OS is also provided through community contributions. Macos is not great at the moment... but might work if the wind blow in the right direction... +This repository primarily provides a Gradio GUI for [Kohya's Stable Diffusion trainers](https://github.com/kohya-ss/sd-scripts). However, support for Linux OS is also offered through community contributions. macOS support is not optimal at the moment but might work if the conditions are favorable. The GUI allows you to set the training parameters and generate and run the required CLI commands to train the model. @@ -30,6 +30,7 @@ The GUI allows you to set the training parameters and generate and run the requi - [Starting GUI Service](#starting-gui-service) - [Launching the GUI on Windows](#launching-the-gui-on-windows) - [Launching the GUI on Linux and macOS](#launching-the-gui-on-linux-and-macos) + - [Custom Path Defaults](#custom-path-defaults) - [LoRA](#lora) - [Sample image generation during training](#sample-image-generation-during-training) - [Troubleshooting](#troubleshooting) @@ -37,6 +38,10 @@ The GUI allows you to set the training parameters and generate and run the requi - [No module called tkinter](#no-module-called-tkinter) - [SDXL training](#sdxl-training) - [Change History](#change-history) + - [2024/03/16 (v23.0.12)](#20240316-v23012) + - [New Features \& Improvements](#new-features--improvements) + - [Software Updates](#software-updates) + - [Recommendations for Users](#recommendations-for-users) - [2024/03/13 (v23.0.11)](#20240313-v23011) - [2024/03/13 (v23.0.9)](#20240313-v2309) - [2024/03/12 (v23.0.8)](#20240312-v2308) @@ -70,11 +75,11 @@ To install the necessary dependencies on a Windows system, follow these steps: 1. Install [Python 3.10.11](https://www.python.org/ftp/python/3.10.11/python-3.10.11-amd64.exe). - During the installation process, ensure that you select the option to add Python to the 'PATH' environment variable. -2. Install [CUDA 11.8 toolkit](https://developer.nvidia.com/cuda-11-8-0-download-archive?target_os=Windows&target_arch=x86_64) +2. Install [CUDA 11.8 toolkit](https://developer.nvidia.com/cuda-11-8-0-download-archive?target_os=Windows&target_arch=x86_64). -2. Install [Git](https://git-scm.com/download/win). +3. Install [Git](https://git-scm.com/download/win). -3. Install the [Visual Studio 2015, 2017, 2019, and 2022 redistributable](https://aka.ms/vs/17/release/vc_redist.x64.exe). +4. Install the [Visual Studio 2015, 2017, 2019, and 2022 redistributable](https://aka.ms/vs/17/release/vc_redist.x64.exe). #### Setup Windows @@ -100,13 +105,13 @@ To set up the project, follow these steps: .\setup.bat ``` - During the accelerate config step use the default values as proposed during the configuration unless you know your hardware demand otherwise. The amount of VRAM on your GPU does not have an impact on the values used. + During the accelerate config step, use the default values as proposed during the configuration unless you know your hardware demands otherwise. The amount of VRAM on your GPU does not impact the values used. #### Optional: CUDNN 8.9.6.50 The following steps are optional but will improve the learning speed for owners of NVIDIA 30X0/40X0 GPUs. These steps enable larger training batch sizes and faster training speeds. -1. Run .\setup.bat and select `2. (Optional) Install cudnn files (if you want to use latest supported cudnn version)` +1. Run `.\setup.bat` and select `2. (Optional) Install cudnn files (if you want to use the latest supported cudnn version)`. ### Linux and macOS @@ -120,7 +125,7 @@ To install the necessary dependencies on a Linux system, ensure that you fulfill apt install python3.10-venv ``` -- Install the CUDA 11.8 Tolkit by following the instructions provided in [this link](https://developer.nvidia.com/cuda-11-8-0-download-archive?target_os=Linux&target_arch=x86_64). +- Install the CUDA 11.8 Toolkit by following the instructions provided in [this link](https://developer.nvidia.com/cuda-11-8-0-download-archive?target_os=Linux&target_arch=x86_64). - Make sure you have Python version 3.10.9 or higher (but lower than 3.11.0) installed on your system. @@ -188,13 +193,13 @@ To install the necessary components for Runpod and run kohya_ss, follow these st ./setup-runpod.sh ``` -5. Run the gui with: +5. Run the GUI with: ```shell ./gui.sh --share --headless ``` - or with this if you expose 7860 directly via the runpod configuration + or with this if you expose 7860 directly via the runpod configuration: ```shell ./gui.sh --listen=0.0.0.0 --headless @@ -204,13 +209,13 @@ To install the necessary components for Runpod and run kohya_ss, follow these st #### Pre-built Runpod template -To run from a pre-built Runpod template you can: +To run from a pre-built Runpod template, you can: -1. Open the Runpod template by clicking on +1. Open the Runpod template by clicking on . -2. Deploy the template on the desired host +2. Deploy the template on the desired host. -3. Once deployed connect to the Runpod on HTTP 3010 to connect to kohya_ss GUI. You can also connect to auto1111 on HTTP 3000. +3. Once deployed, connect to the Runpod on HTTP 3010 to access the kohya_ss GUI. You can also connect to auto1111 on HTTP 3000. ### Docker @@ -235,13 +240,13 @@ If you prefer to use Docker, follow the instructions below: - All training data must be placed in the `dataset` subdirectory, as the Docker container cannot access files from other directories. - The file picker feature is not functional. You need to manually set the folder path and config file path. - Dialogs may not work as expected, and it is recommended to use unique file names to avoid conflicts. - - This dockerfile has been designed to be easily disposable. You can discard the container at any time and docker build it with a new version of the code. To update the system, run update scripts outside of Docker and rebuild using `docker compose down && docker compose up -d --build`. + - This Dockerfile has been designed to be easily disposable. You can discard the container at any time and docker build it with a new version of the code. To update the system, run update scripts outside of Docker and rebuild using `docker compose down && docker compose up -d --build`. If you are running Linux, an alternative Docker container port with fewer limitations is available [here](https://github.com/P2Enjoy/kohya_ss-docker). #### ashleykleynhans runpod docker builds -You may want to use the following Dockerfile repos to build the images: +You may want to use the following Dockerfile repositories to build the images: - Standalone Kohya_ss template: - Auto1111 + Kohya_ss GUI template: @@ -270,9 +275,7 @@ If a new release becomes available, you can upgrade your repository by running t To upgrade your installation on Linux or macOS, follow these steps: -1. Open a terminal and navigate to the root - - directory of the project. +1. Open a terminal and navigate to the root directory of the project. 2. Pull the latest changes from the repository: @@ -322,6 +325,13 @@ To launch the GUI on Linux or macOS, run the `gui.sh` script located in the root gui.sh --listen 127.0.0.1 --server_port 7860 --inbrowser --share ``` +## Custom Path Defaults + +You can now specify custom paths more easily: + +- Simply copy the `config example.toml` file located in the root directory of the repository to `config.toml`. +- Edit the `config.toml` file to adjust paths and settings according to your preferences. + ## LoRA To train a LoRA, you can currently use the `train_network.py` code. You can create a LoRA network by using the all-in-one GUI. @@ -349,7 +359,7 @@ Lines beginning with `#` are comments. You can specify options for the generated - `--l`: Specifies the CFG scale of the generated image. - `--s`: Specifies the number of steps in the generation. -The prompt weighting such as `( )` and `[ ]` are working. +The prompt weighting such as `( )` and `[ ]` is working. ## Troubleshooting @@ -369,54 +379,83 @@ The documentation in this section will be moved to a separate document later. ## Change History +### 2024/03/16 (v23.0.12) + +#### New Features & Improvements + +- **Enhanced Logging and Tracking Capabilities** + - Added support for configuring advanced logging and tracking: + - `wandb_run_name`: Set a custom name for your Weights & Biases runs to easily identify and organize your experiments. + - `log_tracker_name` and `log_tracker_config`: Integrate custom logging trackers with your projects. Specify the tracker name and provide its configuration to enable detailed monitoring and logging of your runs. + +- **Custom Path Defaults** + - You can now specify custom paths more easily: + - Simply copy the `config example.toml` file located in the root directory of the repository to `config.toml`. + - Edit the `config.toml` file to adjust paths and settings according to your preferences. + +#### Software Updates + +- **sd-scripts updated to v0.8.5** + - **Bug Fixes:** + - Corrected an issue where the value of timestep embedding was incorrect during SDXL training. This fix ensures accurate training progress and results. + - Addressed a related inference issue with the generation script, improving the reliability of SDXL model outputs. + - **Note:** The exact impact of this bug is currently unknown, but it's recommended to update to v0.8.5 for anyone engaged in SDXL training to ensure optimal performance and results. + +- **Upgrade of `lycoris_lora` Python Module** + - Updated the `lycoris_lora` module to version 2.2.0.post3. This update may include bug fixes, performance improvements, and new features. + +#### Recommendations for Users + +- To benefit from the latest features and improvements, users are encouraged to update their installations and configurations accordingly. + ### 2024/03/13 (v23.0.11) -- Increase icon size -- More setup fixes +- Increase icon size. +- More setup fixes. ### 2024/03/13 (v23.0.9) -- Reworked how setup can be run to improve Stability Matrix support -- Add support for huggingface based vea path +- Reworked how setup can be run to improve Stability Matrix support. +- Added support for huggingface-based vea path. ### 2024/03/12 (v23.0.8) -- Add the ability to create outout and logs folder if it does not exist +- Add the ability to create output and logs folder if it does not exist ### 2024/03/12 (v23.0.7) -- Fix minor issues related to functions and file path +- Fixed minor issues related to functions and file paths. ### 2024/03/11 (v23.0.6) -- Fix issue with PYTHON path that have "spaces" in them +- Fixed an issue with PYTHON paths that have "spaces" in them. ### 2024/03/11 (v23.0.5) -- Update python module verification -- Remove cudnn module installation in windows +- Updated python module verification. +- Removed cudnn module installation in Windows. ### 2024/03/10 (v23.0.4) -- Update bitsandbytes to 0.43.0 -- Add packaging to runpod setup +- Updated bitsandbytes to 0.43.0. +- Added packaging to runpod setup. ### 2024/03/10 (v23.0.3) -- Fix bug with setup -- Enforce proper python version before running the GUI to prevent issues with execution of the GUI. +- Fixed a bug with setup. +- Enforced proper python version before running the GUI to prevent issues with execution of the GUI. ### 2024/03/10 (v23.0.2) -- Improve validation of path provided by users before running training +- Improved validation of the path provided by users before running training. ### 2024/03/09 (v23.0.1) -- Update bitsandbytes module to 0.43.0 as it provide native windows support -- Minor fixes to code +- Updated bitsandbytes module to 0.43.0 as it provides native Windows support. +- Minor fixes to the code. ### 2024/03/02 (v23.0.0) -- Use sd-scripts release [0.8.4](https://github.com/kohya-ss/sd-scripts/releases/tag/v0.8.4) post commit [fccbee27277d65a8dcbdeeb81787ed4116b92e0b](https://github.com/kohya-ss/sd-scripts/commit/fccbee27277d65a8dcbdeeb81787ed4116b92e0b) -- Major code refactoring thanks to @wkpark , This will make updating sd-script cleaner by keeping sd-scripts files separate from the GUI files. This will also make configuration more streamlined with fewer tabs and more accordion elements. Hope you like the new style. -- This new release is implementing a significant structure change, moving all of the sd-scripts written by kohya under a folder called sd-scripts in the root of this project. This folder is a submodule that will be populated during setup or gui execution. +- Used sd-scripts release [0.8.4](https://github.com/kohya-ss/sd-scripts/releases/tag/v0.8.4) post commit [fccbee27277d65a8dcbdeeb81787ed4116b92e0b](https://github.com/kohya-ss/sd-scripts/commit/fccbee27277d65a8dcbdeeb81787ed4116b92e0b). +- Major code refactoring thanks to @wkpark. This will make updating sd-scripts cleaner by keeping sd-scripts files separate from the GUI files. This will also make configuration more streamlined with fewer tabs and more accordion elements. Hope you like the new style. +- This new release is implementing a significant structure change, moving all of the sd-scripts written by kohya under a folder called sd-scripts in the root of this project. This folder is a submodule that will be populated during setup or GUI execution. diff --git a/config example.toml b/config example.toml new file mode 100644 index 000000000..5c9f1b052 --- /dev/null +++ b/config example.toml @@ -0,0 +1,16 @@ +# Copy this file and name it config.toml +# Edit the values to suit your needs + +# Default folders location +models_dir = "./models" # Pretrained model name or path +train_data_dir = "./data" # Image folder (containing training images subfolders) / Image folder (containing training images) +output_dir = "./outputs" # Output directory for trained model +reg_data_dir = "./data/reg" # Regularisation directory +logging_dir = "./logs" # Logging directory +config_dir = "./presets" # Load/Save Config file +log_tracker_config_dir = "./logs" # Log tracker configs directory +state_dir = "./outputs" # Resume from saved training state +vae_dir = "./models/vae" # VAEs folder path + +# Example custom folder location +# models_dir = "e:/models" # Pretrained model name or path diff --git a/dataset/.gitkeep b/dataset/images/.gitkeep similarity index 100% rename from dataset/.gitkeep rename to dataset/images/.gitkeep diff --git a/dataset/logs/.gitkeep b/dataset/logs/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/dataset/outputs/.gitkeep b/dataset/outputs/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/dataset/regularization/.gitkeep b/dataset/regularization/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/docker-compose.yaml b/docker-compose.yaml index ffcc5897f..e042f2670 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,4 +1,5 @@ version: "3.8" + services: kohya-ss-gui: container_name: kohya-ss-gui @@ -11,22 +12,23 @@ services: ports: - 7860:7860 - 6006:6006 - tty: true - ipc: host environment: - CLI_ARGS: "" SAFETENSORS_FAST_GPU: 1 - DISPLAY: $DISPLAY tmpfs: - /tmp volumes: - /tmp/.X11-unix:/tmp/.X11-unix - ./dataset:/dataset + - ./dataset/images:/app/data + - ./dataset/logs:/app/logs + - ./dataset/outputs:/app/outputs + - ./dataset/regularization:/app/regularization + - ./.cache/config:/app/config - ./.cache/user:/home/1000/.cache - ./.cache/triton:/home/1000/.triton - ./.cache/nv:/home/1000/.nv - ./.cache/keras:/home/1000/.keras - - ./.cache/config:/home/1000/.config + - ./.cache/config:/home/1000/.config # For backward compatibility deploy: resources: reservations: diff --git a/kohya_gui.py b/kohya_gui.py index 78be71d73..aff1e5555 100644 --- a/kohya_gui.py +++ b/kohya_gui.py @@ -1,6 +1,7 @@ import gradio as gr import os import argparse +from kohya_gui.class_gui_config import KohyaSSGUIConfig from dreambooth_gui import dreambooth_tab from finetune_gui import finetune_tab from textual_inversion_gui import ti_tab @@ -24,7 +25,7 @@ def UI(**kwargs): if os.path.exists("./style.css"): with open(os.path.join("./style.css"), "r", encoding="utf8") as file: - log.info("Load CSS...") + log.debug("Load CSS...") css += file.read() + "\n" if os.path.exists("./.release"): @@ -38,6 +39,8 @@ def UI(**kwargs): interface = gr.Blocks( css=css, title=f"Kohya_ss GUI {release}", theme=gr.themes.Default() ) + + config = KohyaSSGUIConfig() with interface: with gr.Tab("Dreambooth"): @@ -46,13 +49,13 @@ def UI(**kwargs): reg_data_dir_input, output_dir_input, logging_dir_input, - ) = dreambooth_tab(headless=headless) + ) = dreambooth_tab(headless=headless, config=config) with gr.Tab("LoRA"): - lora_tab(headless=headless) + lora_tab(headless=headless, config=config) with gr.Tab("Textual Inversion"): - ti_tab(headless=headless) + ti_tab(headless=headless, config=config) with gr.Tab("Finetuning"): - finetune_tab(headless=headless) + finetune_tab(headless=headless, config=config) with gr.Tab("Utilities"): utilities_tab( train_data_dir_input=train_data_dir_input, diff --git a/kohya_gui/basic_caption_gui.py b/kohya_gui/basic_caption_gui.py index 8cb4094bc..0d49fb18c 100644 --- a/kohya_gui/basic_caption_gui.py +++ b/kohya_gui/basic_caption_gui.py @@ -13,15 +13,31 @@ PYTHON = sys.executable def caption_images( - caption_text, - images_dir, - overwrite, - caption_ext, - prefix, - postfix, - find_text, - replace_text, + caption_text: str, + images_dir: str, + overwrite: bool, + caption_ext: str, + prefix: str, + postfix: str, + find_text: str, + replace_text: str, ): + """ + Captions images in a given directory with a given caption text. + + Args: + caption_text (str): The text to be used as the caption. + images_dir (str): The directory containing the images to be captioned. + overwrite (bool): Whether to overwrite existing captions. + caption_ext (str): The file extension for the caption files. + prefix (str): Text to be added before the caption text. + postfix (str): Text to be added after the caption text. + find_text (str): Text to be replaced in the caption files. + replace_text (str): Text to replace the found text in the caption files. + + Returns: + None + """ # Check if images_dir is provided if not images_dir: msgbox( @@ -34,6 +50,7 @@ def caption_images( msgbox('Please provide an extension for the caption files.') return + # Log the captioning process if caption_text: log.info(f'Captioning files in {images_dir} with {caption_text}...') @@ -49,8 +66,10 @@ def caption_images( run_cmd += f' "{images_dir}"' + # Log the command log.info(run_cmd) + # Set the environment variable for the Python path env = os.environ.copy() env['PYTHONPATH'] = fr"{scriptdir}{os.pathsep}{scriptdir}/tools{os.pathsep}{env.get('PYTHONPATH', '')}" @@ -59,16 +78,16 @@ def caption_images( # Check if overwrite option is enabled if overwrite: + # Add prefix and postfix to caption files if prefix or postfix: - # Add prefix and postfix to caption files add_pre_postfix( folder=images_dir, caption_file_ext=caption_ext, prefix=prefix, postfix=postfix, ) + # Find and replace text in caption files if find_text: - # Find and replace text in caption files find_replace( folder_path=images_dir, caption_file_ext=caption_ext, @@ -76,17 +95,29 @@ def caption_images( replace_text=replace_text, ) else: + # Show a message if modification is not possible without overwrite option enabled if prefix or postfix: - # Show a message if modification is not possible without overwrite option enabled msgbox( 'Could not modify caption files with requested change because the "Overwrite existing captions in folder" option is not selected.' ) + # Log the end of the captioning process log.info('Captioning done.') # Gradio UI def gradio_basic_caption_gui_tab(headless=False, default_images_dir=None): + """ + Creates a Gradio tab for basic image captioning. + + Args: + headless (bool, optional): If True, the GUI will be headless (no visible elements). Defaults to False. + default_images_dir (str, optional): The default directory to use for image selection. If not provided, + it defaults to the 'data' directory in the script directory. + + Returns: + None + """ from .common_gui import create_refresh_button # Set default images directory if not provided @@ -95,6 +126,15 @@ def gradio_basic_caption_gui_tab(headless=False, default_images_dir=None): # Function to list directories def list_images_dirs(path): + """ + Lists directories within a specified path and updates the current image directory. + + Parameters: + path (str): The directory path to list image directories from. + + Returns: + list: A list of directories within the specified path. + """ # Allows list_images_dirs to modify current_images_dir outside of this function nonlocal current_images_dir current_images_dir = path @@ -198,7 +238,7 @@ def list_images_dirs(path): # Event handler for dynamic update of dropdown choices images_dir.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_images_dirs(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_images_dirs(path)), inputs=images_dir, outputs=images_dir, show_progress=False, diff --git a/kohya_gui/blip_caption_gui.py b/kohya_gui/blip_caption_gui.py index 525c537ea..a122e10af 100644 --- a/kohya_gui/blip_caption_gui.py +++ b/kohya_gui/blip_caption_gui.py @@ -13,24 +13,44 @@ def caption_images( - train_data_dir, - caption_file_ext, - batch_size, - num_beams, - top_p, - max_length, - min_length, - beam_search, - prefix, - postfix, -): + train_data_dir: str, + caption_file_ext: str, + batch_size: int, + num_beams: int, + top_p: float, + max_length: int, + min_length: int, + beam_search: bool, + prefix: str = "", + postfix: str = "", +) -> None: + """ + Automatically generates captions for images in the specified directory using the BLIP model. + + This function prepares and executes a command-line script to process images in batches, applying advanced + NLP techniques for caption generation. It supports customization of the captioning process through various + parameters like batch size, beam search, and more. Optionally, prefixes and postfixes can be added to captions. + + + Args: + train_data_dir (str): The directory containing the images to be captioned. + caption_file_ext (str): The extension for the caption files. + batch_size (int): The batch size for the captioning process. + num_beams (int): The number of beams to use in the captioning process. + top_p (float): The top p value to use in the captioning process. + max_length (int): The maximum length of the captions. + min_length (int): The minimum length of the captions. + beam_search (bool): Whether to use beam search in the captioning process. + prefix (str): The prefix to add to the captions. + postfix (str): The postfix to add to the captions. + """ # Check if the image folder is provided - if train_data_dir == "": + if not train_data_dir: msgbox("Image folder is missing...") return # Check if the caption file extension is provided - if caption_file_ext == "": + if not caption_file_ext: msgbox("Please provide an extension for the caption files.") return @@ -38,27 +58,26 @@ def caption_images( # Construct the command to run run_cmd = rf'"{PYTHON}" "{scriptdir}/sd-scripts/finetune/make_captions.py"' - run_cmd += f' --batch_size="{int(batch_size)}"' - run_cmd += f' --num_beams="{int(num_beams)}"' + run_cmd += f' --batch_size="{batch_size}"' + run_cmd += f' --num_beams="{num_beams}"' run_cmd += f' --top_p="{top_p}"' - run_cmd += f' --max_length="{int(max_length)}"' - run_cmd += f' --min_length="{int(min_length)}"' + run_cmd += f' --max_length="{max_length}"' + run_cmd += f' --min_length="{min_length}"' if beam_search: run_cmd += f" --beam_search" - if caption_file_ext != "": + if caption_file_ext: run_cmd += f' --caption_extension="{caption_file_ext}"' run_cmd += f' "{train_data_dir}"' run_cmd += f' --caption_weights="https://storage.googleapis.com/sfr-vision-language-research/BLIP/models/model_large_caption.pth"' log.info(run_cmd) + # Set up the environment env = os.environ.copy() - env["PYTHONPATH"] = ( - rf"{scriptdir}{os.pathsep}{scriptdir}/sd-scripts{os.pathsep}{env.get('PYTHONPATH', '')}" - ) + env["PYTHONPATH"] = f"{scriptdir}{os.pathsep}{scriptdir}/sd-scripts{os.pathsep}{env.get('PYTHONPATH', '')}" # Run the command in the sd-scripts folder context - subprocess.run(run_cmd, shell=True, env=env, cwd=rf"{scriptdir}/sd-scripts") + subprocess.run(run_cmd, shell=True, env=env, cwd=f"{scriptdir}/sd-scripts") # Add prefix and postfix add_pre_postfix( @@ -123,7 +142,7 @@ def list_train_dirs(path): with gr.Row(): caption_file_ext = gr.Textbox( label="Caption file extension", - placeholder="Extension for caption file, e.g., .caption, .txt", + placeholder="Extension for caption file (e.g., .caption, .txt)", value=".txt", interactive=True, ) @@ -171,7 +190,7 @@ def list_train_dirs(path): ) train_data_dir.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_train_dirs(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_train_dirs(path)), inputs=train_data_dir, outputs=train_data_dir, show_progress=False, diff --git a/kohya_gui/class_advanced_training.py b/kohya_gui/class_advanced_training.py index bc4adc278..ba3d98640 100644 --- a/kohya_gui/class_advanced_training.py +++ b/kohya_gui/class_advanced_training.py @@ -1,65 +1,114 @@ import gradio as gr import os -from .common_gui import get_folder_path, get_any_file_path, scriptdir, list_files, list_dirs, create_refresh_button +from typing import Tuple +from .common_gui import ( + get_folder_path, + get_any_file_path, + list_files, + list_dirs, + create_refresh_button, +) class AdvancedTraining: - def __init__(self, headless=False, finetuning: bool = False, training_type: str = "", default_vae_dir: str = "", default_output_dir: str = ""): + """ + This class configures and initializes the advanced training settings for a machine learning model, + including options for headless operation, fine-tuning, training type selection, and default directory paths. + + Attributes: + headless (bool): If True, run without the Gradio interface. + finetuning (bool): If True, enables fine-tuning of the model. + training_type (str): Specifies the type of training to perform. + no_token_padding (gr.Checkbox): Checkbox to disable token padding. + gradient_accumulation_steps (gr.Slider): Slider to set the number of gradient accumulation steps. + weighted_captions (gr.Checkbox): Checkbox to enable weighted captions. + """ + + def __init__( + self, + headless: bool = False, + finetuning: bool = False, + training_type: str = "", + config: dict = {}, + ) -> None: + """ + Initializes the AdvancedTraining class with given settings. + + Parameters: + headless (bool): Run in headless mode without GUI. + finetuning (bool): Enable model fine-tuning. + training_type (str): The type of training to be performed. + default_vae_dir (str): Default directory for VAE models. + default_output_dir (str): Default directory for output files. + """ self.headless = headless self.finetuning = finetuning self.training_type = training_type + self.config = config - current_vae_dir = default_vae_dir if default_vae_dir != "" else os.path.join(scriptdir, "vae") - current_state_dir = default_output_dir if default_output_dir != "" else os.path.join(scriptdir, "outputs") + # Determine the current directories for VAE and output, falling back to defaults if not specified. + self.current_vae_dir = self.config.get("vae_dir", "./models/vae") + self.current_state_dir = self.config.get("state_dir", "./outputs") + self.current_log_tracker_config_dir = self.config.get( + "log_tracker_config_dir", "./logs" + ) - def noise_offset_type_change(noise_offset_type): - if noise_offset_type == 'Original': - return ( - gr.Group.update(visible=True), - gr.Group.update(visible=False), - ) + # Define the behavior for changing noise offset type. + def noise_offset_type_change( + noise_offset_type: str, + ) -> Tuple[gr.Group, gr.Group]: + """ + Returns a tuple of Gradio Groups with visibility set based on the noise offset type. + + Parameters: + noise_offset_type (str): The selected noise offset type. + + Returns: + Tuple[gr.Group, gr.Group]: A tuple containing two Gradio Group elements with their visibility set. + """ + if noise_offset_type == "Original": + return (gr.Group(visible=True), gr.Group(visible=False)) else: - return ( - gr.Group.update(visible=False), - gr.Group.update(visible=True), - ) + return (gr.Group(visible=False), gr.Group(visible=True)) + # GUI elements are only visible when not fine-tuning. with gr.Row(visible=not finetuning): - if training_type != "lora": # Not available for LoRA + # Exclude token padding option for LoRA training type. + if training_type != "lora": self.no_token_padding = gr.Checkbox( - label='No token padding', value=False + label="No token padding", value=False ) self.gradient_accumulation_steps = gr.Slider( - label='Gradient accumulate steps', - info='Number of updates steps to accumulate before performing a backward/update pass', - value='1', + label="Gradient accumulate steps", + info="Number of updates steps to accumulate before performing a backward/update pass", + value="1", minimum=1, maximum=120, step=1, ) - self.weighted_captions = gr.Checkbox( - label='Weighted captions', value=False - ) + self.weighted_captions = gr.Checkbox(label="Weighted captions", value=False) with gr.Group(), gr.Row(visible=not finetuning): - self.prior_loss_weight = gr.Number( - label='Prior loss weight', value=1.0 - ) + self.prior_loss_weight = gr.Number(label="Prior loss weight", value=1.0) def list_vae_files(path): - nonlocal current_vae_dir - current_vae_dir = path + self.current_vae_dir = path if not path == "" else "." return list(list_files(path, exts=[".ckpt", ".safetensors"], all=True)) self.vae = gr.Dropdown( - label='VAE (Optional. path to checkpoint of vae to replace for training)', + label="VAE (Optional: Path to checkpoint of vae for training)", interactive=True, - choices=[""] + list_vae_files(current_vae_dir), + choices=[""] + list_vae_files(self.current_vae_dir), value="", allow_custom_value=True, ) - create_refresh_button(self.vae, lambda: None, lambda: {"choices": list_vae_files(current_vae_dir)},"open_folder_small") + create_refresh_button( + self.vae, + lambda: None, + lambda: {"choices": [""] + list_vae_files(self.current_vae_dir)}, + "open_folder_small", + ) self.vae_button = gr.Button( - '📂', elem_id='open_folder_small', visible=(not headless) + "📂", elem_id="open_folder_small", visible=(not headless) ) self.vae_button.click( get_any_file_path, @@ -68,7 +117,7 @@ def list_vae_files(path): ) self.vae.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_vae_files(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_vae_files(path)), inputs=self.vae, outputs=self.vae, show_progress=False, @@ -76,27 +125,27 @@ def list_vae_files(path): with gr.Row(): self.additional_parameters = gr.Textbox( - label='Additional parameters', + label="Additional parameters", placeholder='(Optional) Use to provide additional parameters not handled by the GUI. Eg: --some_parameters "value"', ) with gr.Row(): self.save_every_n_steps = gr.Number( - label='Save every N steps', + label="Save every N steps", value=0, precision=0, - info='(Optional) The model is saved every specified steps', + info="(Optional) The model is saved every specified steps", ) self.save_last_n_steps = gr.Number( - label='Save last N steps', + label="Save last N steps", value=0, precision=0, - info='(Optional) Save only the specified number of models (old models will be deleted)', + info="(Optional) Save only the specified number of models (old models will be deleted)", ) self.save_last_n_steps_state = gr.Number( - label='Save last N steps state', + label="Save last N steps state", value=0, precision=0, - info='(Optional) Save only the specified number of states (old models will be deleted)', + info="(Optional) Save only the specified number of states (old models will be deleted)", ) with gr.Row(): @@ -113,38 +162,38 @@ def full_options_update(full_fp16, full_bf16): ), gr.Checkbox(interactive=full_bf16_active) self.keep_tokens = gr.Slider( - label='Keep n tokens', value='0', minimum=0, maximum=32, step=1 + label="Keep n tokens", value="0", minimum=0, maximum=32, step=1 ) self.clip_skip = gr.Slider( - label='Clip skip', value='1', minimum=1, maximum=12, step=1 + label="Clip skip", value="1", minimum=1, maximum=12, step=1 ) self.max_token_length = gr.Dropdown( - label='Max Token Length', + label="Max Token Length", choices=[ - '75', - '150', - '225', + "75", + "150", + "225", ], - value='75', + value="75", ) - + with gr.Row(): if training_type == "lora": self.fp8_base = gr.Checkbox( - label='fp8 base training (experimental)', + label="fp8 base training (experimental)", info="U-Net and Text Encoder can be trained with fp8 (experimental)", value=False, ) self.full_fp16 = gr.Checkbox( - label='Full fp16 training (experimental)', + label="Full fp16 training (experimental)", value=False, ) self.full_bf16 = gr.Checkbox( - label='Full bf16 training (experimental)', + label="Full bf16 training (experimental)", value=False, - info='Required bitsandbytes >= 0.36.0', + info="Required bitsandbytes >= 0.36.0", ) - + self.full_fp16.change( full_options_update, inputs=[self.full_fp16, self.full_bf16], @@ -158,39 +207,30 @@ def full_options_update(full_fp16, full_bf16): with gr.Row(): self.gradient_checkpointing = gr.Checkbox( - label='Gradient checkpointing', value=False - ) - self.shuffle_caption = gr.Checkbox( - label='Shuffle caption', value=False + label="Gradient checkpointing", value=False ) + self.shuffle_caption = gr.Checkbox(label="Shuffle caption", value=False) self.persistent_data_loader_workers = gr.Checkbox( - label='Persistent data loader', value=False + label="Persistent data loader", value=False ) self.mem_eff_attn = gr.Checkbox( - label='Memory efficient attention', value=False + label="Memory efficient attention", value=False ) with gr.Row(): - # This use_8bit_adam element should be removed in a future release as it is no longer used - # use_8bit_adam = gr.Checkbox( - # label='Use 8bit adam', value=False, visible=False - # ) - # self.xformers = gr.Checkbox(label='Use xformers', value=True, info='Use xformers for CrossAttention') self.xformers = gr.Dropdown( - label='CrossAttention', - choices=['none', 'sdpa', 'xformers'], - value='xformers', - ) - self.color_aug = gr.Checkbox( - label='Color augmentation', value=False + label="CrossAttention", + choices=["none", "sdpa", "xformers"], + value="xformers", ) - self.flip_aug = gr.Checkbox(label='Flip augmentation', value=False) + self.color_aug = gr.Checkbox(label="Color augmentation", value=False) + self.flip_aug = gr.Checkbox(label="Flip augmentation", value=False) self.min_snr_gamma = gr.Slider( - label='Min SNR gamma', + label="Min SNR gamma", value=0, minimum=0, maximum=20, step=1, - info='Recommended value of 5 when used', + info="Recommended value of 5 when used", ) with gr.Row(): # self.sdpa = gr.Checkbox(label='Use sdpa', value=False, info='Use sdpa for CrossAttention') @@ -198,83 +238,83 @@ def full_options_update(full_fp16, full_bf16): label="Don't upscale bucket resolution", value=True ) self.bucket_reso_steps = gr.Slider( - label='Bucket resolution steps', + label="Bucket resolution steps", value=64, minimum=1, maximum=128, ) self.random_crop = gr.Checkbox( - label='Random crop instead of center crop', value=False + label="Random crop instead of center crop", value=False ) self.v_pred_like_loss = gr.Slider( - label='V Pred like loss', + label="V Pred like loss", value=0, minimum=0, maximum=1, step=0.01, - info='Recommended value of 0.5 when used', + info="Recommended value of 0.5 when used", ) with gr.Row(): self.min_timestep = gr.Slider( - label='Min Timestep', + label="Min Timestep", value=0, step=1, minimum=0, maximum=1000, - info='Values greater than 0 will make the model more img2img focussed. 0 = image only', + info="Values greater than 0 will make the model more img2img focussed. 0 = image only", ) self.max_timestep = gr.Slider( - label='Max Timestep', + label="Max Timestep", value=1000, step=1, minimum=0, maximum=1000, - info='Values lower than 1000 will make the model more img2img focussed. 1000 = noise only', + info="Values lower than 1000 will make the model more img2img focussed. 1000 = noise only", ) with gr.Row(): self.noise_offset_type = gr.Dropdown( - label='Noise offset type', + label="Noise offset type", choices=[ - 'Original', - 'Multires', + "Original", + "Multires", ], - value='Original', + value="Original", ) with gr.Row(visible=True) as self.noise_offset_original: self.noise_offset = gr.Slider( - label='Noise offset', + label="Noise offset", value=0, minimum=0, maximum=1, step=0.01, - info='recommended values are 0.05 - 0.15', + info='Recommended values are 0.05 - 0.15', ) self.adaptive_noise_scale = gr.Slider( - label='Adaptive noise scale', + label="Adaptive noise scale", value=0, minimum=-1, maximum=1, step=0.001, - info='(Experimental, Optional) Since the latent is close to a normal distribution, it may be a good idea to specify a value around 1/10 the noise offset.', + info="(Experimental, Optional) Since the latent is close to a normal distribution, it may be a good idea to specify a value around 1/10 the noise offset.", ) with gr.Row(visible=False) as self.noise_offset_multires: self.multires_noise_iterations = gr.Slider( - label='Multires noise iterations', + label="Multires noise iterations", value=0, minimum=0, maximum=64, step=1, - info='enable multires noise (recommended values are 6-10)', + info='Enable multires noise (recommended values are 6-10)', ) self.multires_noise_discount = gr.Slider( - label='Multires noise discount', + label="Multires noise discount", value=0, minimum=0, maximum=1, step=0.01, - info='recommended values are 0.8. For LoRAs with small datasets, 0.1-0.3', + info='Recommended values are 0.8. For LoRAs with small datasets, 0.1-0.3', ) self.noise_offset_type.change( noise_offset_type_change, @@ -286,34 +326,36 @@ def full_options_update(full_fp16, full_bf16): ) with gr.Row(): self.caption_dropout_every_n_epochs = gr.Number( - label='Dropout caption every n epochs', value=0 + label="Dropout caption every n epochs", value=0 ) self.caption_dropout_rate = gr.Slider( - label='Rate of caption dropout', value=0, minimum=0, maximum=1 + label="Rate of caption dropout", value=0, minimum=0, maximum=1 ) self.vae_batch_size = gr.Slider( - label='VAE batch size', minimum=0, maximum=32, value=0, step=1 + label="VAE batch size", minimum=0, maximum=32, value=0, step=1 ) with gr.Group(), gr.Row(): - self.save_state = gr.Checkbox( - label='Save training state', value=False - ) + self.save_state = gr.Checkbox(label="Save training state", value=False) def list_state_dirs(path): - nonlocal current_state_dir - current_state_dir = path + self.current_state_dir = path if not path == "" else "." return list(list_dirs(path)) self.resume = gr.Dropdown( label='Resume from saved training state (path to "last-state" state folder)', - choices=[""] + list_state_dirs(current_state_dir), + choices=[""] + list_state_dirs(self.current_state_dir), value="", interactive=True, allow_custom_value=True, ) - create_refresh_button(self.resume, lambda: None, lambda: {"choices": list_state_dirs(current_state_dir)}, "open_folder_small") + create_refresh_button( + self.resume, + lambda: None, + lambda: {"choices": [""] + list_state_dirs(self.current_state_dir)}, + "open_folder_small", + ) self.resume_button = gr.Button( - '📂', elem_id='open_folder_small', visible=(not headless) + "📂", elem_id="open_folder_small", visible=(not headless) ) self.resume_button.click( get_folder_path, @@ -321,7 +363,7 @@ def list_state_dirs(path): show_progress=False, ) self.resume.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_state_dirs(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_state_dirs(path)), inputs=self.resume, outputs=self.resume, show_progress=False, @@ -331,51 +373,93 @@ def list_state_dirs(path): # placeholder='(Optional) Override number of epoch', # ) self.max_data_loader_n_workers = gr.Textbox( - label='Max num workers for DataLoader', - placeholder='(Optional) Override number of epoch. Default: 8', - value='0', + label="Max num workers for DataLoader", + placeholder="(Optional) Override number of epoch. Default: 8", + value="0", ) with gr.Row(): self.num_processes = gr.Number( - label='Number of processes', - value=1, - precision=0, - minimum=1 + label="Number of processes", value=1, precision=0, minimum=1 ) self.num_machines = gr.Number( - label='Number of machines', - value=1, - precision=0, - minimum=1 - ) - self.multi_gpu = gr.Checkbox( - label='Multi GPU', - value=False + label="Number of machines", value=1, precision=0, minimum=1 ) + self.multi_gpu = gr.Checkbox(label="Multi GPU", value=False) self.gpu_ids = gr.Textbox( - label='GPU IDs', - value="", - placeholder="example: 0,1" + label="GPU IDs", value="", placeholder="example: 0,1" ) with gr.Row(): - self.wandb_api_key = gr.Textbox( - label='WANDB API Key', - value='', - placeholder='(Optional)', - info='Users can obtain and/or generate an api key in the their user settings on the website: https://wandb.ai/login', - ) self.use_wandb = gr.Checkbox( - label='WANDB Logging', + label="WANDB Logging", value=False, - info='If unchecked, tensorboard will be used as the default for logging.', + info="If unchecked, tensorboard will be used as the default for logging.", ) + self.wandb_api_key = gr.Textbox( + label="WANDB API Key", + value="", + placeholder="(Optional)", + info="Users can obtain and/or generate an api key in the their user settings on the website: https://wandb.ai/login", + ) + self.wandb_run_name = gr.Textbox( + label="WANDB run name", + value="", + placeholder="(Optional)", + info="The name of the specific wandb session", + ) + with gr.Group(), gr.Row(): + + def list_log_tracker_config_files(path): + self.current_log_tracker_config_dir = path if not path == "" else "." + return list(list_files(path, exts=[".json"], all=True)) + + self.log_tracker_name = gr.Textbox( + label="Log tracker name", + value="", + placeholder="(Optional)", + info="Name of tracker to use for logging, default is script-specific default name", + ) + self.log_tracker_config = gr.Dropdown( + label="Log tracker config", + choices=[""] + + list_log_tracker_config_files(self.current_log_tracker_config_dir), + value="", + info="Path to tracker config file to use for logging", + interactive=True, + allow_custom_value=True, + ) + create_refresh_button( + self.log_tracker_config, + lambda: None, + lambda: { + "choices": [""] + + list_log_tracker_config_files(self.current_log_tracker_config_dir) + }, + "open_folder_small", + ) + self.log_tracker_config_button = gr.Button( + "📂", elem_id="open_folder_small", visible=(not headless) + ) + self.log_tracker_config_button.click( + get_any_file_path, + outputs=self.log_tracker_config, + show_progress=False, + ) + self.log_tracker_config.change( + fn=lambda path: gr.Dropdown( + choices=[""] + list_log_tracker_config_files(path) + ), + inputs=self.log_tracker_config, + outputs=self.log_tracker_config, + show_progress=False, + ) + with gr.Row(): self.scale_v_pred_loss_like_noise_pred = gr.Checkbox( - label='Scale v prediction loss', + label="Scale v prediction loss", value=False, - info='Only for SD v2 models. By scaling the loss according to the time step, the weights of global noise prediction and local noise prediction become the same, and the improvement of details may be expected.', + info="Only for SD v2 models. By scaling the loss according to the time step, the weights of global noise prediction and local noise prediction become the same, and the improvement of details may be expected.", ) self.debiased_estimation_loss = gr.Checkbox( - label='Debiased Estimation loss', + label="Debiased Estimation loss", value=False, - info='Automates the processing of noise, allowing for faster model fitting, as well as balancing out color issues', + info="Automates the processing of noise, allowing for faster model fitting, as well as balancing out color issues", ) diff --git a/kohya_gui/class_basic_training.py b/kohya_gui/class_basic_training.py index 700313bf8..8eb3e8493 100644 --- a/kohya_gui/class_basic_training.py +++ b/kohya_gui/class_basic_training.py @@ -1,58 +1,115 @@ import gradio as gr import os +from typing import Tuple class BasicTraining: + """ + This class configures and initializes the basic training settings for a machine learning model, + including options for SDXL, learning rate, learning rate scheduler, and training epochs. + + Attributes: + sdxl_checkbox (gr.Checkbox): Checkbox to enable SDXL training. + learning_rate_value (str): Initial learning rate value. + lr_scheduler_value (str): Initial learning rate scheduler value. + lr_warmup_value (str): Initial learning rate warmup value. + finetuning (bool): If True, enables fine-tuning of the model. + dreambooth (bool): If True, enables Dreambooth training. + """ + def __init__( self, sdxl_checkbox: gr.Checkbox, - learning_rate_value="1e-6", - lr_scheduler_value="constant", - lr_warmup_value="0", + learning_rate_value: str = "1e-6", + lr_scheduler_value: str = "constant", + lr_warmup_value: str = "0", finetuning: bool = False, dreambooth: bool = False, - ): + ) -> None: + """ + Initializes the BasicTraining object with the given parameters. + + Args: + sdxl_checkbox (gr.Checkbox): Checkbox to enable SDXL training. + learning_rate_value (str): Initial learning rate value. + lr_scheduler_value (str): Initial learning rate scheduler value. + lr_warmup_value (str): Initial learning rate warmup value. + finetuning (bool): If True, enables fine-tuning of the model. + dreambooth (bool): If True, enables Dreambooth training. + """ + self.sdxl_checkbox = sdxl_checkbox self.learning_rate_value = learning_rate_value self.lr_scheduler_value = lr_scheduler_value self.lr_warmup_value = lr_warmup_value self.finetuning = finetuning self.dreambooth = dreambooth - self.sdxl_checkbox = sdxl_checkbox + # Initialize the UI components + self.initialize_ui_components() + + def initialize_ui_components(self) -> None: + """ + Initializes the UI components for the training settings. + """ + # Initialize the training controls + self.init_training_controls() + # Initialize the precision and resources controls + self.init_precision_and_resources_controls() + # Initialize the learning rate and optimizer controls + self.init_lr_and_optimizer_controls() + # Initialize the gradient and learning rate controls + self.init_grad_and_lr_controls() + # Initialize the learning rate controls + self.init_learning_rate_controls() + # Initialize the scheduler controls + self.init_scheduler_controls() + # Initialize the resolution and bucket controls + self.init_resolution_and_bucket_controls() + # Setup the behavior of the SDXL checkbox + self.setup_sdxl_checkbox_behavior() + + def init_training_controls(self) -> None: + """ + Initializes the training controls for the model. + """ + # Create a row for the training controls with gr.Row(): + # Initialize the train batch size slider self.train_batch_size = gr.Slider( - minimum=1, - maximum=64, - label="Train batch size", - value=1, - step=1, + minimum=1, maximum=64, label="Train batch size", value=1, step=1 ) + # Initialize the epoch number input self.epoch = gr.Number(label="Epoch", value=1, precision=0) + # Initialize the maximum train epochs input self.max_train_epochs = gr.Textbox( label="Max train epoch", - placeholder="(Optional) Enforce number of epoch", + placeholder="(Optional) Enforce # epochs", ) + # Initialize the maximum train steps input self.max_train_steps = gr.Textbox( label="Max train steps", - placeholder="(Optional) Enforce number of steps", + placeholder="(Optional) Enforce # steps", ) + # Initialize the save every N epochs input self.save_every_n_epochs = gr.Number( label="Save every N epochs", value=1, precision=0 ) + # Initialize the caption extension input self.caption_extension = gr.Textbox( label="Caption Extension", - placeholder="(Optional) Extension for caption files. default: .caption", + placeholder="(Optional) default: .caption", ) + + def init_precision_and_resources_controls(self) -> None: + """ + Initializes the precision and resources controls for the model. + """ with gr.Row(): + # Initialize the mixed precision dropdown self.mixed_precision = gr.Dropdown( - label="Mixed precision", - choices=[ - "no", - "fp16", - "bf16", - ], - value="fp16", + label="Mixed precision", choices=["no", "fp16", "bf16"], value="fp16" ) + # Initialize the number of CPU threads per core slider self.num_cpu_threads_per_process = gr.Slider( minimum=1, maximum=os.cpu_count(), @@ -60,12 +117,21 @@ def __init__( label="Number of CPU threads per core", value=2, ) + # Initialize the seed textbox self.seed = gr.Textbox(label="Seed", placeholder="(Optional) eg:1234") + # Initialize the cache latents checkbox self.cache_latents = gr.Checkbox(label="Cache latents", value=True) + # Initialize the cache latents to disk checkbox self.cache_latents_to_disk = gr.Checkbox( label="Cache latents to disk", value=False ) + + def init_lr_and_optimizer_controls(self) -> None: + """ + Initializes the learning rate and optimizer controls for the model. + """ with gr.Row(): + # Initialize the learning rate scheduler dropdown self.lr_scheduler = gr.Dropdown( label="LR Scheduler", choices=[ @@ -77,8 +143,9 @@ def __init__( "linear", "polynomial", ], - value=lr_scheduler_value, + value=self.lr_scheduler_value, ) + # Initialize the optimizer dropdown self.optimizer = gr.Dropdown( label="Optimizer", choices=[ @@ -105,95 +172,120 @@ def __init__( value="AdamW8bit", interactive=True, ) + + def init_grad_and_lr_controls(self) -> None: + """ + Initializes the gradient and learning rate controls for the model. + """ with gr.Row(): + # Initialize the maximum gradient norm slider self.max_grad_norm = gr.Slider( - label="Max grad norm", - value=1.0, - minimum=0.0, - maximum=1.0 + label="Max grad norm", value=1.0, minimum=0.0, maximum=1.0 ) + # Initialize the learning rate scheduler extra arguments textbox self.lr_scheduler_args = gr.Textbox( label="LR scheduler extra arguments", + lines=2, placeholder='(Optional) eg: "milestones=[1,10,30,50]" "gamma=0.1"', ) + # Initialize the optimizer extra arguments textbox self.optimizer_args = gr.Textbox( label="Optimizer extra arguments", + lines=2, placeholder="(Optional) eg: relative_step=True scale_parameter=True warmup_init=True", ) + + def init_learning_rate_controls(self) -> None: + """ + Initializes the learning rate controls for the model. + """ with gr.Row(): - # Original GLOBAL LR - if finetuning or dreambooth: - self.learning_rate = gr.Number( - label="Learning rate Unet", value=learning_rate_value, - minimum=0, - maximum=1, - info="Set to 0 to not train the Unet" - ) - else: - self.learning_rate = gr.Number( - label="Learning rate", value=learning_rate_value, - minimum=0, - maximum=1 - ) - # New TE LR for non SDXL models + # Adjust visibility based on training modes + lr_label = ( + "Learning rate Unet" + if self.finetuning or self.dreambooth + else "Learning rate" + ) + # Initialize the learning rate number input + self.learning_rate = gr.Number( + label=lr_label, + value=self.learning_rate_value, + minimum=0, + maximum=1, + info="Set to 0 to not train the Unet", + ) + # Initialize the learning rate TE number input self.learning_rate_te = gr.Number( label="Learning rate TE", - value=learning_rate_value, - visible=finetuning or dreambooth, + value=self.learning_rate_value, + visible=self.finetuning or self.dreambooth, minimum=0, maximum=1, - info="Set to 0 to not train the Text Encoder" + info="Set to 0 to not train the Text Encoder", ) - # New TE LR for SDXL models + # Initialize the learning rate TE1 number input self.learning_rate_te1 = gr.Number( label="Learning rate TE1", - value=learning_rate_value, + value=self.learning_rate_value, visible=False, minimum=0, maximum=1, - info="Set to 0 to not train the Text Encoder 1" + info="Set to 0 to not train the Text Encoder 1", ) - # New TE LR for SDXL models + # Initialize the learning rate TE2 number input self.learning_rate_te2 = gr.Number( label="Learning rate TE2", - value=learning_rate_value, + value=self.learning_rate_value, visible=False, minimum=0, maximum=1, - info="Set to 0 to not train the Text Encoder 2" + info="Set to 0 to not train the Text Encoder 2", ) + # Initialize the learning rate warmup slider self.lr_warmup = gr.Slider( label="LR warmup (% of total steps)", - value=lr_warmup_value, + value=self.lr_warmup_value, minimum=0, maximum=100, step=1, ) - with gr.Row(visible=not finetuning): + + def init_scheduler_controls(self) -> None: + """ + Initializes the scheduler controls for the model. + """ + with gr.Row(visible=not self.finetuning): + # Initialize the learning rate scheduler number of cycles textbox self.lr_scheduler_num_cycles = gr.Textbox( - label="LR number of cycles", + label="LR # cycles", placeholder="(Optional) For Cosine with restart and polynomial only", ) - + # Initialize the learning rate scheduler power textbox self.lr_scheduler_power = gr.Textbox( label="LR power", placeholder="(Optional) For Cosine with restart and polynomial only", ) - with gr.Row(visible=not finetuning): + + def init_resolution_and_bucket_controls(self) -> None: + """ + Initializes the resolution and bucket controls for the model. + """ + with gr.Row(visible=not self.finetuning): + # Initialize the maximum resolution textbox self.max_resolution = gr.Textbox( - label="Max resolution", - value="512,512", - placeholder="512,512", + label="Max resolution", value="512,512", placeholder="512,512" ) + # Initialize the stop text encoder training slider self.stop_text_encoder_training = gr.Slider( minimum=-1, maximum=100, value=0, step=1, - label="Stop text encoder training (% of total steps)", + label="Stop TE (% of total steps)", ) - with gr.Row(visible=not finetuning): + # Initialize the enable buckets checkbox self.enable_bucket = gr.Checkbox(label="Enable buckets", value=True) + # Initialize the minimum bucket resolution slider self.min_bucket_reso = gr.Slider( label="Minimum bucket resolution", value=256, @@ -202,6 +294,7 @@ def __init__( step=64, info="Minimum size in pixel a bucket can be (>= 64)", ) + # Initialize the maximum bucket resolution slider self.max_bucket_reso = gr.Slider( label="Maximum bucket resolution", value=2048, @@ -211,19 +304,46 @@ def __init__( info="Maximum size in pixel a bucket can be (>= 64)", ) - def update_learning_rate_te(sdxl_checkbox, finetuning, dreambooth): - return ( - gr.Number(visible=(not sdxl_checkbox and (finetuning or dreambooth))), - gr.Number(visible=(sdxl_checkbox and (finetuning or dreambooth))), - gr.Number(visible=(sdxl_checkbox and (finetuning or dreambooth))), - ) - + def setup_sdxl_checkbox_behavior(self) -> None: + """ + Sets up the behavior of the SDXL checkbox based on the finetuning and dreambooth flags. + """ self.sdxl_checkbox.change( - update_learning_rate_te, - inputs=[self.sdxl_checkbox, gr.Checkbox(value=finetuning, visible=False), gr.Checkbox(value=dreambooth, visible=False)], + self.update_learning_rate_te, + inputs=[ + self.sdxl_checkbox, + gr.Checkbox(value=self.finetuning, visible=False), + gr.Checkbox(value=self.dreambooth, visible=False), + ], outputs=[ self.learning_rate_te, self.learning_rate_te1, self.learning_rate_te2, ], ) + + def update_learning_rate_te( + self, + sdxl_checkbox: gr.Checkbox, + finetuning: bool, + dreambooth: bool, + ) -> Tuple[gr.Number, gr.Number, gr.Number]: + """ + Updates the visibility of the learning rate TE, TE1, and TE2 based on the SDXL checkbox and finetuning/dreambooth flags. + + Args: + sdxl_checkbox (gr.Checkbox): The SDXL checkbox. + finetuning (bool): Whether finetuning is enabled. + dreambooth (bool): Whether dreambooth is enabled. + + Returns: + Tuple[gr.Number, gr.Number, gr.Number]: A tuple containing the updated visibility for learning rate TE, TE1, and TE2. + """ + # Determine the visibility condition based on finetuning and dreambooth flags + visibility_condition = finetuning or dreambooth + # Return a tuple of gr.Number instances with updated visibility + return ( + gr.Number(visible=(not sdxl_checkbox and visibility_condition)), + gr.Number(visible=(sdxl_checkbox and visibility_condition)), + gr.Number(visible=(sdxl_checkbox and visibility_condition)), + ) diff --git a/kohya_gui/class_command_executor.py b/kohya_gui/class_command_executor.py index c7b55e57f..d64ad92a9 100644 --- a/kohya_gui/class_command_executor.py +++ b/kohya_gui/class_command_executor.py @@ -5,30 +5,47 @@ # Set up logging log = setup_logging() - class CommandExecutor: + """ + A class to execute and manage commands. + """ + def __init__(self): + """ + Initialize the CommandExecutor. + """ self.process = None - def execute_command(self, run_cmd, **kwargs): + def execute_command(self, run_cmd: str, **kwargs): + """ + Execute a command if no other command is currently running. + + Parameters: + - run_cmd (str): The command to execute. + - **kwargs: Additional keyword arguments to pass to subprocess.Popen. + """ if self.process and self.process.poll() is None: - log.info( - 'The command is already running. Please wait for it to finish.' - ) + log.info("The command is already running. Please wait for it to finish.") else: self.process = subprocess.Popen(run_cmd, shell=True, **kwargs) def kill_command(self): + """ + Kill the currently running command and its child processes. + """ if self.process and self.process.poll() is None: try: + # Get the parent process and kill all its children parent = psutil.Process(self.process.pid) for child in parent.children(recursive=True): child.kill() parent.kill() - log.info('The running process has been terminated.') + log.info("The running process has been terminated.") except psutil.NoSuchProcess: - log.info('The process does not exist.') + # Explicitly handle the case where the process does not exist + log.info("The process does not exist. It might have terminated before the kill command was issued.") except Exception as e: - log.info(f'Error when terminating process: {e}') + # General exception handling for any other errors + log.info(f"Error when terminating process: {e}") else: - log.info('There is no running process to kill.') + log.info("There is no running process to kill.") diff --git a/kohya_gui/class_configuration_file.py b/kohya_gui/class_configuration_file.py index 68420bf3b..a99caa1dd 100644 --- a/kohya_gui/class_configuration_file.py +++ b/kohya_gui/class_configuration_file.py @@ -1,60 +1,99 @@ import gradio as gr import os +from .common_gui import list_files, scriptdir, create_refresh_button +from .custom_logging import setup_logging -from .common_gui import list_files +# Set up logging +log = setup_logging() class ConfigurationFile: - def __init__(self, headless=False, output_dir: gr.Dropdown = None): - from .common_gui import create_refresh_button + """ + A class to handle configuration file operations in the GUI. + """ + + def __init__(self, headless: bool = False, config_dir: str = None, config:dict = {}): + """ + Initialize the ConfigurationFile class. + + Parameters: + - headless (bool): Whether to run in headless mode. + - config_dir (str): The directory for configuration files. + """ self.headless = headless - self.output_dir = None + + self.config = config + + # Sets the directory for storing configuration files, defaults to a 'presets' folder within the script directory. + self.current_config_dir = self.config.get('config_dir', os.path.join(scriptdir, "presets")) - def update_configs(output_dir): - self.output_dir = output_dir - return gr.Dropdown().update(choices=[""] + list(list_files(output_dir, exts=[".json"], all=True))) + # Initialize the GUI components for configuration. + self.create_config_gui() - def list_configs(path): - self.output_dir = path - return list(list_files(path, exts=[".json"], all=True)) + def list_config_dir(self, path: str) -> list: + """ + List directories in the data directory. + Parameters: + - path (str): The path to list directories from. + + Returns: + - list: A list of directories. + """ + self.current_config_dir = path if not path == "" else "." + # Lists all .json files in the current configuration directory, used for populating dropdown choices. + return list(list_files(self.current_config_dir, exts=[".json"], all=True)) + + def create_config_gui(self) -> None: + """ + Create the GUI for configuration file operations. + """ + # Starts a new group in the GUI for better layout organization. with gr.Group(): + # Creates a row within the group to align elements horizontally. with gr.Row(): + # Dropdown for selecting or entering the name of a configuration file. self.config_file_name = gr.Dropdown( - label='Load/Save Config file', - choices=[""] + list_configs(self.output_dir), + label="Load/Save Config file", + choices=[""] + self.list_config_dir(self.current_config_dir), value="", interactive=True, allow_custom_value=True, ) - create_refresh_button(self.config_file_name, lambda: None, lambda: {"choices": list_configs(self.output_dir)}, "open_folder_small") + + # Button to refresh the list of configuration files in the dropdown. + create_refresh_button( + self.config_file_name, + lambda: None, # Placeholder for potential future functionality. + lambda: { + "choices": [""] + self.list_config_dir(self.current_config_dir) + }, + "open_folder_small", + ) + + # Buttons for opening, saving, and loading configuration files, displayed conditionally based on headless mode. self.button_open_config = gr.Button( - '📂', - elem_id='open_folder_small', - elem_classes=['tool'], + "📂", + elem_id="open_folder_small", + elem_classes=["tool"], visible=(not self.headless), ) self.button_save_config = gr.Button( - '💾', - elem_id='open_folder_small', - elem_classes=['tool'], + "💾", + elem_id="open_folder_small", + elem_classes=["tool"], ) self.button_load_config = gr.Button( - '↩️ ', - elem_id='open_folder_small', - elem_classes=['tool'], + "↩️ ", + elem_id="open_folder_small", + elem_classes=["tool"], ) + # Handler for change events on the configuration file dropdown, allowing dynamic update of choices. self.config_file_name.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_configs(path)), + fn=lambda path: gr.Dropdown(choices=[""] + self.list_config_dir(path)), inputs=self.config_file_name, outputs=self.config_file_name, show_progress=False, ) - - output_dir.change( - fn=update_configs, - inputs=output_dir, - outputs=self.config_file_name, - ) diff --git a/kohya_gui/class_dreambooth_gui.py b/kohya_gui/class_dreambooth_gui.py deleted file mode 100644 index b74c56ee9..000000000 --- a/kohya_gui/class_dreambooth_gui.py +++ /dev/null @@ -1,110 +0,0 @@ -import gradio as gr -import json -from .class_configuration_file import ConfigurationFile -from .class_source_model import SourceModel -from .class_folders import Folders -from .class_basic_training import BasicTraining -from .class_advanced_training import AdvancedTraining -from .class_sample_images import SampleImages -from .dreambooth_folder_creation_gui import ( - gradio_dreambooth_folder_creation_tab, -) -from .dataset_balancing_gui import gradio_dataset_balancing_tab -from .common_gui import color_aug_changed - - -class Dreambooth: - def __init__( - self, - headless: bool = False, - ): - self.headless = headless - self.dummy_db_true = gr.Label(value=True, visible=False) - self.dummy_db_false = gr.Label(value=False, visible=False) - self.dummy_headless = gr.Label(value=headless, visible=False) - - gr.Markdown( - 'Train a custom model using kohya dreambooth python code...' - ) - - # Setup Configuration Files Gradio - self.config = ConfigurationFile(headless) - - self.source_model = SourceModel(headless=headless) - - with gr.Tab('Folders'): - self.folders = Folders(headless=headless) - with gr.Tab('Parameters'): - self.basic_training = BasicTraining( - learning_rate_value='1e-5', - lr_scheduler_value='cosine', - lr_warmup_value='10', - ) - self.full_bf16 = gr.Checkbox(label='Full bf16', value=False) - with gr.Accordion('Advanced Configuration', open=False): - self.advanced_training = AdvancedTraining(headless=headless) - self.advanced_training.color_aug.change( - color_aug_changed, - inputs=[self.advanced_training.color_aug], - outputs=[self.basic_training.cache_latents], - ) - - self.sample = SampleImages() - - with gr.Tab('Dataset Preparation'): - gr.Markdown( - 'This section provide Dreambooth tools to help setup your dataset...' - ) - gradio_dreambooth_folder_creation_tab( - train_data_dir_input=self.folders.train_data_dir, - reg_data_dir_input=self.folders.reg_data_dir, - output_dir_input=self.folders.output_dir, - logging_dir_input=self.folders.logging_dir, - headless=headless, - ) - gradio_dataset_balancing_tab(headless=headless) - - def save_to_json(self, filepath): - def serialize(obj): - if isinstance(obj, gr.inputs.Input): - return obj.get() - if isinstance(obj, (bool, int, float, str)): - return obj - if isinstance(obj, dict): - return {k: serialize(v) for k, v in obj.items()} - if hasattr(obj, '__dict__'): - return serialize(vars(obj)) - return str(obj) # Fallback for objects that can't be serialized - - try: - with open(filepath, 'w') as outfile: - print(serialize(vars(self))) - json.dump(serialize(vars(self)), outfile) - except Exception as e: - print(f'Error saving to JSON: {str(e)}') - - def load_from_json(self, filepath): - def deserialize(key, value): - if hasattr(self, key): - attr = getattr(self, key) - if isinstance(attr, gr.inputs.Input): - attr.set(value) - elif hasattr(attr, '__dict__'): - for k, v in value.items(): - deserialize(k, v) - else: - setattr(self, key, value) - else: - print(f"Warning: {key} not found in the object's attributes.") - - try: - with open(filepath) as json_file: - data = json.load(json_file) - for key, value in data.items(): - deserialize(key, value) - except FileNotFoundError: - print(f'Error: The file {filepath} was not found.') - except json.JSONDecodeError: - print(f'Error: The file {filepath} could not be decoded as JSON.') - except Exception as e: - print(f'Error loading from JSON: {str(e)}') diff --git a/kohya_gui/class_folders.py b/kohya_gui/class_folders.py index 81558c29b..6d206abd0 100644 --- a/kohya_gui/class_folders.py +++ b/kohya_gui/class_folders.py @@ -1,109 +1,172 @@ import gradio as gr import os -from .common_gui import get_folder_path, scriptdir, list_dirs - +from .common_gui import get_folder_path, scriptdir, list_dirs, create_refresh_button class Folders: - def __init__(self, finetune=False, train_data_dir: gr.Dropdown = None, data_dir=None, output_dir=None, logging_dir=None, headless=False): - from .common_gui import create_refresh_button + """ + A class to handle folder operations in the GUI. + """ + def __init__(self, finetune: bool = False, headless: bool = False, config:dict = {}): + """ + Initialize the Folders class. + Parameters: + - finetune (bool): Whether to finetune the model. + - headless (bool): Whether to run in headless mode. + """ self.headless = headless + self.finetune = finetune + + # Load kohya_ss GUI configs from config.toml if it exist + self.config = config + + # Set default directories if not provided + self.current_output_dir = self.config.get('output_dir', os.path.join(scriptdir, "outputs")) + self.current_logging_dir = self.config.get('logging_dir', os.path.join(scriptdir, "logs")) + self.current_reg_data_dir = self.config.get('reg_data_dir', os.path.join(scriptdir, "reg")) + + # Create directories if they don't exist + self.create_directory_if_not_exists(self.current_output_dir) + self.create_directory_if_not_exists(self.current_logging_dir) + + # Create the GUI for folder selection + self.create_folders_gui() + + def create_directory_if_not_exists(self, directory: str) -> None: + """ + Create a directory if it does not exist. + + Parameters: + - directory (str): The directory to create. + """ + if directory is not None and directory.strip() != "" and not os.path.exists(directory): + os.makedirs(directory, exist_ok=True) + + + def list_output_dirs(self, path: str) -> list: + """ + List directories in the output directory. + + Parameters: + - path (str): The path to list directories from. - default_data_dir = data_dir if data_dir is not None else os.path.join(scriptdir, "data") - default_output_dir = output_dir if output_dir is not None else os.path.join(scriptdir, "outputs") - default_logging_dir = logging_dir if logging_dir is not None else os.path.join(scriptdir, "logs") - default_reg_data_dir = default_data_dir + Returns: + - list: A list of directories. + """ + self.current_output_dir = path if not path == "" else "." + return list(list_dirs(path)) - self.current_data_dir = default_data_dir - self.current_output_dir = default_output_dir - self.current_logging_dir = default_logging_dir + def list_logging_dirs(self, path: str) -> list: + """ + List directories in the logging directory. + Parameters: + - path (str): The path to list directories from. - if default_data_dir is not None and default_data_dir.strip() != "" and not os.path.exists(default_data_dir): - os.makedirs(default_data_dir, exist_ok=True) - if default_output_dir is not None and default_output_dir.strip() != "" and not os.path.exists(default_output_dir): - os.makedirs(default_output_dir, exist_ok=True) - if default_logging_dir is not None and default_logging_dir.strip() != "" and not os.path.exists(default_logging_dir): - os.makedirs(default_logging_dir, exist_ok=True) + Returns: + - list: A list of directories. + """ + self.current_logging_dir = path if not path == "" else "." + return list(list_dirs(path)) - def list_data_dirs(path): - self.current_data_dir = path - return list(list_dirs(path)) + def list_reg_data_dirs(self, path: str) -> list: + """ + List directories in the regularization data directory. - def list_output_dirs(path): - self.current_output_dir = path - return list(list_dirs(path)) + Parameters: + - path (str): The path to list directories from. - def list_logging_dirs(path): - self.current_logging_dir = path - return list(list_dirs(path)) + Returns: + - list: A list of directories. + """ + self.current_reg_data_dir = path if not path == "" else "." + return list(list_dirs(path)) + def create_folders_gui(self) -> None: + """ + Create the GUI for folder selection. + """ with gr.Row(): + # Output directory dropdown self.output_dir = gr.Dropdown( - label=f'Output folder to output trained model', - choices=[""] + list_output_dirs(default_output_dir), + label=f'Output directory for trained model', + choices=[""] + self.list_output_dirs(self.current_output_dir), value="", interactive=True, allow_custom_value=True, ) - create_refresh_button(self.output_dir, lambda: None, lambda: {"choices": list_output_dirs(self.current_output_dir)}, "open_folder_small") + # Refresh button for output directory + create_refresh_button(self.output_dir, lambda: None, lambda: {"choices": [""] + self.list_output_dirs(self.current_output_dir)}, "open_folder_small") + # Output directory button self.output_dir_folder = gr.Button( '📂', elem_id='open_folder_small', elem_classes=["tool"], visible=(not self.headless) ) + # Output directory button click event self.output_dir_folder.click( get_folder_path, outputs=self.output_dir, show_progress=False, ) + # Regularisation directory dropdown self.reg_data_dir = gr.Dropdown( - label='Regularisation folder (Optional. containing reqularization images)' if not finetune else 'Train config folder (Optional. where config files will be saved)', - choices=[""] + list_data_dirs(default_reg_data_dir), + label='Regularisation directory (Optional. containing regularisation images)' if not self.finetune else 'Train config directory (Optional. where config files will be saved)', + choices=[""] + self.list_reg_data_dirs(self.current_reg_data_dir), value="", interactive=True, allow_custom_value=True, ) - create_refresh_button(self.reg_data_dir, lambda: None, lambda: {"choices": list_data_dirs(self.current_data_dir)}, "open_folder_small") + # Refresh button for regularisation directory + create_refresh_button(self.reg_data_dir, lambda: None, lambda: {"choices": [""] + self.list_reg_data_dirs(self.current_reg_data_dir)}, "open_folder_small") + # Regularisation directory button self.reg_data_dir_folder = gr.Button( '📂', elem_id='open_folder_small', elem_classes=["tool"], visible=(not self.headless) ) + # Regularisation directory button click event self.reg_data_dir_folder.click( get_folder_path, outputs=self.reg_data_dir, show_progress=False, ) with gr.Row(): + # Logging directory dropdown self.logging_dir = gr.Dropdown( - label='Logging folder (Optional. to enable logging and output Tensorboard log)', - choices=[""] + list_logging_dirs(default_logging_dir), + label='Logging directory (Optional. to enable logging and output Tensorboard log)', + choices=[""] + self.list_logging_dirs(self.current_logging_dir), value="", interactive=True, allow_custom_value=True, ) - create_refresh_button(self.logging_dir, lambda: None, lambda: {"choices": list_logging_dirs(self.current_logging_dir)}, "open_folder_small") + # Refresh button for logging directory + create_refresh_button(self.logging_dir, lambda: None, lambda: {"choices": [""] + self.list_logging_dirs(self.current_logging_dir)}, "open_folder_small") + # Logging directory button self.logging_dir_folder = gr.Button( '📂', elem_id='open_folder_small', elem_classes=["tool"], visible=(not self.headless) ) + # Logging directory button click event self.logging_dir_folder.click( get_folder_path, outputs=self.logging_dir, show_progress=False, ) + # Change event for output directory dropdown self.output_dir.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_output_dirs(path)), + fn=lambda path: gr.Dropdown(choices=[""] + self.list_output_dirs(path)), inputs=self.output_dir, outputs=self.output_dir, show_progress=False, ) + # Change event for regularisation directory dropdown self.reg_data_dir.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_data_dirs(path)), + fn=lambda path: gr.Dropdown(choices=[""] + self.list_reg_data_dirs(path)), inputs=self.reg_data_dir, outputs=self.reg_data_dir, show_progress=False, ) + # Change event for logging directory dropdown self.logging_dir.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_logging_dirs(path)), + fn=lambda path: gr.Dropdown(choices=[""] + self.list_logging_dirs(path)), inputs=self.logging_dir, outputs=self.logging_dir, show_progress=False, diff --git a/kohya_gui/class_gui_config.py b/kohya_gui/class_gui_config.py new file mode 100644 index 000000000..7f73ac3f7 --- /dev/null +++ b/kohya_gui/class_gui_config.py @@ -0,0 +1,77 @@ +import toml +from .common_gui import scriptdir +from .custom_logging import setup_logging + +# Set up logging +log = setup_logging() + +class KohyaSSGUIConfig: + """ + A class to handle the configuration for the Kohya SS GUI. + """ + + def __init__(self): + """ + Initialize the KohyaSSGUIConfig class. + """ + self.config = self.load_config() + + def load_config(self) -> dict: + """ + Loads the Kohya SS GUI configuration from a TOML file. + + Returns: + dict: The configuration data loaded from the TOML file. + """ + try: + # Attempt to load the TOML configuration file from the specified directory. + config = toml.load(f"{scriptdir}/config.toml") + log.debug(f"Loaded configuration from {scriptdir}/config.toml") + except FileNotFoundError: + # If the config file is not found, initialize `config` as an empty dictionary to handle missing configurations gracefully. + config = {} + log.debug(f"No configuration file found at {scriptdir}/config.toml. Initializing empty configuration.") + + return config + + def save_config(self, config: dict): + """ + Saves the Kohya SS GUI configuration to a TOML file. + + Parameters: + - config (dict): The configuration data to save. + """ + # Write the configuration data to the TOML file + with open(f"{scriptdir}/config.toml", "w") as f: + toml.dump(config, f) + + def get(self, key: str, default=None): + """ + Retrieves the value of a specified key from the configuration data. + + Parameters: + - key (str): The key to retrieve the value for. + - default: The default value to return if the key is not found. + + Returns: + The value associated with the key, or the default value if the key is not found. + """ + # Split the key into a list of keys if it contains a dot (.) + keys = key.split(".") + # Initialize `data` with the entire configuration data + data = self.config + + # Iterate over the keys to access nested values + for k in keys: + log.debug(k) + # If the key is not found in the current data, return the default value + if k not in data: + log.debug(f"Key '{key}' not found in configuration. Returning default value.") + return default + + # Update `data` to the value associated with the current key + data = data.get(k) + + # Return the final value + log.debug(f"Returned {data}") + return data diff --git a/kohya_gui/class_lora_tab.py b/kohya_gui/class_lora_tab.py index bcc1d16e1..b8db73d42 100644 --- a/kohya_gui/class_lora_tab.py +++ b/kohya_gui/class_lora_tab.py @@ -9,15 +9,9 @@ from .extract_lora_from_dylora_gui import gradio_extract_dylora_tab from .merge_lycoris_gui import gradio_merge_lycoris_tab -# Deprecated code -from .dataset_balancing_gui import gradio_dataset_balancing_tab -from .dreambooth_folder_creation_gui import ( - gradio_dreambooth_folder_creation_tab, -) - class LoRATools: - def __init__(self, train_data_dir=None, reg_data_dir=None, output_dir=None, logging_dir=None, headless: bool = False): + def __init__(self, headless: bool = False): self.headless = headless gr.Markdown( diff --git a/kohya_gui/class_sample_images.py b/kohya_gui/class_sample_images.py index 8088973aa..0410244d6 100644 --- a/kohya_gui/class_sample_images.py +++ b/kohya_gui/class_sample_images.py @@ -25,14 +25,25 @@ def run_cmd_sample( sample_prompts, output_dir, ): + """ + Generates a command string for sampling images during training. + + Args: + sample_every_n_steps (int): The number of steps after which to sample images. + sample_every_n_epochs (int): The number of epochs after which to sample images. + sample_sampler (str): The sampler to use for image sampling. + sample_prompts (str): The prompts to use for image sampling. + output_dir (str): The directory where the output images will be saved. + + Returns: + str: The command string for sampling images. + """ output_dir = os.path.join(output_dir, 'sample') - - if not os.path.exists(output_dir): - os.makedirs(output_dir) + os.makedirs(output_dir, exist_ok=True) run_cmd = '' - if sample_every_n_epochs == sample_every_n_steps == 0: + if sample_every_n_epochs == sample_every_n_steps == '0': return run_cmd # Create the prompt file and get its path @@ -44,20 +55,32 @@ def run_cmd_sample( run_cmd += f' --sample_sampler={sample_sampler}' run_cmd += f' --sample_prompts="{sample_prompts_path}"' - if sample_every_n_epochs != 0: - run_cmd += f' --sample_every_n_epochs="{sample_every_n_epochs}"' + if sample_every_n_epochs != '0': + run_cmd += f' --sample_every_n_epochs={sample_every_n_epochs}' - if sample_every_n_steps != 0: - run_cmd += f' --sample_every_n_steps="{sample_every_n_steps}"' + if sample_every_n_steps != '0': + run_cmd += f' --sample_every_n_steps={sample_every_n_steps}' return run_cmd class SampleImages: + """ + A class for managing the Gradio interface for sampling images during training. + """ + def __init__( self, ): - # with gr.Accordion('Sample images config', open=False): + """ + Initializes the SampleImages class. + """ + self.initialize_accordion() + + def initialize_accordion(self): + """ + Initializes the accordion for the Gradio interface. + """ with gr.Row(): self.sample_every_n_steps = gr.Number( label='Sample every n steps', diff --git a/kohya_gui/class_sdxl_parameters.py b/kohya_gui/class_sdxl_parameters.py index f4956a1ca..a95ff13af 100644 --- a/kohya_gui/class_sdxl_parameters.py +++ b/kohya_gui/class_sdxl_parameters.py @@ -1,15 +1,18 @@ import gradio as gr -### SDXL Parameters class class SDXLParameters: def __init__( - self, sdxl_checkbox, show_sdxl_cache_text_encoder_outputs: bool = True + self, + sdxl_checkbox: gr.Checkbox, + show_sdxl_cache_text_encoder_outputs: bool = True, ): self.sdxl_checkbox = sdxl_checkbox self.show_sdxl_cache_text_encoder_outputs = ( show_sdxl_cache_text_encoder_outputs ) + self.initialize_accordion() + def initialize_accordion(self): with gr.Accordion( visible=False, open=True, label='SDXL Specific Parameters' ) as self.sdxl_row: @@ -18,7 +21,7 @@ def __init__( label='Cache text encoder outputs', info='Cache the outputs of the text encoders. This option is useful to reduce the GPU memory usage. This option cannot be used with options for shuffling or dropping the captions.', value=False, - visible=show_sdxl_cache_text_encoder_outputs, + visible=self.show_sdxl_cache_text_encoder_outputs, ) self.sdxl_no_half_vae = gr.Checkbox( label='No half VAE', diff --git a/kohya_gui/class_source_model.py b/kohya_gui/class_source_model.py index f82525f69..218b7a680 100644 --- a/kohya_gui/class_source_model.py +++ b/kohya_gui/class_source_model.py @@ -8,35 +8,37 @@ scriptdir, list_dirs, list_files, + create_refresh_button, ) -folder_symbol = '\U0001f4c2' # 📂 -refresh_symbol = '\U0001f504' # 🔄 -save_style_symbol = '\U0001f4be' # 💾 -document_symbol = '\U0001F4C4' # 📄 +folder_symbol = "\U0001f4c2" # 📂 +refresh_symbol = "\U0001f504" # 🔄 +save_style_symbol = "\U0001f4be" # 💾 +document_symbol = "\U0001F4C4" # 📄 default_models = [ - 'stabilityai/stable-diffusion-xl-base-1.0', - 'stabilityai/stable-diffusion-xl-refiner-1.0', - 'stabilityai/stable-diffusion-2-1-base/blob/main/v2-1_512-ema-pruned', - 'stabilityai/stable-diffusion-2-1-base', - 'stabilityai/stable-diffusion-2-base', - 'stabilityai/stable-diffusion-2-1/blob/main/v2-1_768-ema-pruned', - 'stabilityai/stable-diffusion-2-1', - 'stabilityai/stable-diffusion-2', - 'runwayml/stable-diffusion-v1-5', - 'CompVis/stable-diffusion-v1-4', + "stabilityai/stable-diffusion-xl-base-1.0", + "stabilityai/stable-diffusion-xl-refiner-1.0", + "stabilityai/stable-diffusion-2-1-base/blob/main/v2-1_512-ema-pruned", + "stabilityai/stable-diffusion-2-1-base", + "stabilityai/stable-diffusion-2-base", + "stabilityai/stable-diffusion-2-1/blob/main/v2-1_768-ema-pruned", + "stabilityai/stable-diffusion-2-1", + "stabilityai/stable-diffusion-2", + "runwayml/stable-diffusion-v1-5", + "CompVis/stable-diffusion-v1-4", ] + class SourceModel: def __init__( self, save_model_as_choices=[ - 'same as source model', - 'ckpt', - 'diffusers', - 'diffusers_safetensors', - 'safetensors', + "same as source model", + "ckpt", + "diffusers", + "diffusers_safetensors", + "safetensors", ], save_precision_choices=[ "float", @@ -44,113 +46,150 @@ def __init__( "bf16", ], headless=False, - default_data_dir=None, finetuning=False, + config: dict = {}, ): self.headless = headless self.save_model_as_choices = save_model_as_choices self.finetuning = finetuning - - from .common_gui import create_refresh_button - - default_data_dir = default_data_dir if default_data_dir is not None else os.path.join(scriptdir, "outputs") - default_train_dir = default_data_dir if default_data_dir is not None else os.path.join(scriptdir, "data") - model_checkpoints = list(list_files(default_data_dir, exts=[".ckpt", ".safetensors"], all=True)) - self.current_data_dir = default_data_dir - self.current_train_dir = default_train_dir + self.config = config + + # Set default directories if not provided + self.current_models_dir = self.config.get( + "models_dir", os.path.join(scriptdir, "models") + ) + self.current_train_data_dir = self.config.get( + "train_data_dir", os.path.join(scriptdir, "data") + ) + + model_checkpoints = list( + list_files( + self.current_models_dir, exts=[".ckpt", ".safetensors"], all=True + ) + ) def list_models(path): - self.current_data_dir = path if os.path.isdir(path) else os.path.dirname(path) - return default_models + list(list_files(path, exts=[".ckpt", ".safetensors"], all=True)) + self.current_models_dir = ( + path if os.path.isdir(path) else os.path.dirname(path) + ) + return default_models + list( + list_files(path, exts=[".ckpt", ".safetensors"], all=True) + ) - def list_train_dirs(path): - self.current_train_dir = path if os.path.isdir(path) else os.path.dirname(path) + def list_train_data_dirs(path): + self.current_train_data_dir = path if not path == "" else "." return list(list_dirs(path)) - if default_data_dir is not None and default_data_dir.strip() != "" and not os.path.exists(default_data_dir): - os.makedirs(default_data_dir, exist_ok=True) - with gr.Column(), gr.Group(): # Define the input elements with gr.Row(): - with gr.Column(), gr.Row(): - self.model_list = gr.Textbox(visible=False, value="") - self.pretrained_model_name_or_path = gr.Dropdown( - label='Pretrained model name or path', - choices=default_models + model_checkpoints, - value='runwayml/stable-diffusion-v1-5', - allow_custom_value=True, - visible=True, - min_width=100, - ) - create_refresh_button(self.pretrained_model_name_or_path, lambda: None, lambda: {"choices": list_models(self.current_data_dir)},"open_folder_small") - - self.pretrained_model_name_or_path_file = gr.Button( - document_symbol, - elem_id='open_folder_small', - elem_classes=['tool'], - visible=(not headless), - ) - self.pretrained_model_name_or_path_file.click( - get_any_file_path, - inputs=self.pretrained_model_name_or_path, - outputs=self.pretrained_model_name_or_path, - show_progress=False, - ) - self.pretrained_model_name_or_path_folder = gr.Button( - folder_symbol, - elem_id='open_folder_small', - elem_classes=['tool'], - visible=(not headless), - ) - self.pretrained_model_name_or_path_folder.click( - get_folder_path, - inputs=self.pretrained_model_name_or_path, - outputs=self.pretrained_model_name_or_path, - show_progress=False, - ) - - with gr.Column(), gr.Row(): - self.train_data_dir = gr.Dropdown( - label='Image folder (containing training images subfolders)' if not finetuning else 'Image folder (containing training images)', - choices=[""] + list_train_dirs(default_train_dir), - value="", - interactive=True, - allow_custom_value=True, - ) - create_refresh_button(self.train_data_dir, lambda: None, lambda: {"choices": list_train_dirs(self.current_train_dir)}, "open_folder_small") - self.train_data_dir_folder = gr.Button( - '📂', elem_id='open_folder_small', elem_classes=["tool"], visible=(not self.headless) - ) - self.train_data_dir_folder.click( - get_folder_path, - outputs=self.train_data_dir, - show_progress=False, - ) + with gr.Column(), gr.Row(): + self.model_list = gr.Textbox(visible=False, value="") + self.pretrained_model_name_or_path = gr.Dropdown( + label="Pretrained model name or path", + choices=default_models + model_checkpoints, + value="runwayml/stable-diffusion-v1-5", + allow_custom_value=True, + visible=True, + min_width=100, + ) + create_refresh_button( + self.pretrained_model_name_or_path, + lambda: None, + lambda: {"choices": list_models(self.current_models_dir)}, + "open_folder_small", + ) + + self.pretrained_model_name_or_path_file = gr.Button( + document_symbol, + elem_id="open_folder_small", + elem_classes=["tool"], + visible=(not headless), + ) + self.pretrained_model_name_or_path_file.click( + get_any_file_path, + inputs=self.pretrained_model_name_or_path, + outputs=self.pretrained_model_name_or_path, + show_progress=False, + ) + self.pretrained_model_name_or_path_folder = gr.Button( + folder_symbol, + elem_id="open_folder_small", + elem_classes=["tool"], + visible=(not headless), + ) + self.pretrained_model_name_or_path_folder.click( + get_folder_path, + inputs=self.pretrained_model_name_or_path, + outputs=self.pretrained_model_name_or_path, + show_progress=False, + ) + + with gr.Column(), gr.Row(): + self.train_data_dir = gr.Dropdown( + label=( + "Image folder (containing training images subfolders)" + if not finetuning + else "Image folder (containing training images)" + ), + choices=[""] + + list_train_data_dirs(self.current_train_data_dir), + value="", + interactive=True, + allow_custom_value=True, + ) + create_refresh_button( + self.train_data_dir, + lambda: None, + lambda: { + "choices": [""] + + list_train_data_dirs(self.current_train_data_dir) + }, + "open_folder_small", + ) + self.train_data_dir_folder = gr.Button( + "📂", + elem_id="open_folder_small", + elem_classes=["tool"], + visible=(not self.headless), + ) + self.train_data_dir_folder.click( + get_folder_path, + outputs=self.train_data_dir, + show_progress=False, + ) with gr.Row(): with gr.Column(): with gr.Row(): - self.v2 = gr.Checkbox(label='v2', value=False, visible=False, min_width=60) + self.v2 = gr.Checkbox( + label="v2", value=False, visible=False, min_width=60 + ) self.v_parameterization = gr.Checkbox( - label='v_parameterization', value=False, visible=False, min_width=130, + label="v_parameterization", + value=False, + visible=False, + min_width=130, ) self.sdxl_checkbox = gr.Checkbox( - label='SDXL', value=False, visible=False, min_width=60, + label="SDXL", + value=False, + visible=False, + min_width=60, ) with gr.Column(): gr.Box(visible=False) with gr.Row(): self.output_name = gr.Textbox( - label='Trained Model output name', - placeholder='(Name of the model to output)', - value='last', + label="Trained Model output name", + placeholder="(Name of the model to output)", + value="last", interactive=True, ) self.training_comment = gr.Textbox( - label='Training comment', - placeholder='(Optional) Add training comment to be included in metadata', + label="Training comment", + placeholder="(Optional) Add training comment to be included in metadata", interactive=True, ) @@ -167,7 +206,9 @@ def list_train_dirs(path): ) self.pretrained_model_name_or_path.change( - fn=lambda path: set_pretrained_model_name_or_path_input(path, refresh_method=list_models), + fn=lambda path: set_pretrained_model_name_or_path_input( + path, refresh_method=list_models + ), inputs=[ self.pretrained_model_name_or_path, ], @@ -181,7 +222,7 @@ def list_train_dirs(path): ) self.train_data_dir.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_train_dirs(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_train_data_dirs(path)), inputs=self.train_data_dir, outputs=self.train_data_dir, show_progress=False, diff --git a/kohya_gui/common_gui.py b/kohya_gui/common_gui.py index 0998848b4..bf0f74275 100644 --- a/kohya_gui/common_gui.py +++ b/kohya_gui/common_gui.py @@ -1,15 +1,15 @@ from tkinter import filedialog, Tk -from easygui import msgbox +from easygui import msgbox, ynbox +from typing import Optional +from .custom_logging import setup_logging + import os import re import gradio as gr -import easygui import shutil import sys import json -from .custom_logging import setup_logging - # Set up logging log = setup_logging() @@ -54,8 +54,21 @@ ENV_EXCLUSION = ["COLAB_GPU", "RUNPOD_POD_ID"] +def check_if_model_exist( + output_name: str, output_dir: str, save_model_as: str, headless: bool = False +) -> bool: + """ + Checks if a model with the same name already exists and prompts the user to overwrite it if it does. + + Parameters: + output_name (str): The name of the output model. + output_dir (str): The directory where the model is saved. + save_model_as (str): The format to save the model as. + headless (bool, optional): If True, skips the verification and returns False. Defaults to False. -def check_if_model_exist(output_name, output_dir, save_model_as, headless=False): + Returns: + bool: True if the model already exists and the user chooses not to overwrite it, otherwise False. + """ if headless: log.info( "Headless mode, skipping verification if model already exist... if model already exist it will be overwritten..." @@ -66,26 +79,37 @@ def check_if_model_exist(output_name, output_dir, save_model_as, headless=False) ckpt_folder = os.path.join(output_dir, output_name) if os.path.isdir(ckpt_folder): msg = f"A diffuser model with the same name {ckpt_folder} already exists. Do you want to overwrite it?" - if not easygui.ynbox(msg, "Overwrite Existing Model?"): + if not ynbox(msg, "Overwrite Existing Model?"): log.info("Aborting training due to existing model with same name...") return True elif save_model_as in ["ckpt", "safetensors"]: ckpt_file = os.path.join(output_dir, output_name + "." + save_model_as) if os.path.isfile(ckpt_file): msg = f"A model with the same file name {ckpt_file} already exists. Do you want to overwrite it?" - if not easygui.ynbox(msg, "Overwrite Existing Model?"): + if not ynbox(msg, "Overwrite Existing Model?"): log.info("Aborting training due to existing model with same name...") return True else: log.info( - 'Can\'t verify if existing model exist when save model is set a "same as source model", continuing to train model...' + 'Can\'t verify if existing model exist when save model is set as "same as source model", continuing to train model...' ) return False return False -def output_message(msg="", title="", headless=False): +def output_message(msg: str = "", title: str = "", headless: bool = False) -> None: + """ + Outputs a message to the user, either in a message box or in the log. + + Parameters: + msg (str, optional): The message to be displayed. Defaults to an empty string. + title (str, optional): The title of the message box. Defaults to an empty string. + headless (bool, optional): If True, the message is logged instead of displayed in a message box. Defaults to False. + + Returns: + None + """ if headless: log.info(msg) else: @@ -93,36 +117,60 @@ def output_message(msg="", title="", headless=False): def create_refresh_button(refresh_component, refresh_method, refreshed_args, elem_id): + """ + Creates a refresh button that can be used to update UI components. + + Parameters: + refresh_component (list or object): The UI component(s) to be refreshed. + refresh_method (callable): The method to be called when the button is clicked. + refreshed_args (dict or callable): The arguments to be passed to the refresh method. + elem_id (str): The ID of the button element. + + Returns: + gr.Button: The configured refresh button. + """ + # Converts refresh_component into a list for uniform processing. If it's already a list, keep it the same. refresh_components = ( refresh_component if isinstance(refresh_component, list) else [refresh_component] ) + # Initialize label to None. This will store the label of the first component with a non-None label, if any. label = None + # Iterate over each component to find the first non-None label and assign it to 'label'. for comp in refresh_components: label = getattr(comp, "label", None) if label is not None: break + # Define the refresh function that will be triggered upon clicking the refresh button. def refresh(): + # Invoke the refresh_method, which is intended to perform the refresh operation. refresh_method() + # Determine the arguments for the refresh: call refreshed_args if it's callable, otherwise use it directly. args = refreshed_args() if callable(refreshed_args) else refreshed_args + # For each key-value pair in args, update the corresponding properties of each component. for k, v in args.items(): for comp in refresh_components: setattr(comp, k, v) + # Use gr.update to refresh the UI components. If multiple components are present, update each; else, update only the first. return ( - [gr.update(**(args or {})) for _ in refresh_components] + [gr.Dropdown(**(args or {})) for _ in refresh_components] if len(refresh_components) > 1 - else gr.update(**(args or {})) + else gr.Dropdown(**(args or {})) ) + # Create a refresh button with the specified label (via refresh_symbol), ID, and classes. + # 'refresh_symbol' should be defined outside this function or passed as an argument, representing the button's label or icon. refresh_button = gr.Button( value=refresh_symbol, elem_id=elem_id, elem_classes=["tool"] ) + # Configure the button to invoke the refresh function. refresh_button.click(fn=refresh, inputs=[], outputs=refresh_components) + # Return the configured refresh button to be used in the UI. return refresh_button @@ -234,11 +282,15 @@ def update_my_data(my_data): elif not value: my_data[key] = 0 - # Convert values to float if they are strings + # Convert values to float if they are strings, correctly handling float representations for key in ["noise_offset", "learning_rate", "text_encoder_lr", "unet_lr"]: value = my_data.get(key, 0) - if isinstance(value, str) and value.strip().isdigit(): - my_data[key] = float(value) + if isinstance(value, str): + try: + my_data[key] = float(value) + except ValueError: + # Handle the case where the string is not a valid float + my_data[key] = 0 elif not value: my_data[key] = 0 @@ -277,89 +329,186 @@ def get_dir_and_file(file_path): def get_file_path( file_path="", default_extension=".json", extension_name="Config files" ): + """ + Opens a file dialog to select a file, allowing the user to navigate and choose a file with a specific extension. + If no file is selected, returns the initially provided file path or an empty string if not provided. + This function is conditioned to skip the file dialog on macOS or if specific environment variables are present, + indicating a possible automated environment where a dialog cannot be displayed. + + Parameters: + - file_path (str): The initial file path or an empty string by default. Used as the fallback if no file is selected. + - default_extension (str): The default file extension (e.g., ".json") for the file dialog. + - extension_name (str): The display name for the type of files being selected (e.g., "Config files"). + + Returns: + - str: The path of the file selected by the user, or the initial `file_path` if no selection is made. + + Raises: + - TypeError: If `file_path`, `default_extension`, or `extension_name` are not strings. + + Note: + - The function checks the `ENV_EXCLUSION` list against environment variables to determine if the file dialog should be skipped, aiming to prevent its appearance during automated operations. + - The dialog will also be skipped on macOS (`sys.platform != "darwin"`) as a specific behavior adjustment. + """ + # Validate parameter types + if not isinstance(file_path, str): + raise TypeError("file_path must be a string") + if not isinstance(default_extension, str): + raise TypeError("default_extension must be a string") + if not isinstance(extension_name, str): + raise TypeError("extension_name must be a string") + + # Environment and platform check to decide on showing the file dialog if not any(var in os.environ for var in ENV_EXCLUSION) and sys.platform != "darwin": - current_file_path = file_path - # log.info(f'current file path: {current_file_path}') + current_file_path = file_path # Backup in case no file is selected - initial_dir, initial_file = get_dir_and_file(file_path) + initial_dir, initial_file = get_dir_and_file( + file_path + ) # Decompose file path for dialog setup - # Create a hidden Tkinter root window + # Initialize a hidden Tkinter window for the file dialog root = Tk() - root.wm_attributes("-topmost", 1) - root.withdraw() + root.wm_attributes("-topmost", 1) # Ensure the dialog is topmost + root.withdraw() # Hide the root window to show only the dialog - # Show the open file dialog and get the selected file path + # Open the file dialog and capture the selected file path file_path = filedialog.askopenfilename( - filetypes=( - (extension_name, f"*{default_extension}"), - ("All files", "*.*"), - ), + filetypes=((extension_name, f"*{default_extension}"), ("All files", "*.*")), defaultextension=default_extension, initialfile=initial_file, initialdir=initial_dir, ) - # Destroy the hidden root window - root.destroy() + root.destroy() # Cleanup by destroying the Tkinter root window - # If no file is selected, use the current file path + # Fallback to the initial path if no selection is made if not file_path: file_path = current_file_path - current_file_path = file_path - # log.info(f'current file path: {current_file_path}') + # Return the selected or fallback file path return file_path -def get_any_file_path(file_path=""): - if not any(var in os.environ for var in ENV_EXCLUSION) and sys.platform != "darwin": - current_file_path = file_path - # log.info(f'current file path: {current_file_path}') +def get_any_file_path(file_path: str = "") -> str: + """ + Opens a file dialog to select any file, allowing the user to navigate and choose a file. + If no file is selected, returns the initially provided file path or an empty string if not provided. + This function is conditioned to skip the file dialog on macOS or if specific environment variables are present, + indicating a possible automated environment where a dialog cannot be displayed. - initial_dir, initial_file = get_dir_and_file(file_path) + Parameters: + - file_path (str): The initial file path or an empty string by default. Used as the fallback if no file is selected. - root = Tk() - root.wm_attributes("-topmost", 1) - root.withdraw() - file_path = filedialog.askopenfilename( - initialdir=initial_dir, - initialfile=initial_file, - ) - root.destroy() + Returns: + - str: The path of the file selected by the user, or the initial `file_path` if no selection is made. - if file_path == "": - file_path = current_file_path + Raises: + - TypeError: If `file_path` is not a string. + - EnvironmentError: If there's an issue accessing environment variables. + - RuntimeError: If there's an issue initializing the file dialog. + + Note: + - The function checks the `ENV_EXCLUSION` list against environment variables to determine if the file dialog should be skipped, aiming to prevent its appearance during automated operations. + - The dialog will also be skipped on macOS (`sys.platform != "darwin"`) as a specific behavior adjustment. + """ + # Validate parameter type + if not isinstance(file_path, str): + raise TypeError("file_path must be a string") + + try: + # Check for environment variable conditions + if ( + not any(var in os.environ for var in ENV_EXCLUSION) + and sys.platform != "darwin" + ): + current_file_path: str = file_path + + initial_dir, initial_file = get_dir_and_file(file_path) + + # Initialize a hidden Tkinter window for the file dialog + root = Tk() + root.wm_attributes("-topmost", 1) + root.withdraw() + + try: + # Open the file dialog and capture the selected file path + file_path = filedialog.askopenfilename( + initialdir=initial_dir, + initialfile=initial_file, + ) + except Exception as e: + raise RuntimeError(f"Failed to open file dialog: {e}") + finally: + root.destroy() + + # Fallback to the initial path if no selection is made + if not file_path: + file_path = current_file_path + except KeyError as e: + raise EnvironmentError(f"Failed to access environment variables: {e}") + # Return the selected or fallback file path return file_path -def get_folder_path(folder_path=""): - if not any(var in os.environ for var in ENV_EXCLUSION) and sys.platform != "darwin": - current_folder_path = folder_path +def get_folder_path(folder_path: str = "") -> str: + """ + Opens a folder dialog to select a folder, allowing the user to navigate and choose a folder. + If no folder is selected, returns the initially provided folder path or an empty string if not provided. + This function is conditioned to skip the folder dialog on macOS or if specific environment variables are present, + indicating a possible automated environment where a dialog cannot be displayed. + + Parameters: + - folder_path (str): The initial folder path or an empty string by default. Used as the fallback if no folder is selected. + + Returns: + - str: The path of the folder selected by the user, or the initial `folder_path` if no selection is made. - initial_dir, initial_file = get_dir_and_file(folder_path) + Raises: + - TypeError: If `folder_path` is not a string. + - EnvironmentError: If there's an issue accessing environment variables. + - RuntimeError: If there's an issue initializing the folder dialog. + + Note: + - The function checks the `ENV_EXCLUSION` list against environment variables to determine if the folder dialog should be skipped, aiming to prevent its appearance during automated operations. + - The dialog will also be skipped on macOS (`sys.platform != "darwin"`) as a specific behavior adjustment. + """ + # Validate parameter type + if not isinstance(folder_path, str): + raise TypeError("folder_path must be a string") + + try: + # Check for environment variable conditions + if any(var in os.environ for var in ENV_EXCLUSION) or sys.platform == "darwin": + return folder_path or "" root = Tk() - root.wm_attributes("-topmost", 1) root.withdraw() - folder_path = filedialog.askdirectory(initialdir=initial_dir) + root.wm_attributes("-topmost", 1) + selected_folder = filedialog.askdirectory(initialdir=folder_path or ".") root.destroy() - - if folder_path == "": - folder_path = current_folder_path - - return folder_path + return selected_folder or folder_path + except Exception as e: + raise RuntimeError(f"Error initializing folder dialog: {e}") from e def get_saveasfile_path( - file_path="", defaultextension=".json", extension_name="Config files" -): + file_path: str = "", + defaultextension: str = ".json", + extension_name: str = "Config files", +) -> str: + # Check if the current environment is not macOS and if the environment variables do not match the exclusion list if not any(var in os.environ for var in ENV_EXCLUSION) and sys.platform != "darwin": + # Store the initial file path to use as a fallback in case no file is selected current_file_path = file_path + + # Logging the current file path for debugging purposes; helps in tracking the flow of file selection # log.info(f'current file path: {current_file_path}') + # Split the file path into directory and file name for setting the file dialog start location and filename initial_dir, initial_file = get_dir_and_file(file_path) + # Initialize a hidden Tkinter window to act as the parent for the file dialog, ensuring it appears on top root = Tk() root.wm_attributes("-topmost", 1) root.withdraw() @@ -372,33 +521,71 @@ def get_saveasfile_path( initialdir=initial_dir, initialfile=initial_file, ) + # Close the Tkinter root window to clean up the UI root.destroy() + # Logging the save file path for auditing purposes; useful in confirming the user's file choice # log.info(save_file_path) + # Default to the current file path if no file is selected, ensuring there's always a valid file path if save_file_path == None: file_path = current_file_path else: - log.info(save_file_path.name) + # Log the selected file name for transparency and tracking user actions + # log.info(save_file_path.name) + + # Update the file path with the user-selected file name, facilitating the save operation file_path = save_file_path.name + # Log the final file path for verification, ensuring the intended file is being used # log.info(file_path) + # Return the final file path, either the user-selected file or the fallback path return file_path def get_saveasfilename_path( - file_path="", extensions="*", extension_name="Config files" -): + file_path: str = "", + extensions: str = "*", + extension_name: str = "Config files", +) -> str: + """ + Opens a file dialog to select a file name for saving, allowing the user to specify a file name and location. + If no file is selected, returns the initially provided file path or an empty string if not provided. + This function is conditioned to skip the file dialog on macOS or if specific environment variables are present, + indicating a possible automated environment where a dialog cannot be displayed. + + Parameters: + - file_path (str): The initial file path or an empty string by default. Used as the fallback if no file is selected. + - extensions (str): The file extensions to filter the file dialog by. Defaults to "*" for all files. + - extension_name (str): The name to display for the file extensions in the file dialog. Defaults to "Config files". + + Returns: + - str: The path of the file selected by the user, or the initial `file_path` if no selection is made. + + Raises: + - TypeError: If `file_path` is not a string. + - EnvironmentError: If there's an issue accessing environment variables. + - RuntimeError: If there's an issue initializing the file dialog. + + Note: + - The function checks the `ENV_EXCLUSION` list against environment variables to determine if the file dialog should be skipped, aiming to prevent its appearance during automated operations. + - The dialog will also be skipped on macOS (`sys.platform == "darwin"`) as a specific behavior adjustment. + """ + # Check if the current environment is not macOS and if the environment variables do not match the exclusion list if not any(var in os.environ for var in ENV_EXCLUSION) and sys.platform != "darwin": - current_file_path = file_path + # Store the initial file path to use as a fallback in case no file is selected + current_file_path: str = file_path # log.info(f'current file path: {current_file_path}') + # Split the file path into directory and file name for setting the file dialog start location and filename initial_dir, initial_file = get_dir_and_file(file_path) + # Initialize a hidden Tkinter window to act as the parent for the file dialog, ensuring it appears on top root = Tk() root.wm_attributes("-topmost", 1) root.withdraw() + # Open the file dialog and capture the selected file path save_file_path = filedialog.asksaveasfilename( filetypes=( (f"{extension_name}", f"{extensions}"), @@ -408,14 +595,19 @@ def get_saveasfilename_path( initialdir=initial_dir, initialfile=initial_file, ) + # Close the Tkinter root window to clean up the UI root.destroy() + # Default to the current file path if no file is selected, ensuring there's always a valid file path if save_file_path == "": file_path = current_file_path else: + # Logging the save file path for auditing purposes; useful in confirming the user's file choice # log.info(save_file_path) + # Update the file path with the user-selected file name, facilitating the save operation file_path = save_file_path + # Return the final file path, either the user-selected file or the fallback path return file_path @@ -436,30 +628,45 @@ def add_pre_postfix( caption_file_ext (str, optional): Extension of the caption files. """ + # If neither prefix nor postfix is provided, return early if prefix == "" and postfix == "": return + # Define the image file extensions to filter image_extensions = (".jpg", ".jpeg", ".png", ".webp") + + # List all image files in the folder image_files = [ f for f in os.listdir(folder) if f.lower().endswith(image_extensions) ] + # Iterate over the list of image files for image_file in image_files: + # Construct the caption file name by appending the caption file extension to the image file name caption_file_name = os.path.splitext(image_file)[0] + caption_file_ext + # Construct the full path to the caption file caption_file_path = os.path.join(folder, caption_file_name) + # Check if the caption file does not exist if not os.path.exists(caption_file_path): + # Create a new caption file with the specified prefix and/or postfix with open(caption_file_path, "w", encoding="utf8") as f: + # Determine the separator based on whether both prefix and postfix are provided separator = " " if prefix and postfix else "" f.write(f"{prefix}{separator}{postfix}") else: + # Open the existing caption file for reading and writing with open(caption_file_path, "r+", encoding="utf8") as f: - content = f.read() - content = content.rstrip() + # Read the content of the caption file, stripping any trailing whitespace + content = f.read().rstrip() + # Move the file pointer to the beginning of the file f.seek(0, 0) + # Determine the separator based on whether only prefix is provided prefix_separator = " " if prefix else "" + # Determine the separator based on whether only postfix is provided postfix_separator = " " if postfix else "" + # Write the updated content to the caption file, adding prefix and/or postfix f.write( f"{prefix}{prefix_separator}{content}{postfix_separator}{postfix}" ) @@ -467,18 +674,26 @@ def add_pre_postfix( def has_ext_files(folder_path: str, file_extension: str) -> bool: """ - Check if there are any files with the specified extension in the given folder. + Determines whether any files within a specified folder have a given file extension. + + This function iterates through each file in the specified folder and checks if + its extension matches the provided file_extension argument. The search is case-sensitive + and expects file_extension to include the dot ('.') if applicable (e.g., '.txt'). Args: - folder_path (str): Path to the folder containing files. - file_extension (str): Extension of the files to look for. + folder_path (str): The absolute or relative path to the folder to search within. + file_extension (str): The file extension to search for, including the dot ('.') if applicable. Returns: - bool: True if files with the specified extension are found, False otherwise. + bool: True if at least one file with the specified extension is found, False otherwise. """ + # Iterate directly over files in the specified folder path for file in os.listdir(folder_path): + # Return True at the first occurrence of a file with the specified extension if file.endswith(file_extension): return True + + # If no file with the specified extension is found, return False return False @@ -489,90 +704,153 @@ def find_replace( replace_text: str = "", ) -> None: """ - Find and replace text in caption files within a folder. + Efficiently finds and replaces specified text across all caption files in a given folder. + + This function iterates through each caption file matching the specified extension within the given folder path, replacing all occurrences of the search text with the replacement text. It ensures that the operation only proceeds if the search text is provided and there are caption files to process. Args: - folder_path (str, optional): Path to the folder containing caption files. - caption_file_ext (str, optional): Extension of the caption files. - search_text (str, optional): Text to search for in the caption files. - replace_text (str, optional): Text to replace the search text with. + folder_path (str, optional): The directory path where caption files are located. Defaults to an empty string, which implies the current directory. + caption_file_ext (str, optional): The file extension for caption files. Defaults to ".caption". + search_text (str, optional): The text to search for within the caption files. Defaults to an empty string. + replace_text (str, optional): The text to use as a replacement. Defaults to an empty string. """ + # Log the start of the caption find/replace operation log.info("Running caption find/replace") - if not has_ext_files(folder_path, caption_file_ext): + # Validate the presence of caption files and the search text + if not search_text or not has_ext_files(folder_path, caption_file_ext): + # Display a message box indicating no files were found msgbox( f"No files with extension {caption_file_ext} were found in {folder_path}..." ) + log.warning( + "No files with extension {caption_file_ext} were found in {folder_path}..." + ) + # Exit the function early return - if search_text == "": - return - + # List all caption files in the folder caption_files = [f for f in os.listdir(folder_path) if f.endswith(caption_file_ext)] + # Iterate over the list of caption files for caption_file in caption_files: - with open(os.path.join(folder_path, caption_file), "r", errors="ignore") as f: - content = f.read() - - content = content.replace(search_text, replace_text) - - with open(os.path.join(folder_path, caption_file), "w") as f: + # Construct the full path for each caption file + file_path = os.path.join(folder_path, caption_file) + # Read and replace text + with open(file_path, "r", errors="ignore") as f: + content = f.read().replace(search_text, replace_text) + + # Write the updated content back to the file + with open(file_path, "w") as f: f.write(content) def color_aug_changed(color_aug): + """ + Handles the change in color augmentation checkbox. + + This function is called when the color augmentation checkbox is toggled. + If color augmentation is enabled, it disables the cache latent checkbox + and returns a new checkbox with the value set to False and interactive set to False. + If color augmentation is disabled, it returns a new checkbox with interactive set to True. + + Args: + color_aug (bool): The new state of the color augmentation checkbox. + + Returns: + gr.Checkbox: A new checkbox with the appropriate settings based on the color augmentation state. + """ + # If color augmentation is enabled, disable cache latent and return a new checkbox if color_aug: msgbox( 'Disabling "Cache latent" because "Color augmentation" has been selected...' ) - return gr.Checkbox.update(value=False, interactive=False) + return gr.Checkbox(value=False, interactive=False) + # If color augmentation is disabled, return a new checkbox with interactive set to True else: - return gr.Checkbox.update(value=True, interactive=True) + return gr.Checkbox(interactive=True) + +def save_inference_file( + output_dir: str, + v2: bool, + v_parameterization: bool, + output_name: str, +) -> None: + """ + Save inference file to the specified output directory. -def save_inference_file(output_dir, v2, v_parameterization, output_name): - # List all files in the directory - files = os.listdir(output_dir) + Args: + output_dir (str): Path to the output directory. + v2 (bool): Flag indicating whether to use v2 inference. + v_parameterization (bool): Flag indicating whether to use v parameterization. + output_name (str): Name of the output file. + """ + try: + # List all files in the directory + files = os.listdir(output_dir) + except Exception as e: + log.error(f"Error listing directory contents: {e}") + return # Early return on failure # Iterate over the list of files for file in files: # Check if the file starts with the value of output_name if file.startswith(output_name): # Check if it is a file or a directory - if os.path.isfile(os.path.join(output_dir, file)): + file_path = os.path.join(output_dir, file) + if os.path.isfile(file_path): # Split the file name and extension file_name, ext = os.path.splitext(file) - # Copy the v2-inference-v.yaml file to the current file, with a .yaml extension - if v2 and v_parameterization: + # Determine the source file path based on the v2 and v_parameterization flags + source_file_path = ( + rf"{scriptdir}/v2_inference/v2-inference-v.yaml" + if v2 and v_parameterization + else rf"{scriptdir}/v2_inference/v2-inference.yaml" + ) + + # Copy the source file to the current file, with a .yaml extension + try: log.info( - f"Saving v2-inference-v.yaml as {output_dir}/{file_name}.yaml" + f"Saving {source_file_path} as {output_dir}/{file_name}.yaml" ) shutil.copy( - rf"{scriptdir}/v2_inference/v2-inference-v.yaml", + source_file_path, f"{output_dir}/{file_name}.yaml", ) - elif v2: - log.info( - f"Saving v2-inference.yaml as {output_dir}/{file_name}.yaml" - ) - shutil.copy( - rf"{scriptdir}/v2_inference/v2-inference.yaml", - f"{output_dir}/{file_name}.yaml", + except Exception as e: + log.error( + f"Error copying file to {output_dir}/{file_name}.yaml: {e}" ) def set_pretrained_model_name_or_path_input( pretrained_model_name_or_path, refresh_method=None ): + """ + Sets the pretrained model name or path input based on the model type. + + This function checks the type of the pretrained model and sets the appropriate + parameters for the model. It also handles the case where the model list is + set to 'custom' and a refresh method is provided. + + Args: + pretrained_model_name_or_path (str): The name or path of the pretrained model. + refresh_method (callable, optional): A function to refresh the model list. + + Returns: + tuple: A tuple containing the Dropdown widget, v2 checkbox, v_parameterization checkbox, + and sdxl checkbox. + """ # Check if the given pretrained_model_name_or_path is in the list of SDXL models if pretrained_model_name_or_path in SDXL_MODELS: log.info("SDXL model selected. Setting sdxl parameters") - v2 = gr.Checkbox.update(value=False, visible=False) - v_parameterization = gr.Checkbox.update(value=False, visible=False) - sdxl = gr.Checkbox.update(value=True, visible=False) + v2 = gr.Checkbox(value=False, visible=False) + v_parameterization = gr.Checkbox(value=False, visible=False) + sdxl = gr.Checkbox(value=True, visible=False) return ( - gr.Dropdown().update(), + gr.Dropdown(), v2, v_parameterization, sdxl, @@ -581,11 +859,11 @@ def set_pretrained_model_name_or_path_input( # Check if the given pretrained_model_name_or_path is in the list of V2 base models if pretrained_model_name_or_path in V2_BASE_MODELS: log.info("SD v2 base model selected. Setting --v2 parameter") - v2 = gr.Checkbox.update(value=True, visible=False) - v_parameterization = gr.Checkbox.update(value=False, visible=False) - sdxl = gr.Checkbox.update(value=False, visible=False) + v2 = gr.Checkbox(value=True, visible=False) + v_parameterization = gr.Checkbox(value=False, visible=False) + sdxl = gr.Checkbox(value=False, visible=False) return ( - gr.Dropdown().update(), + gr.Dropdown(), v2, v_parameterization, sdxl, @@ -596,11 +874,11 @@ def set_pretrained_model_name_or_path_input( log.info( "SD v2 model selected. Setting --v2 and --v_parameterization parameters" ) - v2 = gr.Checkbox.update(value=True, visible=False) - v_parameterization = gr.Checkbox.update(value=True, visible=False) - sdxl = gr.Checkbox.update(value=False, visible=False) + v2 = gr.Checkbox(value=True, visible=False) + v_parameterization = gr.Checkbox(value=True, visible=False) + sdxl = gr.Checkbox(value=False, visible=False) return ( - gr.Dropdown().update(), + gr.Dropdown(), v2, v_parameterization, sdxl, @@ -609,21 +887,22 @@ def set_pretrained_model_name_or_path_input( # Check if the given pretrained_model_name_or_path is in the list of V1 models if pretrained_model_name_or_path in V1_MODELS: log.info(f"{pretrained_model_name_or_path} model selected.") - v2 = gr.Checkbox.update(value=False, visible=False) - v_parameterization = gr.Checkbox.update(value=False, visible=False) - sdxl = gr.Checkbox.update(value=False, visible=False) + v2 = gr.Checkbox(value=False, visible=False) + v_parameterization = gr.Checkbox(value=False, visible=False) + sdxl = gr.Checkbox(value=False, visible=False) return ( - gr.Dropdown().update(), + gr.Dropdown(), v2, v_parameterization, sdxl, ) # Check if the model_list is set to 'custom' - v2 = gr.Checkbox.update(visible=True) - v_parameterization = gr.Checkbox.update(visible=True) - sdxl = gr.Checkbox.update(visible=True) + v2 = gr.Checkbox(visible=True) + v_parameterization = gr.Checkbox(visible=True) + sdxl = gr.Checkbox(visible=True) + # If a refresh method is provided, use it to update the choices for the Dropdown widget if refresh_method is not None: args = dict( choices=refresh_method(pretrained_model_name_or_path), @@ -631,7 +910,7 @@ def set_pretrained_model_name_or_path_input( else: args = {} return ( - gr.Dropdown().update(**args), + gr.Dropdown(**args), v2, v_parameterization, sdxl, @@ -643,26 +922,52 @@ def set_pretrained_model_name_or_path_input( ### -# def get_pretrained_model_name_or_path_file(model_list, pretrained_model_name_or_path): -# pretrained_model_name_or_path = get_any_file_path(pretrained_model_name_or_path) -# # set_model_list(model_list, pretrained_model_name_or_path) +def get_int_or_default(kwargs, key, default_value=0): + """ + Retrieves an integer value from the provided kwargs dictionary based on the given key. If the key is not found, + or the value cannot be converted to an integer, a default value is returned. + Args: + kwargs (dict): A dictionary of keyword arguments. + key (str): The key to retrieve from the kwargs dictionary. + default_value (int, optional): The default value to return if the key is not found or the value is not an integer. -def get_int_or_default(kwargs, key, default_value=0): + Returns: + int: The integer value if found and valid, otherwise the default value. + """ + # Try to retrieve the value for the specified key from the kwargs. + # Use the provided default_value if the key does not exist. value = kwargs.get(key, default_value) - if isinstance(value, int): - return value - else: - try: - return int(value) - except ValueError: - log.info( - f"{key} is not an int, float or a string, setting value to {default_value}" - ) - return default_value + try: + # Try to convert the value to a integer. This should works for int, + # and strings that represent a valid floating-point number. + return int(value) + except (ValueError, TypeError): + # If the conversion fails (for example, the value is a string that cannot + # be converted to an integer), log the issue and return the provided default_value. + log.info( + f"{key} is not an int or cannot be converted to int, setting value to {default_value}" + ) + return default_value def get_float_or_default(kwargs, key, default_value=0.0): + """ + Retrieves a float value from the provided kwargs dictionary based on the given key. If the key is not found, + or the value cannot be converted to a float, a default value is returned. + + This function attempts to convert the value to a float, which works for integers, floats, and strings that + represent valid floating-point numbers. If the conversion fails, the issue is logged, and the provided + default_value is returned. + + Args: + kwargs (dict): A dictionary of keyword arguments. + key (str): The key to retrieve from the kwargs dictionary. + default_value (float, optional): The default value to return if the key is not found or the value is not a float. + + Returns: + float: The float value if found and valid, otherwise the default value. + """ # Try to retrieve the value for the specified key from the kwargs. # Use the provided default_value if the key does not exist. value = kwargs.get(key, default_value) @@ -681,6 +986,20 @@ def get_float_or_default(kwargs, key, default_value=0.0): def get_str_or_default(kwargs, key, default_value=""): + """ + Retrieves a string value from the provided kwargs dictionary based on the given key. If the key is not found, + or the value is not a string, a default value is returned. + + Args: + kwargs (dict): A dictionary of keyword arguments. + key (str): The key to retrieve from the kwargs dictionary. + default_value (str, optional): The default value to return if the key is not found or the value is not a string. + + Returns: + str: The string value if found and valid, otherwise the default value. + """ + # Try to retrieve the value for the specified key from the kwargs. + # Use the provided default_value if the key does not exist. value = kwargs.get(key, default_value) # Check if the retrieved value is already a string. @@ -693,6 +1012,26 @@ def get_str_or_default(kwargs, key, default_value=""): def run_cmd_advanced_training(**kwargs): + """ + This function, run_cmd_advanced_training, dynamically constructs a command line string for advanced training + configurations based on provided keyword arguments (kwargs). Each argument represents a different training parameter + or flag that can be used to customize the training process. The function checks for the presence and validity of + arguments, appending them to the command line string with appropriate formatting. + + Purpose + The primary purpose of this function is to enable flexible and customizable training configurations for machine + learning models. It allows users to specify a wide range of parameters and flags that control various aspects of + the training process, such as learning rates, batch sizes, augmentation options, precision settings, and many more. + + Args: + kwargs (dict): A variable number of keyword arguments that represent different training parameters or flags. + Each argument has a specific expected data type and format, which the function checks before + appending to the command line string. + + Returns: + str: A command line string constructed based on the provided keyword arguments. This string includes the base + command and additional parameters and flags tailored to the user's specifications for the training process + """ run_cmd = "" if "additional_parameters" in kwargs: @@ -821,6 +1160,17 @@ def run_cmd_advanced_training(**kwargs): if os.path.exists(logging_dir): run_cmd += rf' --logging_dir="{logging_dir}"' + log_tracker_name = kwargs.get("log_tracker_name") + if log_tracker_name: + run_cmd += rf' --log_tracker_name="{log_tracker_name}"' + + log_tracker_config = kwargs.get("log_tracker_config") + if log_tracker_config: + if log_tracker_config.startswith('"') and log_tracker_config.endswith('"'): + log_tracker_config = log_tracker_config[1:-1] + if os.path.exists(log_tracker_config): + run_cmd += rf' --log_tracker_config="{log_tracker_config}"' + lora_network_weights = kwargs.get("lora_network_weights") if lora_network_weights: run_cmd += f' --network_weights="{lora_network_weights}"' # Yes, the parameter is now called network_weights instead of lora_network_weights @@ -1127,6 +1477,10 @@ def run_cmd_advanced_training(**kwargs): if wandb_api_key: run_cmd += f' --wandb_api_key="{wandb_api_key}"' + wandb_run_name = kwargs.get("wandb_run_name") + if wandb_run_name: + run_cmd += f' --wandb_run_name="{wandb_run_name}"' + weighted_captions = kwargs.get("weighted_captions") if weighted_captions: run_cmd += " --weighted_captions" @@ -1140,16 +1494,31 @@ def run_cmd_advanced_training(**kwargs): return run_cmd -def verify_image_folder_pattern(folder_path): - false_response = True # temporarily set to true to prevent stopping training in case of false positive +def verify_image_folder_pattern(folder_path: str) -> bool: + """ + Verify the image folder pattern in the given folder path. + + Args: + folder_path (str): The path to the folder containing image folders. + + Returns: + bool: True if the image folder pattern is valid, False otherwise. + """ + # Initialize the return value to True + return_value = True + # Log the start of the verification process log.info(f"Verifying image folder pattern of {folder_path}...") + # Check if the folder exists if not os.path.isdir(folder_path): + # Log an error message if the folder does not exist log.error( - f"...the provided path '{folder_path}' is not a valid folder. Please follow the folder structure documentation found at docs\image_folder_structure.md ..." + f"...the provided path '{folder_path}' is not a valid folder. " + "Please follow the folder structure documentation found at docs\image_folder_structure.md ..." ) - return false_response + # Return False to indicate that the folder pattern is not valid + return False # Create a regular expression pattern to match the required sub-folder names # The pattern should start with one or more digits (\d+) followed by an underscore (_) @@ -1174,30 +1543,50 @@ def verify_image_folder_pattern(folder_path): # Print non-matching sub-folders non_matching_subfolders = set(subfolders) - set(matching_subfolders) if non_matching_subfolders: + # Log an error message if any sub-folders do not match the pattern log.error( f"...the following folders do not match the required pattern _: {', '.join(non_matching_subfolders)}" ) + # Log an error message suggesting to follow the folder structure documentation log.error( f"...please follow the folder structure documentation found at docs\image_folder_structure.md ..." ) - return false_response + # Return False to indicate that the folder pattern is not valid + return False # Check if no sub-folders exist if not matching_subfolders: + # Log an error message if no image folders are found log.error( - f"...no image folders found in {folder_path}. Please follow the folder structure documentation found at docs\image_folder_structure.md ..." + f"...no image folders found in {folder_path}. " + "Please follow the folder structure documentation found at docs\image_folder_structure.md ..." ) - return false_response + # Return False to indicate that the folder pattern is not valid + return False + # Log the successful verification log.info(f"...valid") - return True + # Return True to indicate that the folder pattern is valid + return return_value def SaveConfigFile( parameters, file_path: str, - exclusion=["file_path", "save_as", "headless", "print_only"], -): + exclusion: list = ["file_path", "save_as", "headless", "print_only"], +) -> None: + """ + Saves the configuration parameters to a JSON file, excluding specified keys. + + This function iterates over a dictionary of parameters, filters out keys listed + in the `exclusion` list, and saves the remaining parameters to a JSON file + specified by `file_path`. + + Args: + parameters (dict): Dictionary containing the configuration parameters. + file_path (str): Path to the file where the filtered parameters should be saved. + exclusion (list): List of keys to exclude from saving. Defaults to ["file_path", "save_as", "headless", "print_only"]. + """ # Return the values of the variables as a dictionary variables = { name: value @@ -1205,20 +1594,31 @@ def SaveConfigFile( if name not in exclusion } - # Save the data to the selected file + # Save the data to the specified JSON file with open(file_path, "w") as file: json.dump(variables, file, indent=2) def save_to_file(content): + """ + Appends the given content to a file named 'print_command.txt' within a 'logs' directory. + + This function checks for the existence of a 'logs' directory and creates it if + it doesn't exist. Then, it appends the provided content along with a newline character + to the 'print_command.txt' file within this directory. + + Args: + content (str): The content to be saved to the file. + """ logs_directory = "logs" file_path = os.path.join(logs_directory, "print_command.txt") - try: - # Create the 'logs' directory if it does not exist - if not os.path.exists(logs_directory): - os.makedirs(logs_directory) + # Ensure the 'logs' directory exists + if not os.path.exists(logs_directory): + os.makedirs(logs_directory) + # Append content to the specified file + try: with open(file_path, "a") as file: file.write(content + "\n") except IOError as e: @@ -1228,178 +1628,195 @@ def save_to_file(content): def check_duplicate_filenames( - folder_path, image_extension=[".gif", ".png", ".jpg", ".jpeg", ".webp"] -): + folder_path: str, + image_extension: list = [".gif", ".png", ".jpg", ".jpeg", ".webp"], +) -> None: + """ + Checks for duplicate image filenames in a given folder path. + + This function walks through the directory structure of the given folder path, + and logs a warning if it finds files with the same name but different image extensions. + This can lead to issues during training if not handled properly. + + Args: + folder_path (str): The path to the folder containing image files. + image_extension (list, optional): List of image file extensions to consider. + Defaults to [".gif", ".png", ".jpg", ".jpeg", ".webp"]. + """ + # Initialize a flag to track if duplicates are found duplicate = False - - log.info(f"Checking for duplicate image filenames in training data directory {folder_path}...") + + # Log the start of the duplicate check + log.info( + f"Checking for duplicate image filenames in training data directory {folder_path}..." + ) + + # Walk through the directory structure for root, dirs, files in os.walk(folder_path): + # Initialize a dictionary to store filenames and their paths filenames = {} + + # Process each file in the current directory for file in files: + # Split the filename and extension filename, extension = os.path.splitext(file) + + # Check if the extension is in the list of image extensions if extension.lower() in image_extension: + # Construct the full path to the file full_path = os.path.join(root, file) + + # Check if the filename is already in the dictionary if filename in filenames: + # If it is, compare the existing path with the current path existing_path = filenames[filename] if existing_path != full_path: + # Log a warning if the paths are different log.warning( f"...same filename '{filename}' with different image extension found. This will cause training issues. Rename one of the file." ) log.warning(f" Existing file: {existing_path}") log.warning(f" Current file: {full_path}") + + # Set the duplicate flag to True duplicate = True else: + # If not, add the filename and path to the dictionary filenames[filename] = full_path + + # If no duplicates were found, log a message indicating validation if not duplicate: log.info("...valid") -def validate_paths(headless:bool = False, **kwargs): +def validate_paths(headless: bool = False, **kwargs: Optional[str]) -> bool: + """ + Validates the existence of specified paths and patterns for model training configurations. + + This function checks for the existence of various directory paths and files provided as keyword arguments, + including model paths, data directories, output directories, and more. It leverages predefined default + models for validation and ensures directory creation if necessary. + + Args: + headless (bool): A flag indicating if the function should run without requiring user input. + **kwargs (Optional[str]): Keyword arguments that represent various path configurations, + including but not limited to `pretrained_model_name_or_path`, `train_data_dir`, + and more. + + Returns: + bool: True if all specified paths are valid or have been successfully created; False otherwise. + """ + + def validate_path( + path: Optional[str], path_type: str, create_if_missing: bool = False + ) -> bool: + """ + Validates the existence of a path. If the path does not exist and `create_if_missing` is True, + attempts to create the directory. + + Args: + path (Optional[str]): The path to validate. + path_type (str): Description of the path type for logging purposes. + create_if_missing (bool): Whether to create the directory if it does not exist. + + Returns: + bool: True if the path is valid or has been created; False otherwise. + """ + if path: + log.info(f"Validating {path_type} path {path} existence...") + if os.path.exists(path): + log.info("...valid") + else: + if create_if_missing: + try: + os.makedirs(path, exist_ok=True) + log.info(f"...created folder at {path}") + return True + except Exception as e: + log.error(f"...failed to create {path_type} folder: {e}") + return False + else: + log.error( + f"...{path_type} path '{path}' is missing or does not exist" + ) + return False + else: + log.info(f"{path_type} not specified, skipping validation") + return True + + # Validates the model name or path against default models or existence as a local path + if not validate_model_path(kwargs.get("pretrained_model_name_or_path")): + return False + + # Validates the existence of specified directories or files, and creates them if necessary + for key, value in kwargs.items(): + if key in ["output_dir", "logging_dir"]: + if not validate_path(value, key, create_if_missing=True): + return False + else: + if key not in ["pretrained_model_name_or_path"]: + if not validate_path(value, key): + return False + + return True + + +def validate_model_path(pretrained_model_name_or_path: Optional[str]) -> bool: + """ + Validates the pretrained model name or path against Hugging Face models or local paths. + + Args: + pretrained_model_name_or_path (Optional[str]): The pretrained model name or path to validate. + + Returns: + bool: True if the path is a valid Hugging Face model or exists locally; False otherwise. + """ from .class_source_model import default_models - pretrained_model_name_or_path = kwargs.get("pretrained_model_name_or_path") - train_data_dir = kwargs.get("train_data_dir") - reg_data_dir = kwargs.get("reg_data_dir") - output_dir = kwargs.get("output_dir") - logging_dir = kwargs.get("logging_dir") - lora_network_weights= kwargs.get("lora_network_weights") - finetune_image_folder = kwargs.get("finetune_image_folder") - resume = kwargs.get("resume") - vae = kwargs.get("vae") - - if pretrained_model_name_or_path is not None: - log.info(f"Validating model file or folder path {pretrained_model_name_or_path} existence...") - + if pretrained_model_name_or_path: + log.info( + f"Validating model file or folder path {pretrained_model_name_or_path} existence..." + ) + # Check if it matches the Hugging Face model pattern - if re.match(r'^[\w-]+\/[\w-]+$', pretrained_model_name_or_path): + if re.match(r"^[\w-]+\/[\w-]+$", pretrained_model_name_or_path): log.info("...huggingface.co model, skipping validation") elif pretrained_model_name_or_path not in default_models: # If not one of the default models, check if it's a valid local path if not os.path.exists(pretrained_model_name_or_path): - log.error(f"...source model path '{pretrained_model_name_or_path}' is missing or does not exist") + log.error( + f"...source model path '{pretrained_model_name_or_path}' is missing or does not exist" + ) return False else: log.info("...valid") else: log.info("...valid") + else: + log.info("Model name or path not specified, skipping validation") + return True - # Check if train_data_dir is valid - if train_data_dir != None: - log.info(f"Validating training data folder path {train_data_dir} existence...") - if not train_data_dir or not os.path.exists(train_data_dir): - log.error(f"Image folder path '{train_data_dir}' is missing or does not exist") - return False - else: - log.info("...valid") - - # Check if there are files with the same filename but different image extension... warn the user if it is the case. - check_duplicate_filenames(train_data_dir) - - if not verify_image_folder_pattern(folder_path=train_data_dir): - return False - - if finetune_image_folder != None: - log.info(f"Validating finetuning image folder path {finetune_image_folder} existence...") - if not finetune_image_folder or not os.path.exists(finetune_image_folder): - log.error(f"Image folder path '{finetune_image_folder}' is missing or does not exist") - return False - else: - log.info("...valid") - - if reg_data_dir != None: - if reg_data_dir != "": - log.info(f"Validating regularisation data folder path {reg_data_dir} existence...") - if not os.path.exists(reg_data_dir): - log.error("...regularisation folder does not exist") - return False - if not verify_image_folder_pattern(folder_path=reg_data_dir): - return False - log.info("...valid") - else: - log.info("Regularisation folder not specified, skipping validation") - - if output_dir != None: - log.info(f"Validating output folder path {output_dir} existence...") - if output_dir == "": - log.error("...output folder path is missing") - return False - elif not os.path.exists(output_dir): - try: - os.makedirs(output_dir, exist_ok=True) # Create the directory, no error if it already exists - log.info(f"...created folder at {output_dir}") - except Exception as e: - log.error(f"...failed to create output folder: {e}") - return False - else: - log.info("...valid") - - if logging_dir != None: - if logging_dir != "": - log.info(f"Validating logging folder path {logging_dir} existence...") - if not os.path.exists(logging_dir): - try: - os.makedirs(logging_dir, exist_ok=True) # Create the directory, no error if it already exists - log.info(f"...created folder at {logging_dir}") - except Exception as e: - log.error(f"...failed to create logging folder: {e}") - return False - else: - log.info("...valid") - else: - log.info("Logging folder not specified, skipping validation") - - if lora_network_weights != None: - if lora_network_weights != "": - log.info(f"Validating LoRA Network Weight file path {lora_network_weights} existence...") - if not os.path.exists(lora_network_weights): - log.error("...path is invalid") - return False - else: - log.info("...valid") - else: - log.info("LoRA Network Weight file not specified, skipping validation") - - if resume != None: - if resume != "": - log.info(f"Validating model resume file path {resume} existence...") - if not os.path.exists(resume): - log.error("...path is invalid") - return False - else: - log.info("...valid") - else: - log.info("Model resume file not specified, skipping validation") - - if vae != None: - if vae != "": - log.info(f"Validating VAE file path {vae} existence...") - - # Check if it matches the Hugging Face model pattern - if re.match(r'^[\w-]+\/[\w-]+$', vae): - log.info("...huggingface.co vae model provided") - elif not os.path.exists(vae): - log.error("...vae path is invalid") - return False - else: - log.info("...valid") - else: - log.info("VAE file not specified, skipping validation") - - - return True +def is_file_writable(file_path: str) -> bool: + """ + Checks if a file is writable. -def is_file_writable(file_path): + Args: + file_path (str): The path to the file to be checked. + + Returns: + bool: True if the file is writable, False otherwise. + """ + # If the file does not exist, it is considered writable if not os.path.exists(file_path): - # print(f"File '{file_path}' does not exist.") return True try: - log.warning(f"File '{file_path}' already exist... it will be overwritten...") - # Check if the file can be opened in write mode (which implies it's not open by another process) + # Attempt to open the file in append mode to check if it can be written to with open(file_path, "a"): pass + # If the file can be opened, it is considered writable return True except IOError: - log.warning(f"File '{file_path}' can't be written to...") + # If an IOError occurs, the file cannot be written to return False diff --git a/kohya_gui/convert_lcm_gui.py b/kohya_gui/convert_lcm_gui.py index 6336a7a05..81b0f5d35 100644 --- a/kohya_gui/convert_lcm_gui.py +++ b/kohya_gui/convert_lcm_gui.py @@ -133,13 +133,13 @@ def list_save_to(path): show_progress=False, ) model_path.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_models(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_models(path)), inputs=model_path, outputs=model_path, show_progress=False, ) name.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_save_to(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_save_to(path)), inputs=name, outputs=name, show_progress=False, diff --git a/kohya_gui/convert_model_gui.py b/kohya_gui/convert_model_gui.py index 0a4f615d5..500e51066 100644 --- a/kohya_gui/convert_model_gui.py +++ b/kohya_gui/convert_model_gui.py @@ -233,7 +233,7 @@ def list_target_folder(path): ) source_model_input.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_source_model(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_source_model(path)), inputs=source_model_input, outputs=source_model_input, show_progress=False, @@ -273,7 +273,7 @@ def list_target_folder(path): ) target_model_folder_input.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_target_folder(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_target_folder(path)), inputs=target_model_folder_input, outputs=target_model_folder_input, show_progress=False, diff --git a/kohya_gui/dataset_balancing_gui.py b/kohya_gui/dataset_balancing_gui.py index 50ba533df..a56594b63 100644 --- a/kohya_gui/dataset_balancing_gui.py +++ b/kohya_gui/dataset_balancing_gui.py @@ -150,7 +150,7 @@ def list_dataset_dirs(path): label='Training steps per concept per epoch', ) select_dataset_folder_input.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_dataset_dirs(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_dataset_dirs(path)), inputs=select_dataset_folder_input, outputs=select_dataset_folder_input, show_progress=False, diff --git a/kohya_gui/dreambooth_folder_creation_gui.py b/kohya_gui/dreambooth_folder_creation_gui.py index b7dde8e34..24af3c139 100644 --- a/kohya_gui/dreambooth_folder_creation_gui.py +++ b/kohya_gui/dreambooth_folder_creation_gui.py @@ -175,7 +175,7 @@ def list_train_data_dirs(path): elem_id='number_input', ) util_training_images_dir_input.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_train_data_dirs(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_train_data_dirs(path)), inputs=util_training_images_dir_input, outputs=util_training_images_dir_input, show_progress=False, @@ -210,7 +210,7 @@ def list_reg_data_dirs(path): elem_id='number_input', ) util_regularization_images_dir_input.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_reg_data_dirs(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_reg_data_dirs(path)), inputs=util_regularization_images_dir_input, outputs=util_regularization_images_dir_input, show_progress=False, @@ -236,7 +236,7 @@ def list_train_output_dirs(path): get_folder_path, outputs=util_training_dir_output ) util_training_dir_output.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_train_output_dirs(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_train_output_dirs(path)), inputs=util_training_dir_output, outputs=util_training_dir_output, show_progress=False, diff --git a/kohya_gui/dreambooth_gui.py b/kohya_gui/dreambooth_gui.py index be382c912..a40e829f8 100644 --- a/kohya_gui/dreambooth_gui.py +++ b/kohya_gui/dreambooth_gui.py @@ -46,6 +46,7 @@ PYTHON = sys.executable + def save_configuration( save_as, file_path, @@ -136,6 +137,9 @@ def save_configuration( save_last_n_steps_state, use_wandb, wandb_api_key, + wandb_run_name, + log_tracker_name, + log_tracker_config, scale_v_pred_loss_like_noise_pred, min_timestep, max_timestep, @@ -264,6 +268,9 @@ def open_configuration( save_last_n_steps_state, use_wandb, wandb_api_key, + wandb_run_name, + log_tracker_name, + log_tracker_config, scale_v_pred_loss_like_noise_pred, min_timestep, max_timestep, @@ -387,6 +394,9 @@ def train_model( save_last_n_steps_state, use_wandb, wandb_api_key, + wandb_run_name, + log_tracker_name, + log_tracker_config, scale_v_pred_loss_like_noise_pred, min_timestep, max_timestep, @@ -399,6 +409,8 @@ def train_model( headless_bool = True if headless.get("label") == "True" else False + # This function validates files or folder paths. Simply add new variables containing file of folder path + # to validate below if not validate_paths( output_dir=output_dir, pretrained_model_name_or_path=pretrained_model_name_or_path, @@ -406,6 +418,7 @@ def train_model( reg_data_dir=reg_data_dir, headless=headless_bool, logging_dir=logging_dir, + log_tracker_config=log_tracker_config, resume=resume, vae=vae, ): @@ -534,9 +547,9 @@ def train_model( ) if sdxl: - run_cmd += fr' "{scriptdir}/sd-scripts/sdxl_train.py"' + run_cmd += rf' "{scriptdir}/sd-scripts/sdxl_train.py"' else: - run_cmd += fr' "{scriptdir}/sd-scripts/train_db.py"' + run_cmd += rf' "{scriptdir}/sd-scripts/train_db.py"' # Initialize a dictionary with always-included keyword arguments kwargs_for_training = { @@ -561,6 +574,8 @@ def train_model( "keep_tokens": keep_tokens, "learning_rate": learning_rate, "logging_dir": logging_dir, + "log_tracker_name": log_tracker_name, + "log_tracker_config": log_tracker_config, "lr_scheduler": lr_scheduler, "lr_scheduler_args": lr_scheduler_args, "lr_scheduler_num_cycles": lr_scheduler_num_cycles, @@ -613,6 +628,7 @@ def train_model( "vae": vae, "vae_batch_size": vae_batch_size, "wandb_api_key": wandb_api_key, + "wandb_run_name": wandb_run_name, "weighted_captions": weighted_captions, "xformers": xformers, } @@ -660,18 +676,20 @@ def train_model( log.info(run_cmd) env = os.environ.copy() - env['PYTHONPATH'] = fr"{scriptdir}{os.pathsep}{scriptdir}/sd-scripts{os.pathsep}{env.get('PYTHONPATH', '')}" + env["PYTHONPATH"] = ( + rf"{scriptdir}{os.pathsep}{scriptdir}/sd-scripts{os.pathsep}{env.get('PYTHONPATH', '')}" + ) # Run the command executor.execute_command(run_cmd=run_cmd, env=env) - # check if output_dir/last is a folder... therefore it is a diffuser model - last_dir = pathlib.Path(f"{output_dir}/{output_name}") + # # check if output_dir/last is a folder... therefore it is a diffuser model + # last_dir = pathlib.Path(f"{output_dir}/{output_name}") - if not last_dir.is_dir(): - # Copy inference model for v2 if required - save_inference_file(output_dir, v2, v_parameterization, output_name) + # if not last_dir.is_dir(): + # # Copy inference model for v2 if required + # save_inference_file(output_dir, v2, v_parameterization, output_name) def dreambooth_tab( @@ -680,6 +698,7 @@ def dreambooth_tab( # output_dir=gr.Textbox(), # logging_dir=gr.Textbox(), headless=False, + config: dict = {}, ): dummy_db_true = gr.Label(value=True, visible=False) dummy_db_false = gr.Label(value=False, visible=False) @@ -689,10 +708,10 @@ def dreambooth_tab( gr.Markdown("Train a custom model using kohya dreambooth python code...") with gr.Column(): - source_model = SourceModel(headless=headless) + source_model = SourceModel(headless=headless, config=config) with gr.Accordion("Folders", open=False), gr.Group(): - folders = Folders(headless=headless) + folders = Folders(headless=headless, config=config) with gr.Accordion("Parameters", open=False), gr.Column(): with gr.Group(elem_id="basic_tab"): basic_training = BasicTraining( @@ -707,7 +726,7 @@ def dreambooth_tab( # sdxl_params = SDXLParameters(source_model.sdxl_checkbox, show_sdxl_cache_text_encoder_outputs=False) with gr.Accordion("Advanced", open=False, elem_id="advanced_tab"): - advanced_training = AdvancedTraining(headless=headless) + advanced_training = AdvancedTraining(headless=headless, config=config) advanced_training.color_aug.change( color_aug_changed, inputs=[advanced_training.color_aug], @@ -732,7 +751,7 @@ def dreambooth_tab( # Setup Configuration Files Gradio with gr.Accordion("Configuration", open=False): - config = ConfigurationFile(headless=headless, output_dir=folders.output_dir) + configuration = ConfigurationFile(headless=headless, config=config) with gr.Column(), gr.Group(): with gr.Row(): @@ -847,38 +866,41 @@ def dreambooth_tab( advanced_training.save_last_n_steps_state, advanced_training.use_wandb, advanced_training.wandb_api_key, + advanced_training.wandb_run_name, + advanced_training.log_tracker_name, + advanced_training.log_tracker_config, advanced_training.scale_v_pred_loss_like_noise_pred, advanced_training.min_timestep, advanced_training.max_timestep, ] - config.button_open_config.click( + configuration.button_open_config.click( open_configuration, - inputs=[dummy_db_true, config.config_file_name] + settings_list, - outputs=[config.config_file_name] + settings_list, + inputs=[dummy_db_true, configuration.config_file_name] + settings_list, + outputs=[configuration.config_file_name] + settings_list, show_progress=False, ) - config.button_load_config.click( + configuration.button_load_config.click( open_configuration, - inputs=[dummy_db_false, config.config_file_name] + settings_list, - outputs=[config.config_file_name] + settings_list, + inputs=[dummy_db_false, configuration.config_file_name] + settings_list, + outputs=[configuration.config_file_name] + settings_list, show_progress=False, ) - config.button_save_config.click( + configuration.button_save_config.click( save_configuration, - inputs=[dummy_db_false, config.config_file_name] + settings_list, - outputs=[config.config_file_name], + inputs=[dummy_db_false, configuration.config_file_name] + settings_list, + outputs=[configuration.config_file_name], show_progress=False, ) - #config.button_save_as_config.click( + # config.button_save_as_config.click( # save_configuration, # inputs=[dummy_db_true, config.config_file_name] + settings_list, # outputs=[config.config_file_name], # show_progress=False, - #) + # ) button_run.click( train_model, diff --git a/kohya_gui/extract_lora_from_dylora_gui.py b/kohya_gui/extract_lora_from_dylora_gui.py index 2cc83fff0..3c01bd204 100644 --- a/kohya_gui/extract_lora_from_dylora_gui.py +++ b/kohya_gui/extract_lora_from_dylora_gui.py @@ -134,13 +134,13 @@ def list_save_to(path): ) model.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_models(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_models(path)), inputs=model, outputs=model, show_progress=False, ) save_to.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_save_to(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_save_to(path)), inputs=save_to, outputs=save_to, show_progress=False, diff --git a/kohya_gui/extract_lora_gui.py b/kohya_gui/extract_lora_gui.py index bc6b1c170..f0d0aa4fa 100644 --- a/kohya_gui/extract_lora_gui.py +++ b/kohya_gui/extract_lora_gui.py @@ -130,7 +130,7 @@ def list_save_to(path): return list(list_files(path, exts=[".pt", ".safetensors"], all=True)) def change_sdxl(sdxl): - return gr.Dropdown().update(visible=sdxl), gr.Dropdown().update(visible=sdxl) + return gr.Dropdown(visible=sdxl), gr.Dropdown(visible=sdxl) with gr.Tab('Extract LoRA'): @@ -235,19 +235,19 @@ def change_sdxl(sdxl): ) model_tuned.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_models(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_models(path)), inputs=model_tuned, outputs=model_tuned, show_progress=False, ) model_org.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_org_models(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_org_models(path)), inputs=model_org, outputs=model_org, show_progress=False, ) save_to.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_save_to(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_save_to(path)), inputs=save_to, outputs=save_to, show_progress=False, diff --git a/kohya_gui/extract_lycoris_locon_gui.py b/kohya_gui/extract_lycoris_locon_gui.py index feb00ce36..6524dcadf 100644 --- a/kohya_gui/extract_lycoris_locon_gui.py +++ b/kohya_gui/extract_lycoris_locon_gui.py @@ -110,7 +110,7 @@ def extract_lycoris_locon( # Run the command subprocess.run(run_cmd, shell=True, env=env) - + log.info('Done extracting...') @@ -139,7 +139,7 @@ def update_mode(mode): # Iterate through the possible modes for m in modes: # Add a visibility update for each mode, setting it to True if the input mode matches the current mode in the loop - updates.append(gr.Row().update(visible=(mode == m))) + updates.append(gr.Row(visible=(mode == m))) # Return the visibility updates as a tuple return tuple(updates) @@ -166,7 +166,7 @@ def list_save_to(path): current_save_dir = path return list(list_files(path, exts=[".safetensors"], all=True)) - with gr.Tab("Extract LyCORIS LoCON"): + with gr.Tab("Extract LyCORIS LoCon"): gr.Markdown( "This utility can extract a LyCORIS LoCon network from a finetuned model." ) @@ -252,19 +252,19 @@ def list_save_to(path): ) db_model.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_models(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_models(path)), inputs=db_model, outputs=db_model, show_progress=False, ) base_model.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_base_models(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_base_models(path)), inputs=base_model, outputs=base_model, show_progress=False, ) output_name.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_save_to(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_save_to(path)), inputs=output_name, outputs=output_name, show_progress=False, diff --git a/kohya_gui/finetune_gui.py b/kohya_gui/finetune_gui.py index 4fd3cc62e..4ca796faa 100644 --- a/kohya_gui/finetune_gui.py +++ b/kohya_gui/finetune_gui.py @@ -144,6 +144,9 @@ def save_configuration( save_last_n_steps_state, use_wandb, wandb_api_key, + wandb_run_name, + log_tracker_name, + log_tracker_config, scale_v_pred_loss_like_noise_pred, sdxl_cache_text_encoder_outputs, sdxl_no_half_vae, @@ -279,6 +282,9 @@ def open_configuration( save_last_n_steps_state, use_wandb, wandb_api_key, + wandb_run_name, + log_tracker_name, + log_tracker_config, scale_v_pred_loss_like_noise_pred, sdxl_cache_text_encoder_outputs, sdxl_no_half_vae, @@ -421,6 +427,9 @@ def train_model( save_last_n_steps_state, use_wandb, wandb_api_key, + wandb_run_name, + log_tracker_name, + log_tracker_config, scale_v_pred_loss_like_noise_pred, sdxl_cache_text_encoder_outputs, sdxl_no_half_vae, @@ -444,6 +453,7 @@ def train_model( finetune_image_folder=image_folder, headless=headless_bool, logging_dir=logging_dir, + log_tracker_config=log_tracker_config, resume=resume, ): return @@ -582,6 +592,8 @@ def train_model( "keep_tokens": keep_tokens, "learning_rate": learning_rate, "logging_dir": logging_dir, + "log_tracker_name": log_tracker_name, + "log_tracker_config": log_tracker_config, "lr_scheduler": lr_scheduler, "lr_scheduler_args": lr_scheduler_args, "lr_warmup_steps": lr_warmup_steps, @@ -628,6 +640,7 @@ def train_model( "v_pred_like_loss": v_pred_like_loss, "vae_batch_size": vae_batch_size, "wandb_api_key": wandb_api_key, + "wandb_run_name": wandb_run_name, "weighted_captions": weighted_captions, "xformers": xformers, } @@ -682,15 +695,15 @@ def train_model( # Run the command executor.execute_command(run_cmd=run_cmd, env=env) - # check if output_dir/last is a folder... therefore it is a diffuser model - last_dir = pathlib.Path(f"{output_dir}/{output_name}") + # # check if output_dir/last is a folder... therefore it is a diffuser model + # last_dir = pathlib.Path(f"{output_dir}/{output_name}") - if not last_dir.is_dir(): - # Copy inference model for v2 if required - save_inference_file(output_dir, v2, v_parameterization, output_name) + # if not last_dir.is_dir(): + # # Copy inference model for v2 if required + # save_inference_file(output_dir, v2, v_parameterization, output_name) -def finetune_tab(headless=False): +def finetune_tab(headless=False, config: dict = {}): dummy_db_true = gr.Label(value=True, visible=False) dummy_db_false = gr.Label(value=False, visible=False) dummy_headless = gr.Label(value=headless, visible=False) @@ -698,12 +711,12 @@ def finetune_tab(headless=False): gr.Markdown("Train a custom model using kohya finetune python code...") with gr.Column(): - source_model = SourceModel(headless=headless, finetuning=True) + source_model = SourceModel(headless=headless, finetuning=True, config=config) image_folder = source_model.train_data_dir output_name = source_model.output_name with gr.Accordion("Folders", open=False), gr.Group(): - folders = Folders(headless=headless, train_data_dir=source_model.train_data_dir, finetune=True) + folders = Folders(headless=headless, finetune=True, config=config) output_dir = folders.output_dir logging_dir = folders.logging_dir train_dir = folders.reg_data_dir @@ -758,7 +771,7 @@ def list_presets(path): placeholder="(Optional)", info="Specify the different learning rates for each U-Net block. Specify 23 values separated by commas like 1e-3,1e-3 ... 1e-3", ) - advanced_training = AdvancedTraining(headless=headless, finetuning=True) + advanced_training = AdvancedTraining(headless=headless, finetuning=True, config=config) advanced_training.color_aug.change( color_aug_changed, inputs=[advanced_training.color_aug], @@ -812,7 +825,7 @@ def list_presets(path): # Setup Configuration Files Gradio with gr.Accordion("Configuration", open=False): - config = ConfigurationFile(headless=headless, output_dir=train_dir) + configuration = ConfigurationFile(headless=headless, config=config) with gr.Column(), gr.Group(): @@ -929,6 +942,9 @@ def list_presets(path): advanced_training.save_last_n_steps_state, advanced_training.use_wandb, advanced_training.wandb_api_key, + advanced_training.wandb_run_name, + advanced_training.log_tracker_name, + advanced_training.log_tracker_config, advanced_training.scale_v_pred_loss_like_noise_pred, sdxl_params.sdxl_cache_text_encoder_outputs, sdxl_params.sdxl_no_half_vae, @@ -936,12 +952,12 @@ def list_presets(path): advanced_training.max_timestep, ] - config.button_open_config.click( + configuration.button_open_config.click( open_configuration, - inputs=[dummy_db_true, dummy_db_false, config.config_file_name] + inputs=[dummy_db_true, dummy_db_false, configuration.config_file_name] + settings_list + [training_preset], - outputs=[config.config_file_name] + settings_list + [training_preset], + outputs=[configuration.config_file_name] + settings_list + [training_preset], show_progress=False, ) @@ -952,12 +968,12 @@ def list_presets(path): # show_progress=False, # ) - config.button_load_config.click( + configuration.button_load_config.click( open_configuration, - inputs=[dummy_db_false, dummy_db_false, config.config_file_name] + inputs=[dummy_db_false, dummy_db_false, configuration.config_file_name] + settings_list + [training_preset], - outputs=[config.config_file_name] + settings_list + [training_preset], + outputs=[configuration.config_file_name] + settings_list + [training_preset], show_progress=False, ) @@ -970,7 +986,7 @@ def list_presets(path): training_preset.input( open_configuration, - inputs=[dummy_db_false, dummy_db_true, config.config_file_name] + inputs=[dummy_db_false, dummy_db_true, configuration.config_file_name] + settings_list + [training_preset], outputs=[gr.Textbox(visible=False)] + settings_list + [training_preset], @@ -991,10 +1007,10 @@ def list_presets(path): show_progress=False, ) - config.button_save_config.click( + configuration.button_save_config.click( save_configuration, - inputs=[dummy_db_false, config.config_file_name] + settings_list, - outputs=[config.config_file_name], + inputs=[dummy_db_false, configuration.config_file_name] + settings_list, + outputs=[configuration.config_file_name], show_progress=False, ) diff --git a/kohya_gui/git_caption_gui.py b/kohya_gui/git_caption_gui.py index 268f1f3d3..171b8c78b 100644 --- a/kohya_gui/git_caption_gui.py +++ b/kohya_gui/git_caption_gui.py @@ -104,19 +104,19 @@ def list_train_dirs(path): with gr.Row(): caption_ext = gr.Textbox( label='Caption file extension', - placeholder='Extention for caption file. eg: .caption, .txt', + placeholder='Extension for caption file (e.g., .caption, .txt)', value='.txt', interactive=True, ) prefix = gr.Textbox( - label='Prefix to add to BLIP caption', + label='Prefix to add to GIT caption', placeholder='(Optional)', interactive=True, ) postfix = gr.Textbox( - label='Postfix to add to BLIP caption', + label='Postfix to add to GIT caption', placeholder='(Optional)', interactive=True, ) @@ -156,7 +156,7 @@ def list_train_dirs(path): ) train_data_dir.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_train_dirs(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_train_dirs(path)), inputs=train_data_dir, outputs=train_data_dir, show_progress=False, diff --git a/kohya_gui/group_images_gui.py b/kohya_gui/group_images_gui.py index a3420bc92..86854fb40 100644 --- a/kohya_gui/group_images_gui.py +++ b/kohya_gui/group_images_gui.py @@ -113,13 +113,13 @@ def list_output_dirs(path): ) input_folder.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_input_dirs(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_input_dirs(path)), inputs=input_folder, outputs=input_folder, show_progress=False, ) output_folder.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_output_dirs(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_output_dirs(path)), inputs=output_folder, outputs=output_folder, show_progress=False, diff --git a/kohya_gui/lora_gui.py b/kohya_gui/lora_gui.py index 25fbdf613..8cc551ca9 100644 --- a/kohya_gui/lora_gui.py +++ b/kohya_gui/lora_gui.py @@ -52,7 +52,8 @@ document_symbol = "\U0001F4C4" # 📄 -presets_dir = fr'{scriptdir}/presets' +presets_dir = rf"{scriptdir}/presets" + def save_configuration( save_as, @@ -170,6 +171,9 @@ def save_configuration( save_last_n_steps_state, use_wandb, wandb_api_key, + wandb_run_name, + log_tracker_name, + log_tracker_config, scale_v_pred_loss_like_noise_pred, scale_weight_norms, network_dropout, @@ -337,6 +341,9 @@ def open_configuration( save_last_n_steps_state, use_wandb, wandb_api_key, + wandb_run_name, + log_tracker_name, + log_tracker_config, scale_v_pred_loss_like_noise_pred, scale_weight_norms, network_dropout, @@ -362,7 +369,7 @@ def open_configuration( if apply_preset: if training_preset != "none": log.info(f"Applying preset {training_preset}...") - file_path = fr'{presets_dir}/lora/{training_preset}.json' + file_path = rf"{presets_dir}/lora/{training_preset}.json" else: # If not applying a preset, set the `training_preset` field to an empty string # Find the index of the `training_preset` parameter using the `index()` method @@ -536,6 +543,9 @@ def train_model( save_last_n_steps_state, use_wandb, wandb_api_key, + wandb_run_name, + log_tracker_name, + log_tracker_config, scale_v_pred_loss_like_noise_pred, scale_weight_norms, network_dropout, @@ -565,6 +575,7 @@ def train_model( reg_data_dir=reg_data_dir, headless=headless_bool, logging_dir=logging_dir, + log_tracker_config=log_tracker_config, resume=resume, vae=vae, lora_network_weights=lora_network_weights, @@ -702,9 +713,9 @@ def train_model( ) if sdxl: - run_cmd += fr' "{scriptdir}/sd-scripts/sdxl_train_network.py"' + run_cmd += rf' "{scriptdir}/sd-scripts/sdxl_train_network.py"' else: - run_cmd += fr' "{scriptdir}/sd-scripts/train_network.py"' + run_cmd += rf' "{scriptdir}/sd-scripts/train_network.py"' if LoRA_type == "LyCORIS/Diag-OFT": network_module = "lycoris.kohya" @@ -834,18 +845,19 @@ def train_model( # Convert learning rates to float once and store the result for re-use if text_encoder_lr is None: output_message( - msg="Please input valid Text Encoder learning rate (between 0 and 1)", headless=headless_bool + msg="Please input valid Text Encoder learning rate (between 0 and 1)", + headless=headless_bool, ) return if unet_lr is None: output_message( - msg="Please input valid Unet learning rate (between 0 and 1)", headless=headless_bool + msg="Please input valid Unet learning rate (between 0 and 1)", + headless=headless_bool, ) return text_encoder_lr_float = float(text_encoder_lr) unet_lr_float = float(unet_lr) - - + # Determine the training configuration based on learning rate values if text_encoder_lr_float == 0 and unet_lr_float == 0: @@ -867,7 +879,9 @@ def train_model( bucket_reso_steps=bucket_reso_steps, cache_latents=cache_latents, cache_latents_to_disk=cache_latents_to_disk, - cache_text_encoder_outputs=True if sdxl and sdxl_cache_text_encoder_outputs else None, + cache_text_encoder_outputs=( + True if sdxl and sdxl_cache_text_encoder_outputs else None + ), caption_dropout_every_n_epochs=caption_dropout_every_n_epochs, caption_dropout_rate=caption_dropout_rate, caption_extension=caption_extension, @@ -886,6 +900,8 @@ def train_model( keep_tokens=keep_tokens, learning_rate=learning_rate, logging_dir=logging_dir, + log_tracker_name=log_tracker_name, + log_tracker_config=log_tracker_config, lora_network_weights=lora_network_weights, lr_scheduler=lr_scheduler, lr_scheduler_args=lr_scheduler_args, @@ -952,6 +968,7 @@ def train_model( vae=vae, vae_batch_size=vae_batch_size, wandb_api_key=wandb_api_key, + wandb_run_name=wandb_run_name, weighted_captions=weighted_captions, xformers=xformers, ) @@ -988,7 +1005,9 @@ def train_model( log.info(run_cmd) env = os.environ.copy() - env['PYTHONPATH'] = fr"{scriptdir}{os.pathsep}{scriptdir}/sd-scripts{os.pathsep}{env.get('PYTHONPATH', '')}" + env["PYTHONPATH"] = ( + rf"{scriptdir}{os.pathsep}{scriptdir}/sd-scripts{os.pathsep}{env.get('PYTHONPATH', '')}" + ) # Run the command executor.execute_command(run_cmd=run_cmd, env=env) @@ -1009,16 +1028,16 @@ def lora_tab( output_dir_input=gr.Dropdown(), logging_dir_input=gr.Dropdown(), headless=False, + config: dict = {}, ): dummy_db_true = gr.Label(value=True, visible=False) dummy_db_false = gr.Label(value=False, visible=False) dummy_headless = gr.Label(value=headless, visible=False) - with gr.Tab("Training"), gr.Column(variant="compact"): + with gr.Tab("Training"), gr.Column(variant="compact") as tab: gr.Markdown( "Train a custom model using kohya train network LoRA python code..." ) - with gr.Column(): source_model = SourceModel( save_model_as_choices=[ @@ -1026,16 +1045,17 @@ def lora_tab( "safetensors", ], headless=headless, + config=config, ) with gr.Accordion("Folders", open=False), gr.Group(): - folders = Folders(headless=headless) + folders = Folders(headless=headless, config=config) with gr.Accordion("Parameters", open=False), gr.Column(): def list_presets(path): json_files = [] - + # Insert an empty string at the beginning json_files.insert(0, "none") @@ -1054,9 +1074,9 @@ def list_presets(path): training_preset = gr.Dropdown( label="Presets", - choices=[""] + list_presets(fr"{presets_dir}/lora"), + choices=[""] + list_presets(rf"{presets_dir}/lora"), elem_id="myDropdown", - value="none" + value="none", ) with gr.Group(elem_id="basic_tab"): @@ -1091,7 +1111,7 @@ def list_presets(path): ], value="full", visible=False, - interactive=True + interactive=True, # info="https://github.com/KohakuBlueleaf/LyCORIS/blob/0006e2ffa05a48d8818112d9f70da74c0cd30b99/docs/Preset.md" ) with gr.Group(): @@ -1133,7 +1153,7 @@ def list_presets(path): minimum=0, maximum=1, ) - + unet_lr = gr.Number( label="Unet learning rate", value="0.0001", @@ -1199,7 +1219,7 @@ def list_presets(path): decompose_both = gr.Checkbox( value=False, label="LoKr decompose both", - info=" Controls whether both input and output dimensions of the layer's weights are decomposed into smaller matrices for reparameterization.", + info="Controls whether both input and output dimensions of the layer's weights are decomposed into smaller matrices for reparameterization.", visible=False, ) train_on_input = gr.Checkbox( @@ -1423,28 +1443,32 @@ def update_LoRA_settings( "conv_dim": { "gr_type": gr.Slider, "update_params": { - "maximum": 100000 - if LoRA_type - in { - "LyCORIS/LoHa", - "LyCORIS/LoKr", - "LyCORIS/Diag-OFT", - } - else 512, + "maximum": ( + 100000 + if LoRA_type + in { + "LyCORIS/LoHa", + "LyCORIS/LoKr", + "LyCORIS/Diag-OFT", + } + else 512 + ), "value": conv_dim, # if conv_dim > 512 else conv_dim, }, }, "network_dim": { "gr_type": gr.Slider, "update_params": { - "maximum": 100000 - if LoRA_type - in { - "LyCORIS/LoHa", - "LyCORIS/LoKr", - "LyCORIS/Diag-OFT", - } - else 512, + "maximum": ( + 100000 + if LoRA_type + in { + "LyCORIS/LoHa", + "LyCORIS/LoKr", + "LyCORIS/Diag-OFT", + } + else 512 + ), "value": network_dim, # if network_dim > 512 else network_dim, }, }, @@ -1693,7 +1717,7 @@ def update_LoRA_settings( info="Specify the alpha of each block when expanding LoRA to Conv2d 3x3. Specify 25 numbers. If omitted, the value of conv_alpha is used.", ) advanced_training = AdvancedTraining( - headless=headless, training_type="lora" + headless=headless, training_type="lora", config=config ) advanced_training.color_aug.change( color_aug_changed, @@ -1752,11 +1776,9 @@ def update_LoRA_settings( ) gradio_dataset_balancing_tab(headless=headless) - # Setup Configuration Files Gradio - with gr.Accordion('Configuration', open=False): - config = ConfigurationFile(headless=headless, output_dir=folders.output_dir) - + with gr.Accordion("Configuration", open=False): + configuration = ConfigurationFile(headless=headless, config=config) with gr.Column(), gr.Group(): with gr.Row(): @@ -1897,6 +1919,9 @@ def update_LoRA_settings( advanced_training.save_last_n_steps_state, advanced_training.use_wandb, advanced_training.wandb_api_key, + advanced_training.wandb_run_name, + advanced_training.log_tracker_name, + advanced_training.log_tracker_config, advanced_training.scale_v_pred_loss_like_noise_pred, scale_weight_norms, network_dropout, @@ -1912,23 +1937,23 @@ def update_LoRA_settings( advanced_training.debiased_estimation_loss, ] - config.button_open_config.click( + configuration.button_open_config.click( open_configuration, - inputs=[dummy_db_true, dummy_db_false, config.config_file_name] + inputs=[dummy_db_true, dummy_db_false, configuration.config_file_name] + settings_list + [training_preset], - outputs=[config.config_file_name] + outputs=[configuration.config_file_name] + settings_list + [training_preset, convolution_row], show_progress=False, ) - config.button_load_config.click( + configuration.button_load_config.click( open_configuration, - inputs=[dummy_db_false, dummy_db_false, config.config_file_name] + inputs=[dummy_db_false, dummy_db_false, configuration.config_file_name] + settings_list + [training_preset], - outputs=[config.config_file_name] + outputs=[configuration.config_file_name] + settings_list + [training_preset, convolution_row], show_progress=False, @@ -1936,26 +1961,28 @@ def update_LoRA_settings( training_preset.input( open_configuration, - inputs=[dummy_db_false, dummy_db_true, config.config_file_name] + inputs=[dummy_db_false, dummy_db_true, configuration.config_file_name] + settings_list + [training_preset], - outputs=[gr.Textbox(visible=False)] + settings_list + [training_preset, convolution_row], + outputs=[gr.Textbox(visible=False)] + + settings_list + + [training_preset, convolution_row], show_progress=False, ) - config.button_save_config.click( + configuration.button_save_config.click( save_configuration, - inputs=[dummy_db_false, config.config_file_name] + settings_list, - outputs=[config.config_file_name], + inputs=[dummy_db_false, configuration.config_file_name] + settings_list, + outputs=[configuration.config_file_name], show_progress=False, ) - #config.button_save_as_config.click( + # config.button_save_as_config.click( # save_configuration, # inputs=[dummy_db_true, config.config_file_name] + settings_list, # outputs=[config.config_file_name], # show_progress=False, - #) + # ) button_run.click( train_model, @@ -1972,19 +1999,15 @@ def update_LoRA_settings( ) with gr.Tab("Tools"): - lora_tools = LoRATools( - train_data_dir=source_model.train_data_dir, - reg_data_dir=folders.reg_data_dir, - output_dir=folders.output_dir, - logging_dir=folders.logging_dir, - headless=headless - ) + lora_tools = LoRATools(headless=headless) with gr.Tab("Guides"): gr.Markdown("This section provide Various LoRA guides and information...") - if os.path.exists(fr"{scriptdir}/docs/LoRA/top_level.md"): + if os.path.exists(rf"{scriptdir}/docs/LoRA/top_level.md"): with open( - os.path.join(fr"{scriptdir}/docs/LoRA/top_level.md"), "r", encoding="utf8" + os.path.join(rf"{scriptdir}/docs/LoRA/top_level.md"), + "r", + encoding="utf8", ) as file: guides_top_level = file.read() + "\n" gr.Markdown(guides_top_level) diff --git a/kohya_gui/manual_caption_gui.py b/kohya_gui/manual_caption_gui.py index 778305068..9494b5dd2 100644 --- a/kohya_gui/manual_caption_gui.py +++ b/kohya_gui/manual_caption_gui.py @@ -303,7 +303,7 @@ def list_images_dirs(path): load_images_button = gr.Button('Load', elem_id='open_folder') caption_ext = gr.Textbox( label='Caption file extension', - placeholder='Extension for caption file. eg: .caption, .txt', + placeholder='Extension for caption file (e.g., .caption, .txt)', value='.txt', interactive=True, ) @@ -312,7 +312,7 @@ def list_images_dirs(path): ) images_dir.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_images_dirs(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_images_dirs(path)), inputs=images_dir, outputs=images_dir, show_progress=False, diff --git a/kohya_gui/merge_lora_gui.py b/kohya_gui/merge_lora_gui.py index 5d949ab26..db60d92ef 100644 --- a/kohya_gui/merge_lora_gui.py +++ b/kohya_gui/merge_lora_gui.py @@ -129,7 +129,7 @@ def list_save_to(path): sdxl_model = gr.Checkbox(label='SDXL model', value=False) sd_model.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_sd_models(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_sd_models(path)), inputs=sd_model, outputs=sd_model, show_progress=False, @@ -179,13 +179,13 @@ def list_save_to(path): ) lora_a_model.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_a_models(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_a_models(path)), inputs=lora_a_model, outputs=lora_a_model, show_progress=False, ) lora_b_model.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_b_models(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_b_models(path)), inputs=lora_b_model, outputs=lora_b_model, show_progress=False, @@ -253,13 +253,13 @@ def list_save_to(path): show_progress=False, ) lora_c_model.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_c_models(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_c_models(path)), inputs=lora_c_model, outputs=lora_c_model, show_progress=False, ) lora_d_model.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_d_models(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_d_models(path)), inputs=lora_d_model, outputs=lora_d_model, show_progress=False, @@ -319,7 +319,7 @@ def list_save_to(path): ) save_to.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_save_to(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_save_to(path)), inputs=save_to, outputs=save_to, show_progress=False, diff --git a/kohya_gui/merge_lycoris_gui.py b/kohya_gui/merge_lycoris_gui.py index 317f815ec..546f52c97 100644 --- a/kohya_gui/merge_lycoris_gui.py +++ b/kohya_gui/merge_lycoris_gui.py @@ -135,13 +135,13 @@ def list_save_to(path): ) base_model.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_models(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_models(path)), inputs=base_model, outputs=base_model, show_progress=False, ) lycoris_model.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_lycoris_model(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_lycoris_model(path)), inputs=lycoris_model, outputs=lycoris_model, show_progress=False, @@ -203,14 +203,14 @@ def list_save_to(path): ) output_name.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_save_to(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_save_to(path)), inputs=output_name, outputs=output_name, show_progress=False, ) with gr.Row(): - is_sdxl = gr.Checkbox(label='is sdxl', value=False, interactive=True) + is_sdxl = gr.Checkbox(label='is SDXL', value=False, interactive=True) is_v2 = gr.Checkbox(label='is v2', value=False, interactive=True) merge_button = gr.Button('Merge model') diff --git a/kohya_gui/resize_lora_gui.py b/kohya_gui/resize_lora_gui.py index 948fd0b25..be71bd469 100644 --- a/kohya_gui/resize_lora_gui.py +++ b/kohya_gui/resize_lora_gui.py @@ -149,13 +149,13 @@ def list_save_to(path): show_progress=False, ) model.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_models(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_models(path)), inputs=model, outputs=model, show_progress=False, ) save_to.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_save_to(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_save_to(path)), inputs=save_to, outputs=save_to, show_progress=False, @@ -183,7 +183,7 @@ def list_save_to(path): ) with gr.Row(): - verbose = gr.Checkbox(label='Verbose', value=True) + verbose = gr.Checkbox(label='Verbose logging', value=True) save_precision = gr.Radio( label='Save precision', choices=['fp16', 'bf16', 'float'], diff --git a/kohya_gui/svd_merge_lora_gui.py b/kohya_gui/svd_merge_lora_gui.py index ed5059396..196089519 100644 --- a/kohya_gui/svd_merge_lora_gui.py +++ b/kohya_gui/svd_merge_lora_gui.py @@ -189,13 +189,13 @@ def list_save_to(path): show_progress=False, ) lora_a_model.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_a_models(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_a_models(path)), inputs=lora_a_model, outputs=lora_a_model, show_progress=False, ) lora_b_model.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_b_models(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_b_models(path)), inputs=lora_b_model, outputs=lora_b_model, show_progress=False, @@ -261,13 +261,13 @@ def list_save_to(path): ) lora_c_model.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_c_models(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_c_models(path)), inputs=lora_c_model, outputs=lora_c_model, show_progress=False, ) lora_d_model.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_d_models(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_d_models(path)), inputs=lora_d_model, outputs=lora_d_model, show_progress=False, @@ -329,7 +329,7 @@ def list_save_to(path): show_progress=False, ) save_to.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_save_to(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_save_to(path)), inputs=save_to, outputs=save_to, show_progress=False, diff --git a/kohya_gui/textual_inversion_gui.py b/kohya_gui/textual_inversion_gui.py index 39391a9a5..a9e94408e 100644 --- a/kohya_gui/textual_inversion_gui.py +++ b/kohya_gui/textual_inversion_gui.py @@ -137,6 +137,9 @@ def save_configuration( save_last_n_steps_state, use_wandb, wandb_api_key, + wandb_run_name, + log_tracker_name, + log_tracker_config, scale_v_pred_loss_like_noise_pred, min_timestep, max_timestep, @@ -268,6 +271,9 @@ def open_configuration( save_last_n_steps_state, use_wandb, wandb_api_key, + wandb_run_name, + log_tracker_name, + log_tracker_config, scale_v_pred_loss_like_noise_pred, min_timestep, max_timestep, @@ -392,6 +398,9 @@ def train_model( save_last_n_steps_state, use_wandb, wandb_api_key, + wandb_run_name, + log_tracker_name, + log_tracker_config, scale_v_pred_loss_like_noise_pred, min_timestep, max_timestep, @@ -412,6 +421,7 @@ def train_model( reg_data_dir=reg_data_dir, headless=headless_bool, logging_dir=logging_dir, + log_tracker_config=log_tracker_config, resume=resume, vae=vae, ): @@ -425,7 +435,9 @@ def train_model( output_message(msg="Init word is missing", headless=headless_bool) return - if not print_only_bool and check_if_model_exist(output_name, output_dir, save_model_as, headless_bool): + if not print_only_bool and check_if_model_exist( + output_name, output_dir, save_model_as, headless_bool + ): return # Get a list of all subfolders in train_data_dir @@ -511,9 +523,9 @@ def train_model( ) if sdxl: - run_cmd += fr' "{scriptdir}/sd-scripts/sdxl_train_textual_inversion.py"' + run_cmd += rf' "{scriptdir}/sd-scripts/sdxl_train_textual_inversion.py"' else: - run_cmd += fr' "{scriptdir}/sd-scripts/train_textual_inversion.py"' + run_cmd += rf' "{scriptdir}/sd-scripts/train_textual_inversion.py"' run_cmd += run_cmd_advanced_training( adaptive_noise_scale=adaptive_noise_scale, @@ -535,6 +547,8 @@ def train_model( keep_tokens=keep_tokens, learning_rate=learning_rate, logging_dir=logging_dir, + log_tracker_name=log_tracker_name, + log_tracker_config=log_tracker_config, lr_scheduler=lr_scheduler, lr_scheduler_args=lr_scheduler_args, lr_scheduler_num_cycles=lr_scheduler_num_cycles, @@ -588,6 +602,7 @@ def train_model( vae=vae, vae_batch_size=vae_batch_size, wandb_api_key=wandb_api_key, + wandb_run_name=wandb_run_name, xformers=xformers, ) run_cmd += f' --token_string="{token_string}"' @@ -633,29 +648,32 @@ def train_model( log.info(run_cmd) env = os.environ.copy() - env['PYTHONPATH'] = fr"{scriptdir}{os.pathsep}{scriptdir}/sd-scripts{os.pathsep}{env.get('PYTHONPATH', '')}" + env["PYTHONPATH"] = ( + rf"{scriptdir}{os.pathsep}{scriptdir}/sd-scripts{os.pathsep}{env.get('PYTHONPATH', '')}" + ) # Run the command executor.execute_command(run_cmd=run_cmd, env=env) - # check if output_dir/last is a folder... therefore it is a diffuser model - last_dir = pathlib.Path(fr"{output_dir}/{output_name}") + # # check if output_dir/last is a folder... therefore it is a diffuser model + # last_dir = pathlib.Path(fr"{output_dir}/{output_name}") - if not last_dir.is_dir(): - # Copy inference model for v2 if required - save_inference_file(output_dir, v2, v_parameterization, output_name) + # if not last_dir.is_dir(): + # # Copy inference model for v2 if required + # save_inference_file(output_dir, v2, v_parameterization, output_name) -def ti_tab( - headless=False, - default_output_dir=None, -): +def ti_tab(headless=False, default_output_dir=None, config: dict = {}): dummy_db_true = gr.Label(value=True, visible=False) dummy_db_false = gr.Label(value=False, visible=False) dummy_headless = gr.Label(value=headless, visible=False) - current_embedding_dir = default_output_dir if default_output_dir is not None and default_output_dir != "" else os.path.join(scriptdir, "outputs") + current_embedding_dir = ( + default_output_dir + if default_output_dir is not None and default_output_dir != "" + else os.path.join(scriptdir, "outputs") + ) with gr.Tab("Training"), gr.Column(variant="compact"): gr.Markdown("Train a TI using kohya textual inversion python code...") @@ -667,10 +685,11 @@ def ti_tab( "safetensors", ], headless=headless, + config=config, ) with gr.Accordion("Folders", open=False), gr.Group(): - folders = Folders(headless=headless) + folders = Folders(headless=headless, config=config) with gr.Accordion("Parameters", open=False), gr.Column(): with gr.Group(elem_id="basic_tab"): with gr.Row(): @@ -678,20 +697,31 @@ def ti_tab( def list_embedding_files(path): nonlocal current_embedding_dir current_embedding_dir = path - return list(list_files(path, exts=[".pt", ".ckpt", ".safetensors" ], all=True)) + return list( + list_files( + path, exts=[".pt", ".ckpt", ".safetensors"], all=True + ) + ) weights = gr.Dropdown( - label='Resume TI training (Optional. Path to existing TI embedding file to keep training)', + label="Resume TI training (Optional. Path to existing TI embedding file to keep training)", choices=[""] + list_embedding_files(current_embedding_dir), value="", interactive=True, allow_custom_value=True, ) - create_refresh_button(weights, lambda: None, lambda: {"choices": list_embedding_files(current_embedding_dir)}, "open_folder_small") + create_refresh_button( + weights, + lambda: None, + lambda: { + "choices": list_embedding_files(current_embedding_dir) + }, + "open_folder_small", + ) weights_file_input = gr.Button( "📂", elem_id="open_folder_small", - elem_classes=['tool'], + elem_classes=["tool"], visible=(not headless), ) weights_file_input.click( @@ -700,7 +730,9 @@ def list_embedding_files(path): show_progress=False, ) weights.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_embedding_files(path)), + fn=lambda path: gr.Dropdown( + choices=[""] + list_embedding_files(path) + ), inputs=weights, outputs=weights, show_progress=False, @@ -749,7 +781,7 @@ def list_embedding_files(path): ) with gr.Accordion("Advanced", open=False, elem_id="advanced_tab"): - advanced_training = AdvancedTraining(headless=headless) + advanced_training = AdvancedTraining(headless=headless, config=config) advanced_training.color_aug.change( color_aug_changed, inputs=[advanced_training.color_aug], @@ -772,11 +804,9 @@ def list_embedding_files(path): ) gradio_dataset_balancing_tab(headless=headless) - # Setup Configuration Files Gradio with gr.Accordion("Configuration", open=False): - config = ConfigurationFile(headless=headless, output_dir=folders.output_dir) - + configuration = ConfigurationFile(headless=headless) with gr.Column(), gr.Group(): with gr.Row(): @@ -891,39 +921,42 @@ def list_embedding_files(path): advanced_training.save_last_n_steps_state, advanced_training.use_wandb, advanced_training.wandb_api_key, + advanced_training.wandb_run_name, + advanced_training.log_tracker_name, + advanced_training.log_tracker_config, advanced_training.scale_v_pred_loss_like_noise_pred, advanced_training.min_timestep, advanced_training.max_timestep, sdxl_params.sdxl_no_half_vae, ] - config.button_open_config.click( + configuration.button_open_config.click( open_configuration, - inputs=[dummy_db_true, config.config_file_name] + settings_list, - outputs=[config.config_file_name] + settings_list, + inputs=[dummy_db_true, configuration.config_file_name] + settings_list, + outputs=[configuration.config_file_name] + settings_list, show_progress=False, ) - config.button_load_config.click( + configuration.button_load_config.click( open_configuration, - inputs=[dummy_db_false, config.config_file_name] + settings_list, - outputs=[config.config_file_name] + settings_list, + inputs=[dummy_db_false, configuration.config_file_name] + settings_list, + outputs=[configuration.config_file_name] + settings_list, show_progress=False, ) - config.button_save_config.click( + configuration.button_save_config.click( save_configuration, - inputs=[dummy_db_false, config.config_file_name] + settings_list, - outputs=[config.config_file_name], + inputs=[dummy_db_false, configuration.config_file_name] + settings_list, + outputs=[configuration.config_file_name], show_progress=False, ) - #config.button_save_as_config.click( + # config.button_save_as_config.click( # save_configuration, # inputs=[dummy_db_true, config.config_file_name] + settings_list, # outputs=[config.config_file_name], # show_progress=False, - #) + # ) button_run.click( train_model, diff --git a/kohya_gui/verify_lora_gui.py b/kohya_gui/verify_lora_gui.py index aa172739f..4113ad1b2 100644 --- a/kohya_gui/verify_lora_gui.py +++ b/kohya_gui/verify_lora_gui.py @@ -99,7 +99,7 @@ def list_models(path): verify_button = gr.Button('Verify', variant='primary') lora_model.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_models(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_models(path)), inputs=lora_model, outputs=lora_model, show_progress=False, diff --git a/kohya_gui/wd14_caption_gui.py b/kohya_gui/wd14_caption_gui.py index 6c5095f75..744de2ac0 100644 --- a/kohya_gui/wd14_caption_gui.py +++ b/kohya_gui/wd14_caption_gui.py @@ -11,69 +11,90 @@ def caption_images( - train_data_dir, - caption_extension, - batch_size, - general_threshold, - character_threshold, - replace_underscores, - model, - recursive, - max_data_loader_n_workers, - debug, - undesired_tags, - frequency_tags, - prefix, - postfix, - onnx, - append_tags, - force_download, - caption_separator -): + train_data_dir: str, + caption_extension: str, + batch_size: int, + general_threshold: float, + character_threshold: float, + replace_underscores: bool, + repo_id: str, + recursive: bool, + max_data_loader_n_workers: int, + debug: bool, + undesired_tags: str, + frequency_tags: bool, + prefix: str, + postfix: str, + onnx: bool, + append_tags: bool, + force_download: bool, + caption_separator: str, +) -> None: + """ + Captions images in a given directory using the WD14 model. + + Args: + train_data_dir (str): The directory containing the images to be captioned. + caption_extension (str): The extension to be used for the caption files. + batch_size (int): The batch size for the captioning process. + general_threshold (float): The general threshold for the captioning process. + character_threshold (float): The character threshold for the captioning process. + replace_underscores (bool): Whether to replace underscores in filenames with spaces. + repo_id (str): The ID of the repository containing the WD14 model. + recursive (bool): Whether to process subdirectories recursively. + max_data_loader_n_workers (int): The maximum number of workers for the data loader. + debug (bool): Whether to enable debug mode. + undesired_tags (str): Comma-separated list of tags to be removed from the captions. + frequency_tags (bool): Whether to include frequency tags in the captions. + prefix (str): The prefix to be added to the captions. + postfix (str): The postfix to be added to the captions. + onnx (bool): Whether to use ONNX for the captioning process. + append_tags (bool): Whether to append tags to existing tags. + force_download (bool): Whether to force the model to be downloaded. + caption_separator (str): The separator to be used for the captions. + """ # Check for images_dir_input - if train_data_dir == '': - msgbox('Image folder is missing...') + if train_data_dir == "": + msgbox("Image folder is missing...") return - if caption_extension == '': - msgbox('Please provide an extension for the caption files.') + if caption_extension == "": + msgbox("Please provide an extension for the caption files.") return - log.info(f'Captioning files in {train_data_dir}...') - run_cmd = fr'accelerate launch "{scriptdir}/sd-scripts/finetune/tag_images_by_wd14_tagger.py"' - run_cmd += f' --batch_size={int(batch_size)}' - run_cmd += f' --general_threshold={general_threshold}' - run_cmd += f' --character_threshold={character_threshold}' + log.info(f"Captioning files in {train_data_dir}...") + run_cmd = rf'accelerate launch "{scriptdir}/sd-scripts/finetune/tag_images_by_wd14_tagger.py"' + if append_tags: + run_cmd += f" --append_tags" + run_cmd += f" --batch_size={int(batch_size)}" run_cmd += f' --caption_extension="{caption_extension}"' run_cmd += f' --caption_separator="{caption_separator}"' - run_cmd += f' --model="{model}"' - run_cmd += ( - f' --max_data_loader_n_workers="{int(max_data_loader_n_workers)}"' - ) - - if recursive: - run_cmd += f' --recursive' + run_cmd += f" --character_threshold={character_threshold}" if debug: - run_cmd += f' --debug' - if replace_underscores: - run_cmd += f' --remove_underscore' + run_cmd += f" --debug" + if force_download: + run_cmd += f" --force_download" if frequency_tags: - run_cmd += f' --frequency_tags' + run_cmd += f" --frequency_tags" + run_cmd += f" --general_threshold={general_threshold}" + run_cmd += f' --max_data_loader_n_workers="{int(max_data_loader_n_workers)}"' if onnx: - run_cmd += f' --onnx' - if append_tags: - run_cmd += f' --append_tags' - if force_download: - run_cmd += f' --force_download' - - if not undesired_tags == '': + run_cmd += f" --onnx" + if recursive: + run_cmd += f" --recursive" + if replace_underscores: + run_cmd += f" --remove_underscore" + run_cmd += f' --repo_id="{repo_id}"' + if not undesired_tags == "": run_cmd += f' --undesired_tags="{undesired_tags}"' - run_cmd += fr' "{train_data_dir}"' + run_cmd += rf' "{train_data_dir}"' log.info(run_cmd) env = os.environ.copy() - env['PYTHONPATH'] = fr"{scriptdir}{os.pathsep}{scriptdir}/sd-scripts{os.pathsep}{env.get('PYTHONPATH', '')}" + env["PYTHONPATH"] = ( + rf"{scriptdir}{os.pathsep}{scriptdir}/sd-scripts{os.pathsep}{env.get('PYTHONPATH', '')}" + ) # Run the command subprocess.run(run_cmd, shell=True, env=env) @@ -86,7 +107,7 @@ def caption_images( postfix=postfix, ) - log.info('...captioning done') + log.info("...captioning done") ### @@ -97,7 +118,11 @@ def caption_images( def gradio_wd14_caption_gui_tab(headless=False, default_train_dir=None): from .common_gui import create_refresh_button - default_train_dir = default_train_dir if default_train_dir is not None else os.path.join(scriptdir, "data") + default_train_dir = ( + default_train_dir + if default_train_dir is not None + else os.path.join(scriptdir, "data") + ) current_train_dir = default_train_dir def list_train_dirs(path): @@ -105,24 +130,32 @@ def list_train_dirs(path): current_train_dir = path return list(list_dirs(path)) - with gr.Tab('WD14 Captioning'): + with gr.Tab("WD14 Captioning"): gr.Markdown( - 'This utility will use WD14 to caption files for each images in a folder.' + "This utility will use WD14 to caption files for each images in a folder." ) # Input Settings # with gr.Section('Input Settings'): with gr.Group(), gr.Row(): train_data_dir = gr.Dropdown( - label='Image folder to caption (containing the images to caption)', + label="Image folder to caption (containing the images to caption)", choices=[""] + list_train_dirs(default_train_dir), value="", interactive=True, allow_custom_value=True, ) - create_refresh_button(train_data_dir, lambda: None, lambda: {"choices": list_train_dirs(current_train_dir)},"open_folder_small") + create_refresh_button( + train_data_dir, + lambda: None, + lambda: {"choices": list_train_dirs(current_train_dir)}, + "open_folder_small", + ) button_train_data_dir_input = gr.Button( - '📂', elem_id='open_folder_small', elem_classes=['tool'], visible=(not headless) + "📂", + elem_id="open_folder_small", + elem_classes=["tool"], + visible=(not headless), ) button_train_data_dir_input.click( get_folder_path, @@ -132,97 +165,101 @@ def list_train_dirs(path): caption_extension = gr.Textbox( label='Caption file extension', - placeholder='Extention for caption file. eg: .caption, .txt', + placeholder='Extension for caption file (e.g., .caption, .txt)', value='.txt', interactive=True, ) caption_separator = gr.Textbox( - label='Caption Separator', - value=',', + label="Caption Separator", + value=",", interactive=True, ) undesired_tags = gr.Textbox( - label='Undesired tags', - placeholder='(Optional) Separate `undesired_tags` with comma `(,)` if you want to remove multiple tags, e.g. `1girl,solo,smile`.', + label="Undesired tags", + placeholder="(Optional) Separate `undesired_tags` with comma `(,)` if you want to remove multiple tags, e.g. `1girl,solo,smile`.", interactive=True, ) with gr.Row(): prefix = gr.Textbox( - label='Prefix to add to WD14 caption', - placeholder='(Optional)', + label="Prefix to add to WD14 caption", + placeholder="(Optional)", interactive=True, ) postfix = gr.Textbox( - label='Postfix to add to WD14 caption', - placeholder='(Optional)', + label="Postfix to add to WD14 caption", + placeholder="(Optional)", interactive=True, ) with gr.Row(): onnx = gr.Checkbox( - label='Use onnx', + label="Use onnx", value=False, interactive=True, - info="https://github.com/onnx/onnx" + info="https://github.com/onnx/onnx", ) append_tags = gr.Checkbox( - label='Append TAGs', + label="Append TAGs", value=False, interactive=True, - info="This option appends the tags to the existing tags, instead of replacing them." + info="This option appends the tags to the existing tags, instead of replacing them.", ) with gr.Row(): replace_underscores = gr.Checkbox( - label='Replace underscores in filenames with spaces', + label="Replace underscores in filenames with spaces", value=True, interactive=True, ) recursive = gr.Checkbox( - label='Recursive', + label="Recursive", value=False, - info='Tag subfolders images as well', + info="Tag subfolders images as well", ) debug = gr.Checkbox( - label='Verbose logging', + label="Verbose logging", value=True, - info='Debug while tagging, it will print your image file with general tags and character tags.', + info="Debug while tagging, it will print your image file with general tags and character tags.", ) frequency_tags = gr.Checkbox( - label='Show tags frequency', + label="Show tags frequency", value=True, - info='Show frequency of tags for images.', + info="Show frequency of tags for images.", ) # Model Settings with gr.Row(): - model = gr.Dropdown( - label='Model', + repo_id = gr.Dropdown( + label="Repo ID", choices=[ - 'SmilingWolf/wd-v1-4-convnext-tagger-v2', - 'SmilingWolf/wd-v1-4-convnextv2-tagger-v2', - 'SmilingWolf/wd-v1-4-vit-tagger-v2', - 'SmilingWolf/wd-v1-4-swinv2-tagger-v2', - 'SmilingWolf/wd-v1-4-moat-tagger-v2', + "SmilingWolf/wd-v1-4-convnext-tagger-v2", + "SmilingWolf/wd-v1-4-convnextv2-tagger-v2", + "SmilingWolf/wd-v1-4-vit-tagger-v2", + "SmilingWolf/wd-v1-4-swinv2-tagger-v2", + "SmilingWolf/wd-v1-4-moat-tagger-v2", + # 'SmilingWolf/wd-swinv2-tagger-v3', + # 'SmilingWolf/wd-vit-tagger-v3', + # 'SmilingWolf/wd-convnext-tagger-v3', ], - value='SmilingWolf/wd-v1-4-convnextv2-tagger-v2', + value="SmilingWolf/wd-v1-4-convnextv2-tagger-v2", + show_label="Repo id for wd14 tagger on Hugging Face", ) force_download = gr.Checkbox( - label='Force model re-download', + label="Force model re-download", value=False, - info='Useful to force model re download when switching to onnx', + info="Useful to force model re download when switching to onnx", ) general_threshold = gr.Slider( value=0.35, - label='General threshold', - info='Adjust `general_threshold` for pruning tags (less tags, less flexible)', + label="General threshold", + info="Adjust `general_threshold` for pruning tags (less tags, less flexible)", minimum=0, maximum=1, step=0.05, @@ -230,7 +267,6 @@ def list_train_dirs(path): character_threshold = gr.Slider( value=0.35, label='Character threshold', - info='useful if you want to train with character', minimum=0, maximum=1, step=0.05, @@ -238,15 +274,13 @@ def list_train_dirs(path): # Advanced Settings with gr.Row(): - batch_size = gr.Number( - value=8, label='Batch size', interactive=True - ) + batch_size = gr.Number(value=8, label="Batch size", interactive=True) max_data_loader_n_workers = gr.Number( - value=2, label='Max dataloader workers', interactive=True + value=2, label="Max dataloader workers", interactive=True ) - caption_button = gr.Button('Caption images') + caption_button = gr.Button("Caption images") caption_button.click( caption_images, @@ -257,7 +291,7 @@ def list_train_dirs(path): general_threshold, character_threshold, replace_underscores, - model, + repo_id, recursive, max_data_loader_n_workers, debug, @@ -268,13 +302,13 @@ def list_train_dirs(path): onnx, append_tags, force_download, - caption_separator + caption_separator, ], show_progress=False, ) train_data_dir.change( - fn=lambda path: gr.Dropdown().update(choices=[""] + list_train_dirs(path)), + fn=lambda path: gr.Dropdown(choices=[""] + list_train_dirs(path)), inputs=train_data_dir, outputs=train_data_dir, show_progress=False, diff --git a/localizations/zh-TW.json b/localizations/zh-TW.json index df172cb9f..8011bfce8 100644 --- a/localizations/zh-TW.json +++ b/localizations/zh-TW.json @@ -1,508 +1,481 @@ - - { - "-Need to add resources here": "-需要在此添加資料", - "(Experimental, Optional) Since the latent is close to a normal distribution, it may be a good idea to specify a value around 1/10 the noise offset.": " (選填,實驗性功能) 由於潛空間接近常態分布,或許指定一個噪聲偏移約 1/10 的數值是個不錯的作法。", - "(Optional) Add training comment to be included in metadata": " (選填) 在訓練的後設資料加入註解。", - "(Optional) Enforce number of epoch": " (選填) 強制指定一個週期 (Epoch) 數量", - "(Optional) Enforce number of steps": " (選填) 強制指定一個總步數數量", - "(Optional) Save only the specified number of models (old models will be deleted)": " (選填) 僅儲存指定數量的模型 (舊有模型將被刪除) ", - "(Optional) Save only the specified number of states (old models will be deleted)": " (選填) 僅儲存指定數量的訓練資料 (舊有訓練資料將被刪除) ", - "(Optional) Stable Diffusion base model": " (選填) 穩定擴散基礎模型", - "(Optional) Stable Diffusion model": " (選填) 穩定擴散模型", - "(Optional) The model is saved every specified steps": " (選填) 模型會在指定的間隔步數後儲存", - "(Optional)": " (選填) ", - "Optional": "選填", - "Optional. Se": "選填", - "(Optional) Directory containing the regularisation images": " (選填) 含有正規化圖片的資料夾", - "Eg: asd": "例如:asd", - "Eg: person": "例如:person", - "Folder containing the concepts folders to balance...": "含有要平衡的概念資料夾的資料夾路徑...", - "Balance dataset": "平衡資料集", - "Clamp Quantile": "夾取分位數", - "Minimum difference": "最小化差異", - "network dim for linear layer in fixed mode": "固定模式下線性層的網路維度", - "network dim for conv layer in fixed mode": "固定模式下卷積層的網路維度", - "Sparsity for sparse bias": "稀疏偏差的稀疏度", - "path for the file to save...": "儲存檔案的路徑...", - "Verify LoRA": "驗證 LoRA", - "Verify": "驗證", - "Verification output": "驗證輸出", - "Verification error": "驗證錯誤", - "New Rank": "新維度 (Network Rank)", - "New Conv Rank": "新卷積維度 (Conv Rank)", - "Directory containing the images to group": "含有要分組的圖片的資料夾路徑", - "Directory where the grouped images will be stored": "要儲存分組圖片的資料夾路徑", - "Group images": "分組圖片", - "Group Images": "分組圖片", - "Captioning": "標記文字", - "Caption images": "標記圖片", - "(Optional) model id for GIT in Hugging Face": " (選填) Hugging Face 中 GIT 的模型 ID", - "Undesired tags": "不需要的標籤", - "(Optional) Separate `undesired_tags` with comma `(,)` if you want to remove multiple tags, e.g. `1girl,solo,smile`.": " (選填) 如果要移除多個標籤,請使用逗號 `(,)` 分隔 `undesired_tags`,例如:`1girl,solo,smile`。", - "Prefix to add to WD14 caption": "要加入到 WD14 標記文字的前綴", - "Postfix to add to WD14 caption": "要加入到 WD14 標記文字的後綴", - "This option appends the tags to the existing tags, instead of replacing them.": "此選項將標籤附加到現有標籤,而不是取代它們。", - "Append TAGs": "附加標籤", - "Replace underscores in filenames with spaces": "將檔案名稱中的底線替換為空格", - "Tag subfolders images as well": "標記子資料夾中的圖片", - "Recursive": "遞迴標記", - "Debug while tagging, it will print your image file with general tags and character tags.": "標記時除錯,它會列印出你的圖片檔案與一般標籤和角色標籤。", - "Verbose logging": "詳細記錄", - "Show frequency of tags for images.": "顯示圖片的標籤頻率。", - "Show tags frequency": "顯示標籤頻率", - "Model": "模型", - "Useful to force model re download when switching to onnx": "切換到 onnx 時,強制重新下載模型", - "Force model re-download": "強制重新下載模型", - "General threshold": "一般閾值", - "Adjust `general_threshold` for pruning tags (less tags, less flexible)": "調整 `general_threshold` 以修剪標籤 (標籤越少,彈性越小)", - "Character threshold": "角色閾值", - "useful if you want to train with character": "如果你想要使用角色訓練,這很有用", - "Max dataloader workers": "最大資料載入工作數", - "Comma separated list of tags": "逗號分隔的標籤列表", - "Load 💾": "讀取 💾", - "Import 📄": "匯入 📄", - "Options": "選項", - "Caption Separator": "標記文字分隔符號", - "VAE batch size": "VAE 批次大小", - "Max grad norm": "最大梯度規範 (Max grad norm)", - "Learning rate Unet": "Unet 學習率", - "Set to 0 to not train the Unet": "設為 0 以不訓練 Unet", - "Learning rate TE": "文字編碼器學習率", - "Set to 0 to not train the Text Encoder": "設為 0 以不訓練文字編碼器", - "Tools": "工具", - "Convert to LCM": "轉換模型到 LCM", - "This utility convert a model to an LCM model.": "此工具將模型轉換為 LCM 模型。", - "Stable Diffusion model to convert to LCM": "要轉換為 LCM 的穩定擴散模型", - "Name of the new LCM model": "新 LCM 模型的名稱", - "Path to the LCM file to create": "要建立的 LCM 檔案的路徑", - "type the configuration file path or use the 'Open' button above to select it...": "輸入設定檔案的路徑,或使用上方的「Open 📂」按鈕來選擇它...", - "Adjusts the scale of the rank dropout to maintain the average dropout rate, ensuring more consistent regularization across different layers.": "調整維度 (Rank) 捨棄的比例,以維持平均捨棄率,確保在不同層之間更一致的正規化。", - "Rank Dropout Scale": "維度 (Rank) 捨棄比例", - "Selects trainable layers in a network, but trains normalization layers identically across methods as they lack matrix decomposition.": "選擇網路中可訓練的層,但由於缺乏矩陣分解,因此以相同的方式訓練正規化層。", - "Train Norm": "訓練正規化", - "LyCORIS Preset": "LyCORIS 預設範本", - "Presets": "預設範本", - "Efficiently decompose tensor shapes, resulting in a sequence of convolution layers with varying dimensions and Hadamard product implementation through multiplication of two distinct tensors.": "有效地分解張量形狀,從而產生一系列具有不同維度的卷積層,並通過兩個不同張量的乘法實現 Hadamard 乘積。", - "Use Tucker decomposition": "使用 Tucker 分解", - "Train an additional scalar in front of the weight difference, use a different weight initialization strategy.": "在權重差異前訓練額外的標量,使用不同的權重初始化策略。", - "Use Scalar": "使用標量", - "applies an additional scaling factor to the oft_blocks, allowing for further adjustment of their impact on the model's transformations.": "對 oft_blocks 應用額外的縮放因子,從而進一步調整其對模型轉換的影響。", - "Rescaled OFT": "重新縮放 OFT", - "Constrain OFT": "限制 OFT", - "Limits the norm of the oft_blocks, ensuring that their magnitude does not exceed a specified threshold, thus controlling the extent of the transformation applied.": "限制 oft_blocks 的規範,確保其大小不超過指定的閾值,從而控制應用的轉換程度。", - "LoKr factor": "LoKr 因子", - "Set if we change the information going into the system (True) or the information coming out of it (False).": "選用後會改變進入系統的訓練資料,若不選則會改變輸出系統的訓練資料。", - "iA3 train on input": "iA3 訓練輸入", - "Controls whether both input and output dimensions of the layer's weights are decomposed into smaller matrices for reparameterization.": "控制層權重的輸入和輸出維度是否被分解為較小的矩陣以進行重新參數化。", - "LoKr decompose both": "LoKr 同時分解", - "Strength of the LCM": "LCM 的強度", - "folder where the training configuration files will be saved": "訓練設定檔案將會被儲存的資料夾路徑", - "folder where the training images are located": "訓練圖片的資料夾路徑", - "folder where the model will be saved": "模型將會被儲存的資料夾路徑", - "Model type": "模型類型", - "Extract LCM": "提取 LCM", - "Verify LoRA": "驗證 LoRA", - "Path to an existing LoRA network weights to resume training from": "要從中繼續訓練的現有 LoRA 網路權重的路徑", - "Seed": "種子", - "(Optional) eg:1234": " (選填) 例如:1234", - "(Optional) eg: \"milestones=[1,10,30,50]\" \"gamma=0.1\"": " (選填) 例如: \"milestones=[1,10,30,50]\" \"gamma=0.1\"", - "(Optional) eg: relative_step=True scale_parameter=True warmup_init=True": " (選填) 例如:relative_step=True scale_parameter=True warmup_init=True", - "(Optional) For Cosine with restart and polynomial only": " (選填) 只適用於餘弦函數並使用重啟 (cosine_with_restart) 和多項式 (polynomial)", - "Network Rank (Dimension)": "網路維度 (Rank)", - "Network Alpha": "網路 Alpha", - "alpha for LoRA weight scaling": "LoRA 權重縮放的 Alpha 值", - "Convolution Rank (Dimension)": "卷積維度 (Rank)", - "Convolution Alpha": "卷積 Alpha", - "Max Norm Regularization is a technique to stabilize network training by limiting the norm of network weights. It may be effective in suppressing overfitting of LoRA and improving stability when used with other LoRAs. See PR #545 on kohya_ss/sd_scripts repo for details. Recommended setting: 1. Higher is weaker, lower is stronger.": "最大規範正規化是一種穩定網路訓練的技術,通過限制網路權重的規範來實現。當與其他 LoRA 一起使用時,它可能會有效地抑制 LoRA 的過度擬合並提高穩定性。詳細資料請見 kohya_ss/sd_scripts Github 上的 PR#545。建議設置:1.0 越高越弱,越低越強。", - "Is a normal probability dropout at the neuron level. In the case of LoRA, it is applied to the output of down. Recommended range 0.1 to 0.5": "是神經元級的正常概率捨棄。在 LoRA 的情況下,它被應用於 Down Sampler 的輸出。建議範圍 0.1 到 0.5", - "can specify `rank_dropout` to dropout each rank with specified probability. Recommended range 0.1 to 0.3": "可以指定 `rank_dropout` 以指定的概率捨棄每個維度。建議範圍 0.1 到 0.3", - "can specify `module_dropout` to dropout each rank with specified probability. Recommended range 0.1 to 0.3": "可以指定 `module_dropout` 以指定的概率捨棄每個維度。建議範圍 0.1 到 0.3", - "Folder where the training folders containing the images are located": "訓練資料夾的資料夾路徑,包含圖片", - "(Optional) Folder where where the regularization folders containing the images are located": " (選填) 正規化資料夾的資料夾路徑,包含圖片", - "Folder to output trained model": "輸出訓練模型的資料夾路徑", - "Optional: enable logging and output TensorBoard log to this folder": "選填:啟用記錄並將 TensorBoard 記錄輸出到此資料夾", - "Pretrained model name or path": "預訓練模型名稱或路徑", - "enter the path to custom model or name of pretrained model": "輸入自訂模型的路徑或預訓練模型的名稱", - "(Name of the model to output)": " (輸出的模型名稱)", - "LoRA type": "LoRA 類型", - "(Optional) path to checkpoint of vae to replace for training": " (選填) 要替換訓練的 VAE checkpoint 的路徑", - "(Optional) Use to provide additional parameters not handled by the GUI. Eg: --some_parameters \"value\"": " (選填) 用於提供 GUI 未處理的額外參數。例如:--some_parameters \"value\"", - "Automates the processing of noise, allowing for faster model fitting, as well as balancing out color issues": "自動處理噪聲,可以更快地擬合模型,同時平衡顏色問題", - "Debiased Estimation loss": "偏差估算損失 (Debiased Estimation loss)", - "(Optional) Override number of epoch. Default: 8": " (選填) 覆蓋週期 (Epoch) 數量。預設:8", - "Weights": "權重", - "Down LR weights": "Down LR 權重", - "Mid LR weights": "Mid LR 權重", - "Up LR weights": "Up LR 權重", - "Blocks LR zero threshold": "區塊 LR 零閾值", - "(Optional) eg: 0,0,0,0,0,0,1,1,1,1,1,1": " (選填) 例如:0,0,0,0,0,0,1,1,1,1,1,1", - "(Optional) eg: 0.5": " (選填) 例如:0.5", - "(Optional) eg: 0.1": " (選填) 例如:0.1", - "Specify the learning rate weight of the down blocks of U-Net.": "指定 U-Net 下區塊的學習率權重。", - "Specify the learning rate weight of the mid block of U-Net.": "指定 U-Net 中區塊的學習率權重。", - "Specify the learning rate weight of the up blocks of U-Net. The same as down_lr_weight.": "指定 U-Net 上區塊的學習率權重。與 down_lr_weight 相同。", - "If the weight is not more than this value, the LoRA module is not created. The default is 0.": "如果權重不超過此值,則不會創建 LoRA 模組。預設為 0。", - "Blocks": "區塊", - "Block dims": "區塊維度", - "(Optional) eg: 2,2,2,2,4,4,4,4,6,6,6,6,8,6,6,6,6,4,4,4,4,2,2,2,2": " (選填) 例如:2,2,2,2,4,4,4,4,6,6,6,6,8,6,6,6,6,4,4,4,4,2,2,2,2", - "Specify the dim (rank) of each block. Specify 25 numbers.": "指定每個區塊的維度 (Rank)。指定 25 個數字。", - "Specify the alpha of each block. Specify 25 numbers as with block_dims. If omitted, the value of network_alpha is used.": "指定每個區塊的 Alpha。與區塊維度一樣,指定 25 個數字。如果省略,則使用網路 Alpha 的值。", - "Conv": "卷積", - "Conv dims": "卷積維度 (dims)", - "Conv alphas": "卷積 Alphas", - "Extend LoRA to Conv2d 3x3 and specify the dim (rank) of each block. Specify 25 numbers.": "將 LoRA 擴展到 Conv2d 3x3,並指定每個區塊的維度 (Rank)。指定 25 個數字。", - "Specify the alpha of each block when expanding LoRA to Conv2d 3x3. Specify 25 numbers. If omitted, the value of conv_alpha is used.": "將 LoRA 擴展到 Conv2d 3x3 時,指定每個區塊的 Alpha。指定 25 個數字。如果省略,則使用卷積 Alpha 的值。", - "Weighted captions": "加權標記文字", - "About SDXL training": "關於 SDXL 訓練", - "Adaptive noise scale": "自適應噪聲比例", - "Additional parameters": "額外參數", - "Advanced options": "進階選項", - "Advanced parameters": "進階參數", - "Advanced": "進階", - "ashleykleynhans runpod docker builds": "ashleykleynhans runpod docker 建構", - "Automatically determine the dim(rank) from the weight file.": "從指定的權重檔案自動決定 dim(rank)。", - "Autosave": "自動儲存", - "Basic Captioning": "基本標記", - "Basic": "基本", - "Batch size": "批次大小", - "BLIP Captioning": "BLIP 標記", - "Bucket resolution steps": "分桶解析度間隔", - "Built with Gradio": "使用 Gradio 建構", - "Cache latents to disk": "暫存潛空間資料到硬碟", - "Cache latents": "暫存潛空間資料", - "Caption file extension": "標記檔案副檔名", - "Caption Extension": "標記檔案副檔名", - "(Optional) Extension for caption files. default: .caption": " (選填) 標記檔案的副檔名。預設:.caption", - "Caption text": "標記文字", - "caption": "標記", - "Change History": "變更記錄", - "Class prompt": "類 (Class) 提詞", - "Color augmentation": "顏色增強", - "Configuration file": "設定檔", - "constant_with_warmup": "常數並使用預熱 (constant_with_warmup)", - "constant": "常數 (constant)", - "Conv Dimension (Rank)": "卷積維度 (Rank)", - "Conv Dimension": "卷積維度", - "Convert model": "轉換模型", - "Copy info to Folders Tab": "複製資訊到資料夾頁籤", - "cosine_with_restarts": "餘弦函數並使用重啟", - "cosine": "餘弦函數 (cosine)", - "CrossAttention": "交叉注意力", - "DANGER!!! -- Insecure folder renaming -- DANGER!!!": "危險!!! -- 不安全的資料夾重新命名 -- 危險!!!", - "Dataset folder": "資料集資料夾", - "Dataset preparation": "資料集準備", - "Dataset Preparation": "資料集準備", - "Dataset repeats": "資料集重複數", - "Desired LoRA rank": "希望 LoRA 的維度 (Rank)", - "Destination training directory": "訓練結果資料夾", - "Device": "裝置", - "DIM from weights": "從權重讀取 DIM", - "Directory containing the images to caption": "含有需標記的圖片資料夾", - "Directory containing the training images": "訓練的圖片資料夾", - "Directory where formatted training and regularisation folders will be placed": "訓練與正規化資料夾將會被取代", - "Disable CP decomposition": "停用 CP 分解法", - "Do not copy other files in the input folder to the output folder": "不要將輸入資料夾中的其他檔案,複製到輸出資料夾", - "Do not copy other files": "不複製其他檔案", - "Don't upscale bucket resolution": "不要放大分桶解析度", - "Dreambooth/LoRA Dataset balancing": "Dreambooth/LoRA 資料集平衡", - "Dreambooth/LoRA Folder preparation": "Dreambooth/LoRA 準備資料夾", - "Dropout caption every n epochs": "在每 N 個週期 (Epoch) 丟棄標記", - "DyLoRA model": "DyLoRA 模型", - "Dynamic method": "壓縮演算法", - "Dynamic parameter": "壓縮參數", - "e.g., \"by some artist\". Leave empty if you only want to add a prefix or postfix.": "例如,\"由某個藝術家創作\"。如果你只想加入前綴或後綴,請留空白。", - "e.g., \"by some artist\". Leave empty if you want to replace with nothing.": "例如,\"由某個藝術家創作\"。如果你想用空值取代,請留空白。", - "Enable buckets": "啟用資料桶", - "Enable for Hugging Face's stabilityai models": "啟用 HuggingFace 的 stabilityai 模型", - "Enter one sample prompt per line to generate multiple samples per cycle. Optional specifiers include: --w (width), --h (height), --d (seed), --l (cfg scale), --s (sampler steps) and --n (negative prompt). To modify sample prompts during training, edit the prompt.txt file in the samples directory.": "每行輸入一個提示詞來生成每個訓練週期的輸出範本。可以選擇指定的參數,包括:--w (寬度) ,--h (高度) ,--d (種子) ,--l (CFG 比例) ,--s (採樣器步驟) 和 --n (負面提示詞) 。如果要在訓練週期中修改提示詞,請修改範本目錄中的 prompt.txt 檔案。", - "Epoch": "週期 (Epoch)", - "Error": "錯誤", - "Example of the optimizer settings for Adafactor with the fixed learning rate:": "固定學習率 Adafactor 優化器的設定範例:", - "Extract DyLoRA": "提取 DyLoRA", - "Extract LoRA model": "提取 LoRA模型", - "Extract LoRA": "提取 LoRA", - "Extract LyCORIS LoCon": "提取 LyCORIS LoCon", - "Extract LyCORIS LoCON": "提取 LyCORIS LoCON", - "FileNotFoundError": "錯誤!檔案找不到", - "Find text": "尋找文字", - "Finetune": "微調", - "Finetuned model": "微調模型", - "Finetuning Resource Guide": "微調資源指南", - "fixed": "固定", - "Flip augmentation": "翻轉增強", - "float16": "float16", - "Folders": "資料夾", - "U-Net and Text Encoder can be trained with fp8 (experimental)": "U-Net 與 Text Encoder 可以使用 fp8 訓練 (實驗性功能)", - "fp8 base training (experimental)": "使用 fp8 基礎訓練 (實驗性功能)", - "Full bf16 training (experimental)": "完整使用 bf16 訓練 (實驗性功能)", - "Full fp16 training (experimental)": "完整使用 fp16 訓練 (實驗性功能)", - "Generate caption files for the grouped images based on their folder name": "根據圖片的資料夾名稱生成標記文字檔案", - "Generate caption metadata": "生成標記文字後設資料", - "Generate Captions": "生成標記文字", - "Generate image buckets metadata": "生成圖像分桶後設資料", - "GIT Captioning": "GIT 標記文字", - "Gradient accumulate steps": "梯度累加步數", - "Gradient checkpointing": "梯度檢查點", - "Group size": "群組大小", - "Guidelines for SDXL Finetuning": "SDXL 微調指南", - "Guides": "指南", - "How to Create a LoRA Part 1: Dataset Preparation:": "如何建立 LoRA 第 1 部份:資料集準備:", - "If unchecked, tensorboard will be used as the default for logging.": "如果不勾選,Tensorboard 將會使用預設的紀錄方式。", - "If you have valuable resources to add, kindly create a PR on Github.": "如果你有有價值的資源要增加,請在 Github 上建立一個 PR。", - "Ignore Imported Tags Above Word Count": "略過高於字數數量的標記標籤", - "Image folder to caption": "要加入標記的圖片資料夾", - "Image folder": "圖片資料夾", - "Include images in subfolders as well": "包含子資料夾中的圖片", - "Include Subfolders": "包含子資料夾", - "Init word": "初始化標記文字", - "Input folder": "輸入資料夾", - "Install Location": "安裝位置", - "Installation": "安裝", - "Instance prompt": "實例 (Instance) 提示詞", - "Keep n tokens": "保留 N 個提示詞", - "Launching the GUI on Linux and macOS": "在 Linux/macOS 上啟動 GUI", - "Launching the GUI on Windows": "在 Windows 上啟動 GUI", - "Learning rate": "學習率", - "adafactor": "自適應學習 (adafactor)", - "linear": "線性 (linear)", - "Linux and macOS Upgrade": "Linux/macOS 升級", - "Linux and macOS": "Linux/macOS", - "Linux Pre-requirements": "Linux Pre-requirements", - "Load": "載入", - "Loading...": "載入中...", - "Local docker build": "Docker 建構", - "Logging folder": "記錄資料夾", - "LoRA model \"A\"": "LoRA 模型 \"A\"", - "LoRA model \"B\"": "LoRA 模型 \"B\"", - "LoRA model \"C\"": "LoRA 模型 \"C\"", - "LoRA model \"D\"": "LoRA 模型 \"D\"", - "LoRA model": "LoRA 模型", - "LoRA network weights": "LoRA 網路權重", - "LoRA": "LoRA", - "LR number of cycles": "學習率週期數", - "LR power": "學習率乘冪", - "LR scheduler extra arguments": "學習率調度器額外參數", - "LR Scheduler": "學習率調度器", - "LR warmup (% of steps)": "學習率預熱 (% 的步數)", - "LyCORIS model": "LyCORIS 模型", - "Macos is not great at the moment.": "目前 MacOS 支援並不是很好。", - "Manual Captioning": "手動標記文字", - "Manual installation": "手動安裝", - "Max bucket resolution": "最大資料儲存桶解析度", - "Max length": "最大長度", - "Max num workers for DataLoader": "資料工作載入的最大工作數量", - "Max resolution": "最大解析度", - "Max Timestep": "最大時序步數", - "Max Token Length": "最大標記長度", - "Max train epoch": "最大訓練週期 (Epoch) 數", - "Max train steps": "最大訓練總步數", - "Maximum bucket resolution": "最大資料儲存桶解析度", - "Maximum size in pixel a bucket can be (>= 64)": "最大資料儲存桶解析度可達 (>= 64) ", - "Memory efficient attention": "高效記憶體注意力區塊處理", - "Merge LoRA (SVD)": "合併 LoRA (SVD) ", - "Merge LoRA": "合併 LoRA", - "Merge LyCORIS": "合併 LyCORIS", - "Merge model": "合併模型", - "Merge precision": "合併精度", - "Merge ratio model A": "模型 A 合併比例", - "Merge ratio model B": "模型 B 合併比例", - "Merge ratio model C": "模型 C 合併比例", - "Merge ratio model D": "模型 D 合併比例", - "Min bucket resolution": "最小資料儲存桶解析度", - "Min length": "最小長度", - "Min SNR gamma": "最小 SNR gamma", - "Min Timestep": "最小時序步數", - "Minimum bucket resolution": "最小資料儲存桶解析度", - "Minimum size in pixel a bucket can be (>= 64)": "最小資料儲存桶解析度可達 (>= 64) ", - "Mixed precision": "混合精度", - "Mnimum difference": "最小化差異", - "Mode": "模式", - "Model A merge ratio (eg: 0.5 mean 50%)": "模型 A 合併比率 (例如:0.5 指的是 50%) ", - "Model B merge ratio (eg: 0.5 mean 50%)": "模型 B 合併比率 (例如:0.5 指的是 50%) ", - "Model C merge ratio (eg: 0.5 mean 50%)": "模型 C 合併比率 (例如:0.5 指的是 50%) ", - "Model D merge ratio (eg: 0.5 mean 50%)": "模型 D 合併比率 (例如:0.5 指的是 50%) ", - "Model output folder": "模型輸出資料夾", - "Model output name": "模型輸出資料夾", - "Model Quick Pick": "快速選擇模型", - "Module dropout": "模型捨棄", - "Network Dimension (Rank)": "網路維度 (Rank)", - "Network Dimension": "網路維度", - "Network dropout": "網路捨棄", - "No module called tkinter": "沒有名稱為 tkinter 的模組", - "No token padding": "不做提示詞填充", - "Noise offset type": "噪聲偏移類型", - "Noise offset": "噪聲偏移", - "Number of beams": "beam 的數量", - "Number of CPU threads per core": "每個 CPU 核心的線程數", - "Number of images to group together": "要一起分組的圖片數量", - "Number of updates steps to accumulate before performing a backward/update pass": "執行反向/更新傳遞之前,需要累積的更新步驟數", - "object template": "物件樣版", - "Only for SD v2 models. By scaling the loss according to the time step, the weights of global noise prediction and local noise prediction become the same, and the improvement of details may be expected.": "僅適用於 SD v2 模型。通過根據時序步數的縮放損失,整體的噪聲預測與局部的噪聲預測的權重會變得相同,以此希望能改善細節。", - "Open": "開啟", - "Optimizer extra arguments": "優化器額外參數", - "Optimizer": "優化器", - "Optional: CUDNN 8.6": "可選:CUDNN 8.6", - "Original": "原始", - "Output folder": "輸出資料夾", - "Output": "輸出", - "Overwrite existing captions in folder": "覆蓋資料夾中現有的提示詞", - "Page File Limit": "分頁檔案限制", - "PagedAdamW8bit": "PagedAdamW8bit", - "PagedLion8bit": "PagedLion8bit", - "Parameters": "參數", - "path for the checkpoint file to save...": "儲存 checkpoint 檔案路徑...", - "path for the LoRA file to save...": "儲存 LoRA 檔案路徑...", - "path for the new LoRA file to save...": "儲存新 LoRA 檔案路徑...", - "path to \"last-state\" state folder to resume from": "用來繼續訓練的 \"最後狀態\" 資料夾路徑", - "Path to the DyLoRA model to extract from": "要提取 DyLoRA 模型的路徑", - "Path to the finetuned model to extract": "要提取的微調模型的路徑", - "Path to the LoRA A model": "LoRA A 模型的路徑", - "Path to the LoRA B model": "LoRA B 模型的路徑", - "Path to the LoRA C model": "LoRA C 模型的路徑", - "Path to the LoRA D model": "LoRA D 模型的路徑", - "Path to the LoRA model to verify": "要驗證的 LoRA 模型的路徑", - "Path to the LoRA to resize": "要調整大小的 LoRA 的路徑", - "Path to the LyCORIS model": "LyCORIS 模型的路徑", - "path where to save the extracted LoRA model...": "儲存提取出的 LoRA 模型的路徑...", - "Persistent data loader": "持續資料載入器", - "polynomial": "多項式 (polynomial)", - "Postfix to add to BLIP caption": "添加到 BLIP 提示詞的後綴", - "Postfix to add to caption": "添加到提示詞的後綴", - "Pre-built Runpod template": "預先建構的 Runpod 樣版", - "Prefix to add to BLIP caption": "添加到 BLIP 提示詞的前綴", - "Prefix to add to caption": "添加到提示詞的前綴", - "Prepare training data": "準備訓練資料集", - "Print training command": "印出訓練指令", - "Prior loss weight": "正規化驗證損失權重", - "Prodigy": "Prodigy", - "Provide a SD file path IF you want to merge it with LoRA files": "如果你要合併 LoRA 檔案,請提供 SD 檔案資料夾路徑", - "Provide a SD file path that you want to merge with the LyCORIS file": "請提供你想要與 LyCORIS 檔案合併的 SD 檔案資料夾路徑", - "PyTorch 2 seems to use slightly less GPU memory than PyTorch 1.": "PyTorch 2 似乎使用的 GPU 記憶體比 PyTorch 1 略少。", - "Quick Tags": "快速標記", - "Random crop instead of center crop": "使用隨機裁切 (而非中心裁切)", - "Rank dropout": "維度捨棄", - "Rate of caption dropout": "提示詞捨棄比例", - "Recommended value of 0.5 when used": "若使用時,建議使用 0.5", - "Recommended value of 5 when used": "若使用時,建議使用 5", - "recommended values are 0.05 - 0.15": "若使用時,建議使用 0.05 - 0.15", - "Regularisation folder": "正規化資料夾", - "Regularisation images": "正規化圖片", - "Repeats": "重複", - "Replacement text": "取代文字", - "Required bitsandbytes >= 0.36.0": "需要 bitsandbytes >= 0.36.0", - "Resize LoRA": "調整 LoRA 尺寸", - "Resize model": "調整模型大小", - "Resolution (width,height)": "解析度 (寬度, 高度) ", - "Resource Contributions": "資源貢獻者", - "Resume from saved training state": "從儲存的狀態繼續訓練", - "Resume TI training": "恢復 TI 訓練", - "Runpod": "Runpod", - "Sample every n epochs": "每 N 個時期 (Epoch) 進行範本取樣", - "Sample every n steps": "每 N 個步數進行範本取樣", - "Sample image generation during training": "在訓練期間生成取樣圖片", - "Sample prompts": "取樣範本提示詞提示", - "Sample sampler": "取樣範本採樣器", - "Samples": "範本", - "Save dtype": "儲存數據類型", - "Save every N epochs": "每 N 個週期 (Epoch) 儲存", - "Save every N steps": "每 N 個步驟儲存", - "Save last N steps state": "儲存最後 N 個步驟的訓練狀態", - "Save last N steps": "儲存最後 N 個步驟", - "Save precision": "儲存精度", - "Save to": "儲存到", - "Save trained model as": "儲存訓練模型為", - "Save training state": "儲存訓練狀態", - "Save": "儲存", - "Scale v prediction loss": "縮放 v 預測損失 (v prediction loss)", - "Scale weight norms": "縮放權重標準", - "SD Model": "SD 模型", - "SDXL model": "SDXL 模型", - "Set the Max resolution to at least 1024x1024, as this is the standard resolution for SDXL. ": "最大解析度最少設定為 1024x1024,因為這是 SDXL 的標準解析度。", - "Set the Max resolution to at least 1024x1024, as this is the standard resolution for SDXL.": "最大解析度最少設定為 1024x1024,因為這是 SDXL 的標準解析度。", - "Setup": "設定", - "SGDNesterov": "SGDNesterov", - "SGDNesterov8bit": "SGDNesterov8bit", - "Shuffle caption": "打亂提示詞", - "Source LoRA": "來源 LoRA", - "Source model type": "來源模型類型", - "Source model": "來源模型", - "Sparsity": "稀疏性", - "Stable Diffusion base model": "穩定擴散基礎模型", - "Stable Diffusion original model: ckpt or safetensors file": "穩定擴散原始模型:ckpt 或 safetensors 檔案", - "Start tensorboard": "啟動 TensorBoard", - "Start training": "開始訓練", - "Starting GUI Service": "啟動 GUI 服務", - "Stop tensorboard": "停止 TensorBoard", - "Stop text encoder training": "停止文字編碼器訓練", - "Stop training": "停止訓練", - "style template": "風格樣版", - "sv_fro": "sv_fro", - "Target model folder": "目標模型資料夾", - "Target model name": "目標模型名稱", - "Target model precision": "目標模型精度", - "Target model type": "目標模型類型", - "Template": "樣版", - "Text Encoder learning rate": "文字編碼器學習率", - "The fine-tuning can be done with 24GB GPU memory with the batch size of 1.": "微調可以再使用 1 個批次大小的情況下,在 24GB GPU 記憶體的狀態下完成。", - "The GUI allows you to set the training parameters and generate and run the required CLI commands to train the model.": "此 GUI 允許你設定訓練參數,並產生執行模型訓練所需要的 CLI 指令。", - "This guide is a resource compilation to facilitate the development of robust LoRA models.": "該指南是一個資源彙整,以促進強大LoRA模型的開發。", - "This section provide Dreambooth tools to help setup your dataset…": "這些選擇幫助設置自己的資料集", - "This section provide LoRA tools to help setup your dataset…": "本節提供 LoRA 工具以幫助您設置資料集...", - "This section provide Various Finetuning guides and information…": "本節提供各種微調指南和訊息", - "This utility allows quick captioning and tagging of images.": "此工具允許快速地為圖像添加標題和標籤。", - "This utility allows you to create simple caption files for each image in a folder.": "此工具允許您為資料夾中的每個圖片建立簡單的標籤文件。", - "This utility can be used to convert from one stable diffusion model format to another.": "該工具可用於將一個穩定擴散模型格式轉換為另一種格式", - "This utility can extract a DyLoRA network from a finetuned model.": "該工具可以從微調模型中提取 DyLoRA 網絡。", - "This utility can extract a LoRA network from a finetuned model.": "該工具可以從微調模型中提取 LoRA 網絡。", - "This utility can extract a LyCORIS LoCon network from a finetuned model.": "工具可以從微調模型中提取 LyCORIS LoCon 網絡。", - "This utility can merge a LyCORIS model into a SD checkpoint.": "該工具可以將 LyCORIS 模型合並到 SD 模型中。", - "This utility can merge two LoRA networks together into a new LoRA.": "該工具可以將兩個 LoRA 網絡合並為一個新的 LoRA。", - "This utility can merge up to 4 LoRA together or alternatively merge up to 4 LoRA into a SD checkpoint.": "該工具可以合並多達 4 個LoRA,或者選擇性地將多達 4 個 LoRA 合並到 SD 模型中。", - "This utility can resize a LoRA.": "該工具可以調整 LoRA 的大小。", - "This utility can verify a LoRA network to make sure it is properly trained.": "該工具可以驗證 LoRA 網絡以確保其得到適當的訓練。", - "This utility uses BLIP to caption files for each image in a folder.": "此工具使用 BLIP 為資料夾中的每張圖像添加標籤。", - "This utility will create the necessary folder structure for the training images and optional regularization images needed for the kohys_ss Dreambooth/LoRA method to function correctly.": "此工具將為 kohys_ss Dreambooth/LoRA 方法正常運行所需的訓練圖片和正規化圖片(可選)建立必要的資料夾結構。", - "This utility will ensure that each concept folder in the dataset folder is used equally during the training process of the dreambooth machine learning model, regardless of the number of images in each folder. It will do this by renaming the concept folders to indicate the number of times they should be repeated during training.": "此工具將確保在訓練 dreambooth 機器學習模型的過程中,資料集資料夾中的每個概念資料夾都將被平等地使用,無論每個資料夾中有多少圖像。它將通過重命名概念資料夾來指示在訓練期間應重覆使用它們的次數。", - "This utility will group images in a folder based on their aspect ratio.": "此工具將根據它們的縱橫比將文件夾中的圖像分組。", - "This utility will use GIT to caption files for each images in a folder.": "此工具使用 GIT 為資料夾中的每張圖像添加標籤。", - "This utility will use WD14 to caption files for each images in a folder.": "此工具使用 WD14 為資料夾中的每張圖像添加標籤。", - "Tips for SDXL training": "SDXL 訓練提示", - "Token string": "標記符號", - "Train a custom model using kohya finetune python code": "使用 kohya finetune Python 程式訓練自定義模型", - "Train a custom model using kohya train network LoRA python code…": "使用 kohya LoRA Python 程式訓練自定義模型", - "Train batch size": "訓練批次大小", - "Train Network": "訓練網絡", - "Train text encoder": "訓練文字編碼器", - "Train U-Net only.": "僅訓練 U-Net", - "Training config folder": "訓練設定資料夾", - "Training Image folder": "訓練圖片資料夾", - "Training images": "訓練圖片", - "Training steps per concept per epoch": "每個週期每個概念的訓練步驟", - "Training": "訓練", - "Troubleshooting": "故障排除", - "Tutorials": "教學", - "Unet learning rate": "UNet 學習率", - "UNet linear projection": "UNet 線性投影", - "Upgrading": "升级", - "Use --cache_text_encoder_outputs option and caching latents.": "使用 --cache_text_encoder_outputs 選項來暫存潛空間。", - "Use Adafactor optimizer. RMSprop 8bit or Adagrad 8bit may work. AdamW 8bit doesn’t seem to work.": "使用 Adafactor 優化器。 RMSprop 8bit 或 Adagrad 8bit 可能有效。 AdamW 8bit 好像無法運作。", - "Use beam search": "使用 beam 搜尋", - "Use gradient checkpointing.": "使用梯度檢查點。", - "Use latent files": "使用潛空間檔案", - "Use sparse biais": "使用使用稀疏偏差", - "Users can obtain and/or generate an api key in the their user settings on the website: https://wandb.ai/login": "使用者可以在以下網站的用戶設定中取得,或產生 API 金鑰:https://wandb.ai/login", - "V Pred like loss": "V 預測損失", - "Values greater than 0 will make the model more img2img focussed. 0 = image only": "大於 0 的數值會使模型更加聚焦在 img2img 上。0 表示僅關注於圖像生成", - "Values lower than 1000 will make the model more img2img focussed. 1000 = noise only": "小於 1000 的數值會使模型更加聚焦在 img2img 上。1000 表示僅使用噪聲生成圖片", - "Vectors": "向量", - "Verbose": "詳細輸出", - "WANDB API Key": "WANDB API 金鑰", - "WANDB Logging": "WANDB 紀錄", - "WARNING! The use of this utility on the wrong folder can lead to unexpected folder renaming!!!": "警告!在錯誤的資料夾使用此工具,可能會意外導致資料夾被重新命名!!!", - "WD14 Captioning": "WD14 提詞", - "Windows Upgrade": "Windows 升级", - "Train a custom model using kohya dreambooth python code…": "使用 kohya dreambooth Python 程式訓練自定義模型", - "Training comment": "訓練註解", - "Train a TI using kohya textual inversion python code…": "使用 kohya textual inversion Python 程式訓練 TI 模型", - "Train a custom model using kohya finetune python code…": "使用 kohya finetune Python 程式訓練自定義模型" +{ + "WARNING! The use of this utility on the wrong folder can lead to unexpected folder renaming!!!": "警告!在錯誤的資料夾上使用此工具可能導致意外的資料夾重新命名!!!", + "(Experimental, Optional) Since the latent is close to a normal distribution, it may be a good idea to specify a value around 1/10 the noise offset.": " (選填,實驗性功能) 由於潛空間接近常態分布,或許指定一個噪聲偏移約 1/10 的數值是個不錯的作法。", + "(Name of the model to output)": "(要輸出的模型名稱)", + "(Optional) Add training comment to be included in metadata": "(選填) 在訓練的後設資料 (metadata) 加入註解。", + "(Optional) Enforce number of epoch": " (選填) 強制指定一個週期 (Epoch) 數量", + "(Optional) Enforce number of steps": " (選填) 強制指定一個總步數數量", + "(Optional) Extension for caption files. default: .caption": " (選填) 標記檔案的副檔名。預設:.caption", + "(Optional) For Cosine with restart and polynomial only": " (選填) 只適用於餘弦函數並使用重啟 (cosine_with_restart) 和多項式 (polynomial)", + "(Optional) Override number of epoch. Default: 8": " (選填) 覆蓋週期 (Epoch) 數量。預設:8", + "(Optional) Save only the specified number of models (old models will be deleted)": " (選填) 僅儲存指定數量的模型 (舊有模型將被刪除) ", + "(Optional) Save only the specified number of states (old models will be deleted)": " (選填) 僅儲存指定數量的訓練資料 (舊有訓練資料將被刪除) ", + "(Optional) Separate `undesired_tags` with comma `(,)` if you want to remove multiple tags, e.g. `1girl,solo,smile`.": " (選填) 如果要移除多個標籤,請使用逗號 `(,)` 分隔不需要的標籤,例如:`1girl,solo,smile`。", + "(Optional) The model is saved every specified steps": " (選填) 模型會在指定的間隔步數後儲存", + "(Optional) Use to provide additional parameters not handled by the GUI. Eg: --some_parameters \"value\"": " (選填) 用於提供 GUI 未提供的額外參數。例如:--some_parameters \"value\"", + "(Optional) eg: \"milestones=[1,10,30,50]\" \"gamma=0.1\"": " (選填) 例如: \"milestones=[1,10,30,50]\" \"gamma=0.1\"", + "(Optional) eg: 0,0,0,0,0,0,1,1,1,1,1,1": " (選填) 例如:0,0,0,0,0,0,1,1,1,1,1,1", + "(Optional) eg: 0.1": " (選填) 例如:0.1", + "(Optional) eg: 0.5": " (選填) 例如:0.5", + "(Optional) eg: 2,2,2,2,4,4,4,4,6,6,6,6,8,6,6,6,6,4,4,4,4,2,2,2,2": " (選填) 例如:2,2,2,2,4,4,4,4,6,6,6,6,8,6,6,6,6,4,4,4,4,2,2,2,2", + "(Optional) eg: relative_step=True scale_parameter=True warmup_init=True": " (選填) 例如:relative_step=True scale_parameter=True warmup_init=True", + "(Optional) eg:1234": " (選填) 例如:1234", + "(Optional) model id for GIT in Hugging Face": " (選填) Hugging Face 中 GIT 的模型 ID", + "(Optional)": "(選填)", + "< Prev": "< 上一個", + "A two-step approach utilizing tensor decomposition and fine-tuning to accelerate convolution layers in large neural networks, resulting in significant CPU speedups with minor accuracy drops.": "一種利用張量分解和微調的兩步方法,以加速大型神經網路中的卷積層,從而實現顯著的 CPU 加速和輕微的精度下降。", + "Adaptive noise scale": "自適應噪聲比例", + "Additional parameters": "額外參數", + "Adjust `general_threshold` for pruning tags (less tags, less flexible)": "調整一般閾值以修剪標籤 (標籤越少,越不靈活)", + "Adjusts the scale of the rank dropout to maintain the average dropout rate, ensuring more consistent regularization across different layers.": "調整維度 (Rank) 丟棄比例的比例,以保持平均丟棄率,確保在不同層之間更一致的正規化。", + "Advanced Configuration": "進階設定", + "Advanced options": "進階選項", + "Advanced parameters": "進階參數", + "Advanced": "進階", + "Appebnd TAGs": "附加標籤", + "Autosave": "自動儲存", + "Automates the processing of noise, allowing for faster model fitting, as well as balancing out color issues": "自動處理噪聲,可以更快地擬合模型,同時平衡顏色問題", + "Automatically determine the dim(rank) from the weight file.": "從權重檔案自動取用維度 DIM(Rank)。", + "BLIP Captioning": "BLIP 標記", + "Balance dataset": "平衡資料集", + "Basic Captioning": "基本標記", + "Batch size": "批次大小", + "Block LR (SDXL)": "區塊學習率", + "Block alphas": "區塊 Alphas", + "Block dims": "區塊維度", + "Blocks LR zero threshold": "區塊 LR 零閾值", + "Blocks": "區塊", + "Bucket resolution steps need to be greater than 0": "資料儲存桶解析度步數需要大於 0", + "Bucket resolution steps": "分桶解析度間隔", + "Cache latents to disk": "暫存潛空間資料到硬碟", + "Cache latents": "暫存潛空間資料", + "Cache text encoder outputs": "暫存文本編碼器輸出", + "Cache the outputs of the text encoders. This option is useful to reduce the GPU memory usage. This option cannot be used with options for shuffling or dropping the captions.": "暫存文本編碼器的輸出。此選項有助於減少 GPU 記憶體的使用。此選項不能與打亂或丟棄提示詞 (Shuffle/Dropout caption) 的選項一起使用。", + "Caption Extension": "標記檔案副檔名", + "Caption Separator": "標記文字分隔符號", + "Caption file extension (e.g., .txt)": "標記文字檔案副檔名 (例如:.txt)", + "Caption file extension": "標記檔案副檔名", + "Caption images": "標記圖片", + "Caption metadata filename": "標記文字後設資料檔案名稱", + "Caption text": "標記文字", + "Captioning": "標記文字", + "Captions": "標記文字", + "Character threshold": "角色閾值", + "Clamp Quantile": "夾取分位數 (Clamp Quantile)", + "Class prompt missing...": "缺少類別提示詞...", + "Class prompt": "類別提示詞", + "Clip skip": "Clip skip", + "Color augmentation": "色彩增強 (Color augmentation)", + "Configuration": "設定", + "Comma separated list of tags": "逗號分隔的標籤列表", + "Constrain OFT": "限制 OFT", + "Controls whether both input and output dimensions of the layer's weights are decomposed into smaller matrices for reparameterization.": "控制層權重的輸入和輸出維度是否被分解為更小的矩陣以進行重新參數化。", + "Conv Dimension (Rank)": "卷積維度 (Rank)", + "Conv Dimension": "卷積維度", + "Conv alphas": "卷積 Alphas", + "Conv dims": "卷積維度 (dims)", + "Conv quantile": "卷積分位數 (Conv quantile)", + "Conv ratio": "卷積比率 (Conv ratio)", + "Conv threshold": "卷積閾值 (Conv threshold)", + "Conv": "卷積", + "Convert to LCM": "轉換為 LCM", + "Convert model": "轉換模型", + "Convolution Alpha": "卷積 Alpha", + "Convolution Rank (Dimension)": "卷積維度 (Rank)", + "Copy info to Folders Tab": "複製資訊到資料夾區塊", + "CrossAttention": "交叉注意力", + "DANGER!!! -- Insecure folder renaming -- DANGER!!!": "危險!!! -- 不安全的資料夾重新命名 -- 危險!!!", + "DIM from weights": "從權重讀取 DIM", + "Dataset Preparation": "資料集準備", + "Dataset folder (folder containing the concepts folders to balance...)": "資料集資料夾 (含有要平衡的概念資料夾的資料夾路徑...)", + "Dataset repeats": "資料集重複數", + "Debiased Estimation loss": "偏差估算損失 (Debiased Estimation loss)", + "Debug while tagging, it will print your image file with general tags and character tags.": "標記時進行調試,它將打印您的圖片檔案與一般標籤和角色標籤。", + "Desired LoRA rank": "所需的 LoRA 維度 (Rank)", + "Destination training directory (where formatted training and regularisation folders will be placed)": "訓練的目標資料夾 (格式化的訓練和正規化資料夾將被放置的資料夾)", + "Device": "裝置", + "Disable CP decomposition": "禁用 CP 分解 (CP decomposition)", + "Disable the half-precision (mixed-precision) VAE. VAE for SDXL seems to produce NaNs in some cases. This option is useful to avoid the NaNs.": "禁用半精度 (混合精度) VAE。對於 SDXL,VAE 在某些情況下似乎會產生 NaN。此選項有助於避免 NaN。", + "Do not copy other files in the input folder to the output folder": "不複製輸入資料夾中的其他檔案到輸出資料夾", + "Do not copy other files": "不複製其他檔案", + "Don't upscale bucket resolution": "不要放大分桶解析度", + "Down LR weights": "Down LR 權重", + "Dreambooth/LoRA Dataset balancing": "Dreambooth/LoRA 資料集平衡", + "Dreambooth/LoRA Folder preparation": "Dreambooth/LoRA 資料夾準備", + "Dropout caption every n epochs": "在每 N 個週期 (Epoch) 丟棄標記", + "DyLoRA Unit / Block size": "DyLoRA 單元 / 區塊大小", + "DyLoRA model (path to the DyLoRA model to extract from)": "DyLoRA 模型 (要從中提取的 DyLoRA 模型的檔案路徑)", + "Dynamic method": "動態方法", + "Dynamic parameters": "動態參數", + "Efficiently decompose tensor shapes, resulting in a sequence of convolution layers with varying dimensions and Hadamard product implementation through multiplication of two distinct tensors.": "高效地分解張量形狀,從而產生一系列具有不同維度的卷積層,並通過兩個不同張量的乘法實現哈達瑪乘積。", + "Eg: asd": "例如:asd", + "Eg: person": "例如:person", + "Enable buckets": "啟用資料儲存桶", + "Enable multires noise (recommended values are 6-10)": "啟用多解析度噪聲 (建議使用 6-10)", + "Enter one sample prompt per line to generate multiple samples per cycle. Optional specifiers include: --w (width), --h (height), --d (seed), --l (cfg scale), --s (sampler steps) and --n (negative prompt). To modify sample prompts during training, edit the prompt.txt file in the samples directory.": "每行輸入一個提示詞來生成每個訓練週期的輸出範本。可以選擇指定的參數,包括:--w (寬度) ,--h (高度) ,--d (種子) ,--l (CFG 比例) ,--s (採樣器步驟) 和 --n (負面提示詞) 。如果要在訓練週期中修改提示詞,請修改範本目錄中的 prompt.txt 檔案。", + "Epoch": "週期 (Epoch)", + "Error": "錯誤", + "Extend LoRA to Conv2d 3x3 and specify the dim (rank) of each block. Specify 25 numbers.": "將 LoRA 擴展到 Conv2d 3x3,並指定每個區塊的維度 (Rank)。指定 25 個數字。", + "Extension for caption file (e.g., .caption, .txt)": "標記檔案的副檔名(例如: .caption, .txt)", + "Extract DyLoRA": "提取 DyLoRA", + "Extract LCM": "提取 LCM", + "Extract LoRA model": "提取 LoRA 模型", + "Extract LoRA": "提取 LoRA", + "Extract LyCORIS LoCon": "提取 LyCORIS LoCon", + "Find text": "尋找文字", + "Finetuned model (path to the finetuned model to extract)": "微調模型 (Finetuned model)", + "Flip augmentation": "翻轉增強 (Flip augmentation)", + "Folders": "資料夾", + "Force model re-download": "強制重新下載模型", + "Full bf16 training (experimental)": "完整使用 bf16 訓練 (實驗性功能)", + "Full bf16": "完整使用 bf16", + "Full fp16 training (experimental)": "完整使用 fp16 訓練 (實驗性功能)", + "GIT Captioning": "GIT 標記文字", + "GPU IDs": "GPU ID", + "General threshold": "一般閾值", + "Generate Captions": "生成標記文字", + "Generate caption files for the grouped images based on their folder name": "根據資料夾名稱為分組的圖片生成標記文字檔案", + "Generate caption metadata": "生成標記文字後設資料", + "Generate image buckets metadata": "生成圖像分桶後設資料", + "Go >": "前往 >", + "Goto page": "前往頁面", + "Gradient accumulate steps": "梯度累加步數 (Gradient accumulate steps)", + "Gradient checkpointing": "梯度檢查點 (Gradient checkpointing)", + "Group Images": "分組圖片", + "Group images": "分組圖片", + "Group size": "分組大小", + "Guides": "指南", + "If the weight is not more than this value, the LoRA module is not created. The default is 0.": "如果權重不超過此值,則不會創建 LoRA 模組。預設為 0。", + "If unchecked, tensorboard will be used as the default for logging.": "如果不勾選,Tensorboard 將會使用預設的紀錄方式。", + "Ignore Imported Tags Above Word Count": "忽略上面單詞計數的匯入標籤", + "Image folder (containing training images subfolders)": "圖片資料夾 (包含訓練圖片與子資料夾)", + "Image folder (containing training images)": "圖片資料夾 (含有訓練圖片)", + "Image folder is missing...": "圖片資料夾遺失...", + "Image folder to caption (containing the images to caption)": "要加入標記的圖片資料夾", + "Import": "匯入", + "Include Subfolders": "包含子資料夾", + "Include images in subfolders as well": "也包含子資料夾中的圖片", + "Input captions": "輸入標記文字", + "Input folder (containing the images to group)": "輸入資料夾 (含有要分組的圖片的資料夾路徑)", + "Input folder is missing...": "輸入資料夾遺失...", + "Instance prompt": "實例提示詞", + "Invalid base model file": "無效的基礎模型檔案", + "Invalid model A file": "無效的模型 A 檔案", + "Invalid model file": "無效的模型檔案", + "Is a normal probability dropout at the neuron level. In the case of LoRA, it is applied to the output of down. Recommended range 0.1 to 0.5": "是神經元級的正常概率捨棄。在 LoRA 的情況下,它被應用於 Down Sampler 的輸出。建議範圍 0.1 到 0.5", + "Keep n tokens": "保留 N 個提示詞", + "LR Scheduler": "學習率調度器 (LR Scheduler)", + "LR number of cycles": "學習率重啟週期數 (LR number of cycles)", + "LR power": "學習率乘冪 (LR power)", + "LR scheduler extra arguments": "學習率調度器額外參數", + "LR warmup (% of total steps)": "學習率預熱 (LR warmup, 總步數的 %)", + "Latent metadata filename": "潛空間後設資料檔案名稱", + "Learning rate TE": "文本編碼器學習率", + "Learning rate TE1": "文本編碼器 1 學習率", + "Learning rate TE2": "文本編碼器 2 學習率", + "Learning rate Unet": "U-Net 學習率", + "Learning rate": "學習率", + "Limits the norm of the oft_blocks, ensuring that their magnitude does not exceed a specified threshold, thus controlling the extent of the transformation applied.": "限制 oft_blocks 的規範,確保它們的大小不超過指定的閾值,從而控制應用的轉換程度。", + "Linear quantile": "線性分位數 (Linear quantile)", + "Linear ratio": "線性比率 (Linear ratio)", + "Linear threshold": "線性閾值 (Linear threshold)", + "LoKr decompose both": "LoKr 同時分解", + "LoKr factor": "LoKr 因子", + "LoRA model \"A\" (path to the LoRA A model)": "LoRA 模型 \"A\" (LoRA A 模型的檔案路徑)", + "LoRA model \"B\" (path to the LoRA B model)": "LoRA 模型 \"B\" (LoRA B 模型的檔案路徑)", + "LoRA model \"C\" (path to the LoRA C model)": "LoRA 模型 \"C\" (LoRA C 模型的檔案路徑)", + "LoRA model \"D\" (path to the LoRA D model)": "LoRA 模型 \"D\" (LoRA D 模型的檔案路徑)", + "LoRA model (path to the LoRA model to verify)": "LoRA 模型 (要驗證的 LoRA 模型的檔案路徑)", + "LoRA model types": "LoRA 模型類型", + "LoRA network weights": "LoRA 網路權重", + "Load": "載入", + "Load Stable Diffusion base model to": "載入穩定擴散基礎模型到", + "Load finetuned model to": "載入微調模型到", + "Load precision": "讀取精度", + "Load/Save Config file": "讀取/儲存設定檔案", + "Logging folder (Optional. to enable logging and output Tensorboard log)": "紀錄資料夾(選填,啟用紀錄和輸出 Tensorboard 紀錄)", + "LyCORIS model (path to the LyCORIS model)": "LyCORIS 模型 (LyCORIS 模型的檔案路徑)", + "Manual Captioning": "手動標記文字", + "Max Norm Regularization is a technique to stabilize network training by limiting the norm of network weights. It may be effective in suppressing overfitting of LoRA and improving stability when used with other LoRAs. See PR #545 on kohya_ss/sd_scripts repo for details. Recommended setting: 1. Higher is weaker, lower is stronger.": "最大規範正規化是一種穩定網路訓練的技術,通過限制網路權重的規範來實現。當與其他 LoRA 一起使用時,它可能會有效地抑制 LoRA 的過度擬合並提高穩定性。詳細資料請見 kohya_ss/sd_scripts Github 上的 PR#545。建議設置:1.0 越高越弱,越低越強。", + "Max Timestep": "最大時序步數", + "Max Token Length": "最大標記數量", + "Max bucket resolution": "最大資料儲存桶解析度", + "Max dataloader workers": "最大資料加載器工作數", + "Max grad norm": "最大梯度規範 (Max grad norm)", + "Max length": "最大長度", + "Max num workers for DataLoader": "資料工作載入的最大工作數量", + "Max resolution": "最大解析度", + "Max train epoch": "最大訓練週期 (Epoch) 數", + "Max train steps": "最大訓練總步數", + "Maximum bucket resolution": "最大資料儲存桶解析度", + "Maximum size in pixel a bucket can be (>= 64)": "最大資料儲存桶解析度可達 (>= 64) ", + "Memory efficient attention": "高效記憶體注意力區塊處理 (Memory efficient attention)", + "Merge LoRA (SVD)": "合併 LoRA (SVD)", + "Merge LoRA": "合併 LoRA", + "Merge LyCORIS": "合併 LyCORIS", + "Merge model": "合併模型", + "Merge precision": "合併精度", + "Merge ratio model A": "合併比例模型 A", + "Merge ratio model B": "合併比例模型 B", + "Merge ratio model C": "合併比例模型 C", + "Merge ratio model D": "合併比例模型 D", + "Mid LR weights": "Mid LR 權重", + "Min SNR gamma": "Min SNR gamma", + "Min Timestep": "最小時序步數", + "Min bucket resolution": "最小資料儲存桶解析度", + "Min length": "最小長度", + "Minimum bucket resolution": "最小資料儲存桶解析度", + "Minimum difference": "最小化差異 (Minimum difference)", + "Minimum size in pixel a bucket can be (>= 64)": "最小資料儲存桶解析度可達 (>= 64) ", + "Mixed precision": "混合精度", + "Mode": "模式", + "Model A merge ratio (eg: 0.5 mean 50%)": "模型 A 合併比例 (例如:0.5 表示 50%)", + "Model B merge ratio (eg: 0.5 mean 50%)": "模型 B 合併比例 (例如:0.5 表示 50%)", + "Model C merge ratio (eg: 0.5 mean 50%)": "模型 C 合併比例 (例如:0.5 表示 50%)", + "Model D merge ratio (eg: 0.5 mean 50%)": "模型 D 合併比例 (例如:0.5 表示 50%)", + "Model type": "模型類型", + "Model": "模型", + "Module dropout": "模型捨棄", + "Multi GPU": "多個 GPU", + "Multires noise iterations": "多解析度噪聲迭代", + "Name of the new LCM model": "新 LCM 模型的名稱", + "Network Alpha": "網路 Alpha", + "Network Dimension (Rank)": "網路維度 (Rank)", + "Network Rank (Dimension)": "網路維度 (Rank)", + "Network Dimension": "網路維度", + "Network dropout": "網路捨棄", + "New Conv Rank": "新卷積維度 (Conv Rank)", + "New Rank": "新維度 (Network Rank)", + "Next >": "下一個 >", + "No half VAE": "不使用半精度 VAE", + "No token padding": "不做提示詞填充 (No token padding)", + "No, get me out of here": "不,讓我離開這裡", + "Noise offset need to be a value between 0 and 1": "噪聲偏移需要是 0 到 1 之間的數值", + "Noise offset type": "噪聲偏移類型", + "Noise offset": "噪聲偏移", + "Number of CPU threads per core": "每個 CPU 核心的線程數", + "Number of beams": "beam 的數量", + "Number of images to group together": "要一起分組的圖片數量", + "Number of machines": "機器數量", + "Number of processes": "進程數量", + "Number of updates steps to accumulate before performing a backward/update pass": "執行反向/更新傳遞之前,需要累積的更新步驟數", + "Number of workers": "Worker 數量", + "Only for SD v2 models. By scaling the loss according to the time step, the weights of global noise prediction and local noise prediction become the same, and the improvement of details may be expected.": "僅適用於 SD v2 模型。通過根據時序步數的縮放損失,整體的噪聲預測與局部的噪聲預測的權重會變得相同,以此希望能改善細節。", + "Options": "選項", + "Optimizer extra arguments": "優化器額外參數", + "Optimizer": "優化器 (Optimizer)", + "Optional": "選填", + "Output \"stop text encoder training\" is not yet supported. Ignoring": "輸出「停止文本編碼器訓練」尚未支援。忽略", + "Output": "輸出", + "Output folder (where the grouped images will be stored)": "輸出資料夾 (存放分組的圖片)", + "Output folder to output trained model": "輸出資料夾以輸出訓練模型", + "Overwrite existing captions in folder": "覆蓋資料夾中現有的提示詞", + "Page Number": "頁碼", + "Parameters": "參數", + "Path to an existing LoRA network weights to resume training from": "現有 LoRA 檔案路徑,從現有 LoRA 中繼續訓練", + "Persistent data loader": "持續資料載入器 (Persistent data loader)", + "Please input learning rate values.": "請輸入學習率數值。", + "Please input valid Text Encoder learning rate (between 0 and 1)": "請輸入有效的文本編碼器學習率 (在 0 到 1 之間)", + "Please input valid Unet learning rate (between 0 and 1)": "請輸入有效的 U-Net 學習率 (在 0 到 1 之間)", + "Please provide an extension for the caption files.": "請為標記文字檔案提供一個副檔名。", + "Please provide an extension for the caption files...": "請為標記文字檔案提供一個副檔名...", + "Please provide an output folder...": "請提供一個輸出資料夾...", + "Postfix to add to BLIP caption": "要添加到 BLIP 標記文字的後綴", + "Postfix to add to GIT caption": "要加入到 GIT 標記文字的後綴", + "Postfix to add to WD14 caption": "要加入到 WD14 標記文字的後綴", + "Postfix to add to caption": "添加到提示詞的後綴", + "Prefix to add to BLIP caption": "要添加到 BLIP 標記文字的前綴", + "Prefix to add to GIT caption": "要加入到 GIT 標記文字的前綴", + "Prefix to add to WD14 caption": "要加入到 WD14 標記文字的前綴", + "Prefix to add to caption": "添加到提示詞的前綴", + "Prepare training data": "準備訓練資料", + "Presets": "預設範本", + "Print training command": "印出訓練命令", + "Prior loss weight": "正規化驗證損失權重 (Prior loss weight)", + "Provide a SD file path that you want to merge with the LyCORIS file": "提供您想要與 LyCORIS 檔案合併的 SD 檔案路徑", + "Pretrained model name or path": "預訓練模型名稱或路徑", + "Quick Tags": "快速標記", + "Random crop instead of center crop": "使用隨機裁切 (而非中心裁切)", + "Rank Dropout Scale": "維度 (Rank) 丟棄比例", + "Rank dropout": "維度捨棄", + "Rate of caption dropout": "提示詞捨棄比例", + "Recommended value of 0.5 when used": "若使用時,建議使用 0.5", + "Recommended value of 5 when used": "若使用時,建議使用 5", + "Recommended values are 0.05 - 0.15": "若使用時,建議使用 0.05 - 0.15", + "Recommended values are 0.8. For LoRAs with small datasets, 0.1-0.3": "建議使用 0.8。對於小數據集的 LoRA,建議使用 0.1-0.3", + "Recursive": "遞迴", + "Regularisation folder (Optional. containing reqularization images)": "正規化資料夾(選填,包含正規化圖片)", + "Regularisation images (Optional. directory containing the regularisation images)": "正規化圖片 (選填,含有正規化圖片的資料夾)", + "Regularisation images are used... Will double the number of steps required...": "使用了正規化圖片... 將使所需的步數加倍...", + "Repeats": "重複次數", + "Replace underscores in filenames with spaces": "將檔案名稱中的底線替換為空格", + "Replacement text": "取代文字", + "Required bitsandbytes >= 0.36.0": "需要 bitsandbytes >= 0.36.0", + "Rescaled OFT": "重新調整 OFT", + "Resize LoRA": "調整 LoRA 大小", + "Resize model": "調整模型", + "Resolution (width,height)": "解析度 (寬度, 高度) ", + "Resume from saved training state (path to \"last-state\" state folder)": "從儲存的狀態繼續訓練(最後一個儲存的狀態的資料夾路徑)", + "Resume TI training (Optional. Path to existing TI embedding file to keep training)": "繼續 TI 訓練(選填,現有 TI 嵌入檔案的路徑以繼續訓練)", + "Token string": "提示詞字串", + "Init word": "初始化單詞", + "Vectors": "向量", + "Template": "範本", + "SD Model (Optional Stable Diffusion base model)": "SD 模型 (選填,穩定擴散基礎模型)", + "SD Model (Optional. Stable Diffusion model path, if you want to merge it with LoRA files)": "SD 模型 (選填,穩定擴散模型路徑,如果您想將其與 LoRA 檔案合併)", + "SDXL model": "SDXL 模型", + "Sample every n epochs": "每 N 個週期 (Epoch) 取樣", + "Sample prompts": "取樣提示詞", + "Sample sampler": "取樣取樣器", + "Samples": "範本", + "Save dtype": "儲存 dtype", + "Save every N epochs": "每 N 個週期 (Epoch) 儲存", + "Save every N steps": "每 N 個步驟儲存", + "Save last N steps state": "儲存最後 N 個步驟的訓練狀態", + "Save last N steps": "儲存最後 N 個步驟", + "Save precision": "儲存精度", + "Save to (path for the LoRA file to save...)": "儲存到 (要儲存的 LoRA 檔案的路徑...)", + "Save to (path for the new LoRA file to save...)": "儲存到 (要儲存的新 LoRA 檔案的路徑...)", + "Save to (path for the checkpoint file to save...)": "儲存到 (要儲存的模型檔案的路徑...)", + "Save to (path for the file to save...)": "儲存到 (要儲存的檔案的路徑...)", + "Save to (path where to save the extracted LoRA model...)": "儲存到 (要儲存提取的 LoRA 模型的檔案路徑...)", + "Save trained model as": "儲存訓練模型類型為", + "Save training state": "儲存訓練狀態", + "Scale v prediction loss": "縮放 v 預測損失 (v prediction loss)", + "Scale weight norms": "縮放權重標準", + "Seed": "種子 (Seed)", + "Selects trainable layers in a network, but trains normalization layers identically across methods as they lack matrix decomposition.": "選擇網路中的可訓練層,但由於缺乏矩陣分解,因此在各種方法中都以相同方式訓練規範化層。", + "Set if we change the information going into the system (True) or the information coming out of it (False).": "設定為 True,若我們改變進入系統的資訊,否則由系統輸出則設定為 False。", + "Set to 0 to not train the Text Encoder 1": "設為 0 以不訓練文本編碼器 1", + "Set to 0 to not train the Text Encoder 2": "設為 0 以不訓練文本編碼器 2", + "Set to 0 to not train the Text Encoder": "設為 0 以不訓練文本編碼器", + "Set to 0 to not train the Unet": "設為 0 以不訓練 U-Net", + "Show frequency of tags for images.": "顯示圖片的標籤頻率。", + "Show tags frequency": "顯示標籤頻率", + "Shuffle caption": "打亂提示詞", + "Source LoRA (path to the LoRA to resize)": "來源 LoRA (要調整大小的 LoRA 的檔案路徑)", + "Source model (path to source model folder of file to convert...)": "來源模型 (要轉換的來源模型的檔案路徑...)", + "Source model type": "來源模型類型", + "Sparsity for sparse bias": "稀疏偏差的稀疏度", + "Sparsity": "稀疏度", + "Specify the alpha of each block when expanding LoRA to Conv2d 3x3. Specify 25 numbers. If omitted, the value of conv_alpha is used.": "將 LoRA 擴展到 Conv2d 3x3 時,指定每個區塊的 Alpha。指定 25 個數字。如果省略,則使用卷積 Alpha 的值。", + "Specify the alpha of each block. Specify 25 numbers as with block_dims. If omitted, the value of network_alpha is used.": "指定每個區塊的 Alpha。與區塊維度一樣,指定 25 個數字。如果省略,則使用網路 Alpha 的值。", + "Specify the different learning rates for each U-Net block. Specify 23 values separated by commas like 1e-3,1e-3 ... 1e-3": "為每個 U-Net 區塊指定不同的學習率。輸入 23 個以逗號分隔的數值,例如:1e-3,1e-3 ... 1e-3", + "Specify the dim (rank) of each block. Specify 25 numbers.": "指定每個區塊的維度 (Rank)。指定 25 個數字。", + "Specify the learning rate weight of the down blocks of U-Net.": "指定 U-Net 下區塊的學習率權重。", + "Specify the learning rate weight of the mid block of U-Net.": "指定 U-Net 中區塊的學習率權重。", + "Specify the learning rate weight of the up blocks of U-Net. The same as down_lr_weight.": "指定 U-Net 上區塊的學習率權重。與 down_lr_weight 相同。", + "Stable Diffusion base model (original model: ckpt or safetensors file)": "穩定擴散基礎模型 (basemodel: ckpt 或 safetensors 檔案)", + "Stable Diffusion model to convert to LCM": "要轉換為 LCM 的穩定擴散模型", + "Start training": "開始訓練", + "Start tensorboard": "開始 Tensorboard", + "Stop text encoder training (% of total steps)": "停止文本編碼器訓練(總步數的 %)", + "Stop training": "停止訓練", + "Stop tensorboard": "停止 Tensorboard", + "Strength of the LCM": "LCM 的強度", + "Tag subfolders images as well": "標記子資料夾中的圖片", + "Tags": "標籤", + "Target model folder (path to target model folder of file name to create...)": "目標模型 (要創建的目標模型的檔案路徑...)", + "Target model name": "目標模型名稱", + "Target model type": "目標模型類型", + "Target model precision": "目標模型精度", + "Enable for Hugging Face's stabilityai models": "啟用 Hugging Face 的 stabilityai 模型", + "UNet linear projection": "U-Net 線性投影", + "Tensorboard is already running. Terminating existing process before starting new one...": "Tensorboard 已經在運行。在啟動新進程之前終止現有進程...", + "Text Encoder learning rate": "文本編碼器學習率", + "The higher the value, the larger the file. Recommended starting value: 0.75": "數值越高,檔案越大。建議的起始數值:0.75", + "The higher the value, the smaller the file. Recommended starting value: 0.65": "數值越高,檔案越小。建議的起始數值:0.65", + "The higher the value, the smaller the file. Recommended starting value: 0.75": "數值越高,檔案越小。建議的起始數值:0.75", + "The provided DyLoRA model is not a file": "提供的 DyLoRA 模型不是檔案", + "The provided base model is not a file": "提供的基礎模型不是檔案", + "The provided finetuned model is not a file": "提供的微調模型不是檔案", + "The provided model A is not a file": "提供的模型 A 不是檔案", + "The provided model B is not a file": "提供的模型 B 不是檔案", + "The provided model C is not a file": "提供的模型 C 不是檔案", + "The provided model D is not a file": "提供的模型 D 不是檔案", + "The provided model is not a file": "提供的模型不是檔案", + "This option appends the tags to the existing tags, instead of replacing them.": "此選項將標籤附加到現有標籤,而不是替換它們。", + "This section provide Various Finetuning guides and information...": "此部分提供各種微調指南和資訊...", + "This section provide various LoRA tools...": "此部分提供各種 LoRA 工具...", + "This section provide Various LoRA guides and information...": "此部分提供各種 LoRA 指南和資訊...", + "This section provide Dreambooth tools to help setup your dataset...": "此部分提供 Dreambooth 工具,以幫助設置您的資料集...", + "This utility allows quick captioning and tagging of images.": "此工具允許快速標記圖片的標記文字和標籤。", + "This utility allows you to create simple caption files for each image in a folder.": "此工具允許您為資料夾中的每個圖片建立簡單的標籤文件。", + "This utility can extract a DyLoRA network from a finetuned model.": "此工具可以從一個微調模型中提取 DyLoRA 網路。", + "This utility can extract a LoRA network from a finetuned model.": "此工具可以從一個微調模型中提取 LoRA 網路。", + "This utility can extract a LyCORIS LoCon network from a finetuned model.": "此工具可以從一個微調模型中提取 LyCORIS LoCon 網路。", + "This utility can merge a LyCORIS model into a SD checkpoint.": "此工具可以將 LyCORIS 模型合併到一個 SD 模型。", + "This utility can merge two LoRA networks together into a new LoRA.": "此工具可以將兩個 LoRA 網路合併成一個新的 LoRA。", + "This utility can merge up to 4 LoRA together or alternatively merge up to 4 LoRA into a SD checkpoint.": "此工具可以將最多 4 個 LoRA 合併在一起,或者將最多 4 個 LoRA 合併到一個 SD 模型。", + "This utility can resize a LoRA.": "此工具可以調整 LoRA 的大小。", + "This utility can verify a LoRA network to make sure it is properly trained.": "此工具可以驗證 LoRA 網路,以確保它已經得到正確的訓練。", + "This utility convert a model to an LCM model.": "此工具將模型轉換為 LCM 模型。", + "This utility uses BLIP to caption files for each image in a folder.": "此工具使用 BLIP 為資料夾中的每張圖像添加標籤。", + "This utility will create the necessary folder structure for the training images and optional regularization images needed for the kohys_ss Dreambooth/LoRA method to function correctly.": "此工具將為訓練圖片和 kohys_ss Dreambooth/LoRA 方法正確運行所需的正規化圖片創建必要的資料夾結構。", + "This utility will ensure that each concept folder in the dataset folder is used equally during the training process of the dreambooth machine learning model, regardless of the number of images in each folder. It will do this by renaming the concept folders to indicate the number of times they should be repeated during training.": "此工具將確保資料集資料夾中的每個概念資料夾在訓練過程中被平等使用,而不管每個資料夾中的圖片數量。它將通過重新命名概念資料夾來指示它們在訓練期間應該重複的次數。", + "This utility will group images in a folder based on their aspect ratio.": "此工具將根據圖片的長寬比將資料夾中的圖片分組。", + "This utility will use GIT to caption files for each images in a folder.": "此工具將使用 GIT 為資料夾中的每個圖片檔案標記文字。", + "This utility will use WD14 to caption files for each images in a folder.": "此工具將使用 WD14 為資料夾中的每個圖片檔案標記文字。", + "Top p": "Top p", + "Train batch size": "訓練批次大小", + "Train Norm": "訓練規範 (Norm)", + "Train a custom model using kohya dreambooth python code...": "使用 kohya Dreambooth Python 程式訓練自定義模型", + "Train a custom model using kohya train network LoRA python code...": "使用 kohya LoRA Python 程式訓練自定義模型", + "Train a custom model using kohya finetune python code...": "使用 kohya 微調 Python 程式訓練自定義模型", + "Train a TI using kohya textual inversion python code...": "使用 kohya 文本反轉 Python 程式訓練 TI", + "Train an additional scalar in front of the weight difference, use a different weight initialization strategy.": "在權重差異前訓練一個額外的標量,使用不同的權重初始化策略。", + "Train config folder (Optional. where config files will be saved)": "訓練設定資料夾(選填,設定檔案將會被儲存的資料夾)", + "Train text encoder": "訓練文本編碼器", + "Trained Model output name": "訓練模型輸出名稱", + "Training comment": "訓練註解", + "Training images (directory containing the training images)": "訓練圖片 (含有訓練圖片的資料夾)", + "Training steps per concept per epoch": "每個週期 (Epoch) 每個概念的訓練步數", + "Training": "訓練", + "U-Net and Text Encoder can be trained with fp8 (experimental)": "U-Net 與 Text Encoder 使用 fp8 訓練 (實驗性功能)", + "Undesired tags": "不需要的標籤", + "Unet learning rate": "U-Net 學習率", + "Up LR weights": "Up LR 權重", + "Use CP decomposition": "使用 CP 分解 (CP decomposition)", + "Use Scalar": "使用標量", + "Use Tucker decomposition": "使用 Tucker 分解 (Tucker decomposition)", + "Use beam search": "使用 beam 搜尋", + "Use full path": "使用完整路徑", + "Use latent files": "使用潛空間檔案", + "Use onnx": "使用 ONNX", + "Use sparse biais": "使用稀疏偏差 (sparse biais)", + "Useful if you want to train with character": "如果您想要使用角色訓練,這是有用的", + "Useful to force model re download when switching to onnx": "在切換到 ONNX 時強制重新下載模型", + "Users can obtain and/or generate an api key in the their user settings on the website: https://wandb.ai/login": "使用者可以在以下網站的用戶設定中取得,或產生 API 金鑰:https://wandb.ai/login", + "V Pred like loss": "V 預測損失 (V Pred like loss)", + "VAE (Optional. path to checkpoint of vae to replace for training)": "VAE (選填,選擇要替換訓練的 VAE checkpoint 的檔案路徑)", + "VAE batch size": "VAE 批次大小", + "Value for the dynamic method selected.": "選擇的動態方法的數值。", + "Values greater than 0 will make the model more img2img focussed. 0 = image only": "大於 0 的數值會使模型更加聚焦在 img2img 上。0 表示僅關注於圖像生成", + "Values lower than 1000 will make the model more img2img focussed. 1000 = noise only": "小於 1000 的數值會使模型更加聚焦在 img2img 上。1000 表示僅使用噪聲生成圖片", + "Verbose logging": "詳細日誌", + "Verification error": "驗證錯誤", + "Verification output": "驗證輸出", + "Verify LoRA": "驗證 LoRA", + "Verify": "驗證", + "WANDB API Key": "WANDB API 金鑰", + "WANDB Logging": "WANDB 紀錄", + "WD14 Captioning": "WD14 標記文字", + "Weighted captions": "加權標記文字 (Weighted captions)", + "Weights": "權重", + "Yes, I like danger": "是的,我喜歡危險", + "alpha for LoRA weight scaling": "LoRA 權重縮放的 alpha", + "applies an additional scaling factor to the oft_blocks, allowing for further adjustment of their impact on the model's transformations.": "對 oft_blocks 應用額外的縮放因子,從而進一步調整它們對模型轉換的影響。", + "can specify `module_dropout` to dropout each rank with specified probability. Recommended range 0.1 to 0.3": "可以指定 `module_dropout` 以指定的概率捨棄每個維度。建議範圍 0.1 到 0.3", + "can specify `rank_dropout` to dropout each rank with specified probability. Recommended range 0.1 to 0.3": "可以指定 `rank_dropout` 以指定的概率捨棄每個維度。建議範圍 0.1 到 0.3", + "e.g., \"by some artist\". Leave empty if you only want to add a prefix or postfix.": "例如: \"by some artist\"。如果您只想添加前綴或後綴,請留空。", + "e.g., \"by some artist\". Leave empty if you want to replace with nothing.": "例如: \"by some artist\"。如果您想用空值取代,請留空。", + "eg: cat": "例如:cat", + "example: 0,1": "例如:0,1", + "fp8 base training (experimental)": "使用 fp8 基礎訓練 (實驗性功能)", + "iA3 train on input": "iA3 輸入訓練", + "is SDXL": "是 SDXL", + "is v2": "是 v2", + "network dim for linear layer in fixed mode": "固定模式中線性層的網路維度", + "network dim for conv layer in fixed mode": "固定模式中卷積層的網路維度", + "only for SDXL": "僅適用於 SDXL" } diff --git a/models/.keep b/models/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/requirements.txt b/requirements.txt index a54613c26..0ddc62b1c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ huggingface-hub==0.20.1 # for loading Diffusers' SDXL invisible-watermark==0.2.0 lion-pytorch==0.0.6 -lycoris_lora==2.1.0.post3 +lycoris_lora==2.2.0.post3 # for BLIP captioning # requests==2.28.2 # timm==0.6.12 diff --git a/requirements_runpod.txt b/requirements_runpod.txt index 3b2d5bc84..3f7ebdb06 100644 --- a/requirements_runpod.txt +++ b/requirements_runpod.txt @@ -1,5 +1,5 @@ torch==2.1.2+cu118 torchvision==0.16.2+cu118 xformers==0.0.23.post1+cu118 --extra-index-url https://download.pytorch.org/whl/cu118 # no_verify leave this to specify not checking this a verification stage bitsandbytes==0.43.0 -tensorboard==2.15.2 tensorflow==2.15.0.post1 wheel +tensorboard==2.14.1 tensorflow==2.14.0 wheel tensorrt -r requirements.txt diff --git a/sd-scripts b/sd-scripts index 2d7389185..6b1520a46 160000 --- a/sd-scripts +++ b/sd-scripts @@ -1 +1 @@ -Subproject commit 2d7389185c021bc527b414563c245c5489d6328a +Subproject commit 6b1520a46b1b6ee7c33092537dc9449d1cc4f56f diff --git a/test/config/dreambooth-AdamW8bit.json b/test/config/dreambooth-AdamW8bit.json index 09865620f..1771c8a79 100644 --- a/test/config/dreambooth-AdamW8bit.json +++ b/test/config/dreambooth-AdamW8bit.json @@ -1,6 +1,11 @@ { + "LoRA_type": "Standard", + "LyCORIS_preset": "full", "adaptive_noise_scale": 0, "additional_parameters": "", + "block_alphas": "", + "block_dims": "", + "block_lr_zero_threshold": "", "bucket_no_upscale": true, "bucket_reso_steps": 64, "cache_latents": true, @@ -10,31 +15,63 @@ "caption_extension": "", "clip_skip": 2, "color_aug": false, + "constrain": 0.0, + "conv_alpha": 1, + "conv_block_alphas": "", + "conv_block_dims": "", + "conv_dim": 1, + "debiased_estimation_loss": false, + "decompose_both": false, + "dim_from_weights": false, + "down_lr_weight": "", "enable_bucket": true, "epoch": 1, + "factor": -1, "flip_aug": false, + "fp8_base": false, + "full_bf16": false, "full_fp16": false, - "gradient_accumulation_steps": 1.0, + "gpu_ids": "", + "gradient_accumulation_steps": 1, "gradient_checkpointing": false, "keep_tokens": "0", "learning_rate": 5e-05, + "log_tracker_config": "", + "log_tracker_name": "", "logging_dir": "./test/logs", + "lora_network_weights": "", "lr_scheduler": "constant", + "lr_scheduler_args": "", + "lr_scheduler_num_cycles": "", + "lr_scheduler_power": "", "lr_warmup": 0, + "max_bucket_reso": 2048, "max_data_loader_n_workers": "0", + "max_grad_norm": 1, "max_resolution": "512,512", + "max_timestep": 1000, "max_token_length": "75", "max_train_epochs": "", + "max_train_steps": "", "mem_eff_attn": false, + "mid_lr_weight": "", + "min_bucket_reso": 256, "min_snr_gamma": 0, + "min_timestep": 0, "mixed_precision": "bf16", "model_list": "runwayml/stable-diffusion-v1-5", + "module_dropout": 0, + "multi_gpu": false, "multires_noise_discount": 0, "multires_noise_iterations": 0, - "no_token_padding": false, - "noise_offset": "0.05", + "network_alpha": 1, + "network_dim": 8, + "network_dropout": 0, + "noise_offset": 0.05, "noise_offset_type": "Original", "num_cpu_threads_per_process": 2, + "num_machines": 1, + "num_processes": 1, "optimizer": "AdamW8bit", "optimizer_args": "", "output_dir": "./test/output", @@ -43,7 +80,10 @@ "pretrained_model_name_or_path": "runwayml/stable-diffusion-v1-5", "prior_loss_weight": 1.0, "random_crop": false, + "rank_dropout": 0, + "rank_dropout_scale": false, "reg_data_dir": "", + "rescaled": false, "resume": "", "sample_every_n_epochs": 0, "sample_every_n_steps": 25, @@ -57,17 +97,33 @@ "save_precision": "fp16", "save_state": false, "scale_v_pred_loss_like_noise_pred": false, + "scale_weight_norms": 0, + "sdxl": false, + "sdxl_cache_text_encoder_outputs": false, + "sdxl_no_half_vae": true, "seed": "1234", "shuffle_caption": false, "stop_text_encoder_training": 0, + "text_encoder_lr": 0.0, "train_batch_size": 4, "train_data_dir": "./test/img", + "train_norm": false, + "train_on_input": true, + "training_comment": "", + "unet_lr": 0.0, + "unit": 1, + "up_lr_weight": "", + "use_cp": false, + "use_scalar": false, + "use_tucker": false, "use_wandb": false, "v2": false, "v_parameterization": false, + "v_pred_like_loss": 0, "vae": "", "vae_batch_size": 0, "wandb_api_key": "", + "wandb_run_name": "", "weighted_captions": false, - "xformers": true + "xformers": "xformers" } \ No newline at end of file diff --git a/test/config/locon-AdamW8bit.json b/test/config/locon-AdamW8bit.json index 1c1666ef3..99f2947b5 100644 --- a/test/config/locon-AdamW8bit.json +++ b/test/config/locon-AdamW8bit.json @@ -1,5 +1,6 @@ { "LoRA_type": "Kohya LoCon", + "LyCORIS_preset": "full", "adaptive_noise_scale": 0, "additional_parameters": "", "block_alphas": "", @@ -14,10 +15,12 @@ "caption_extension": "", "clip_skip": 2, "color_aug": false, + "constrain": 0.0, "conv_alpha": 8, - "conv_alphas": "", + "conv_block_alphas": "", + "conv_block_dims": "", "conv_dim": 16, - "conv_dims": "", + "debiased_estimation_loss": false, "decompose_both": false, "dim_from_weights": false, "down_lr_weight": "", @@ -25,36 +28,50 @@ "epoch": 1, "factor": -1, "flip_aug": false, + "fp8_base": false, + "full_bf16": false, "full_fp16": false, + "gpu_ids": "", "gradient_accumulation_steps": 1, "gradient_checkpointing": false, "keep_tokens": "0", "learning_rate": 0.0005, + "log_tracker_config": "", + "log_tracker_name": "", "logging_dir": "./test/logs", "lora_network_weights": "", "lr_scheduler": "constant", + "lr_scheduler_args": "", "lr_scheduler_num_cycles": "", "lr_scheduler_power": "", "lr_warmup": 0, + "max_bucket_reso": 2048, "max_data_loader_n_workers": "0", + "max_grad_norm": 1, "max_resolution": "512,512", + "max_timestep": 1000, "max_token_length": "75", "max_train_epochs": "", + "max_train_steps": "", "mem_eff_attn": false, "mid_lr_weight": "", + "min_bucket_reso": 256, "min_snr_gamma": 0, + "min_timestep": 0, "mixed_precision": "bf16", "model_list": "runwayml/stable-diffusion-v1-5", "module_dropout": 0.1, + "multi_gpu": false, "multires_noise_discount": 0, "multires_noise_iterations": 0, "network_alpha": 8, "network_dim": 16, "network_dropout": 0.1, - "no_token_padding": false, - "noise_offset": "0.05", + "noise_offset": 0.05, "noise_offset_type": "Original", "num_cpu_threads_per_process": 2, + "num_machines": 1, + "num_processes": 1, "optimizer": "AdamW8bit", "optimizer_args": "", "output_dir": "./test/output", @@ -64,7 +81,9 @@ "prior_loss_weight": 1.0, "random_crop": false, "rank_dropout": 0.1, + "rank_dropout_scale": false, "reg_data_dir": "", + "rescaled": false, "resume": "", "sample_every_n_epochs": 0, "sample_every_n_steps": 25, @@ -79,23 +98,32 @@ "save_state": false, "scale_v_pred_loss_like_noise_pred": false, "scale_weight_norms": 1, + "sdxl": false, + "sdxl_cache_text_encoder_outputs": false, + "sdxl_no_half_vae": true, "seed": "1234", "shuffle_caption": false, "stop_text_encoder_training": 0, "text_encoder_lr": 0.0001, "train_batch_size": 4, "train_data_dir": "./test/img", + "train_norm": false, "train_on_input": false, "training_comment": "", "unet_lr": 0.0001, "unit": 1, "up_lr_weight": "", "use_cp": false, + "use_scalar": false, + "use_tucker": false, "use_wandb": false, "v2": false, "v_parameterization": false, + "v_pred_like_loss": 0, + "vae": "", "vae_batch_size": 0, "wandb_api_key": "", + "wandb_run_name": "", "weighted_captions": false, - "xformers": true + "xformers": "xformers" } \ No newline at end of file