diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d4376b0 --- /dev/null +++ b/Makefile @@ -0,0 +1,102 @@ +# Makefile for mflux Python 3.10+ project, using 3.11 as recommended Python as of Sep 2024 + +PYTHON_VERSION = 3.11 +VENV_DIR = .venv + +# Default target +.PHONY: all +all: install test + +.PHONY: expect-arm64 +expect-arm64: + # ๐Ÿ–ฅ๏ธ Checking for compatible machine + @if [ "$$(uname -m)" != "arm64" ]; then \ + echo "mflux and MLX is not compatible with older Intel Macs. This project does not support your Mac."; \ + exit 1; \ + fi + +# we "expect" uv but should not install it for the user, let user *choose* to trust a third party installer +.PHONY: expect-uv +expect-uv: + @if ! /usr/bin/which -s uv; then \ + echo "You can use classic python -m venv to setup this project, \nbut we officially support using uv for managing this project's environment.\n"; \ + echo "Please install uv to continue:\n https://github.com/astral-sh/uv?tab=readme-ov-file#installation"; \ + fi + +# assume reasonably that if user has installed uv, they trust ruff from the same team +.PHONY: ensure-ruff +ensure-ruff: + @if ! /usr/bin/which -s ruff; then \ + echo "ruff required for code linting and formatting. Using uv tool to install ruff."; \ + uv tool install ruff; \ + fi + +# Create virtual environment with uv +.PHONY: venv-init +venv-init: expect-arm64 expect-uv + # ๐Ÿ—๏ธ Creating virtual environment with recommended uv tool: + uv python install --quiet $(PYTHON_VERSION) + uv venv --python $(PYTHON_VERSION) + # โœ… "Python $(PYTHON_VERSION) Virtual environment created at $(VENV_DIR)" + +# Install dependencies +.PHONY: install +install: venv-init + # ๐Ÿ—๏ธ Installing dependencies... + uv pip install -e . + # โœ… Dependencies installed. + +# Run linters +.PHONY: lint +lint: ensure-ruff + # ๐Ÿ—๏ธ Running linters, your files will not be mutated. + # Use 'make autofix' to auto-apply fixes." + ruff check + # โœ… Linting complete." + +# Run formatter (if dev does not do so in their IDE) +.PHONY: format +format: ensure-ruff + # ๐Ÿ—๏ธ Running formatter, your files will be changed to comply to formatting configs. + ruff format + # display the summaries of diffs in repo, some of these diffs are generated by the formatter + git diff --stat + # โœ… Formatting complete. Please review your git diffs, if any. + +# Run ruff auto lint and format +# use || true to force possibly failing format/lint checks to allow rest of make helper to continue +.PHONY: autofix +autofix: ensure-ruff + # ๐Ÿ—๏ธ Running linter and formatters on files... + @(ruff format --respect-gitignore --target-version py310 || true) + # โœ… auto formatting complete + @(ruff check --fix || true) + # โœ… auto lint fixes complete, check for issues ruff cannot auto fix + +# Run tests +.PHONY: test +test: + # ๐Ÿ—๏ธ Running tests... + # mock success stub for future test suite ๐Ÿ˜œ + @true + # โœ… Tests completed + +# Clean up +.PHONY: clean +clean: + # ๐Ÿงผ Cleaning up venv. + rm -rf $(VENV_DIR) + # โœ… Cleaned up venv. Run 'make install' to re-generate. + +# Help message +.PHONY: help +help: + @echo "mflux commands:" + @echo " make all - Set up the project and run tests" + @echo " make install - Install project dev dependencies" + @echo " make lint - Run ruff python linter" + @echo " make format - Run ruff code formatter" + @echo " make autofix - Run linters auto fixes *and* style formatter" + @echo " make test - Run tests" + @echo " make clean - Remove the virtual environment" + @echo " make help - Show this help message" diff --git a/README.md b/README.md index ab7398b..ea065c7 100644 --- a/README.md +++ b/README.md @@ -23,48 +23,52 @@ like [Numpy](https://numpy.org) and [Pillow](https://pypi.org/project/pillow/) f - [x] FLUX.1-Dev ### Installation -For users, the easiest way to install MFLUX is to first create a new virtual environment to isolate dependencies: - ``` - mkdir -p mflux && cd mflux && python3 -m venv .venv && source .venv/bin/activate - ``` +For users, the easiest way to install MFLUX is to use `uv tool`: + +If you have [installed `uv`](https://github.com/astral-sh/uv?tab=readme-ov-file#installation), simply: `uv tool install mflux` to get the `mflux-generate` and related command line executables. You can skip to the usage guides below. + +
+For the classic way to create a user virtual environment: + +``` +mkdir -p mflux && cd mflux && python3 -m venv .venv && source .venv/bin/activate +``` + This creates and activates a virtual environment in the `mflux` folder. After that, install MFLUX via pip: - ``` - pip install -U mflux - ``` + +``` +pip install -U mflux +``` + +
+
For contributors (click to expand) 1. Clone the repo: - ``` + ```sh git clone git@github.com:filipstrand/mflux.git ``` -2. Navigate to the project and set up a virtual environment: - ``` - cd mflux - python3 -m venv .venv # alternative: `uv venv` - source .venv/bin/activate - ``` -3. Install the required dependencies in [development mode](https://setuptools.pypa.io/en/latest/userguide/development_mode.html) as defined in `pyproject.toml`: - ``` - pip install -e . - ``` -4. Follow format and lint checks prior to submitting Pull Requests. Install the `ruff` tool as a common resource somewhere else in your system external to this project. - - [`uv tool install ruff`](https://github.com/astral-sh/uv) OR [`pipx install ruff`](https://github.com/pypa/pipx). - - [`ruff check` and `ruff check --fix`](https://github.com/astral-sh/ruff?tab=readme-ov-file#usage) any linter warnings generated from your PR diff. - - [`ruff format`](https://github.com/astral-sh/ruff?tab=readme-ov-file#usage) any added or modified files in your PR diff. +2. `make install` and `make test` (and `make clean` for venv resets) +3. Follow format and lint checks prior to submitting Pull Requests. The recommended `make lint` and `make format` installs and uses [`ruff`](https://github.com/astral-sh/ruff). You can setup your editor/IDE to lint/format automatically, or use our provided `make` helpers: + - `make format` - formats your code + - `make lint` - shows your lint errors and warnings, but does not auto fix + - `make autofix` - formats your code **and** attempts to auto fix lint errors + - consult official [`ruff` documentation](https://docs.astral.sh/ruff/) on advanced usages +
### Generating an image Run the command `mflux-generate` by specifying a prompt and the model and some optional arguments. For example, here we use a quantized version of the `schnell` model for 2 steps: -``` +```sh mflux-generate --model schnell --prompt "Luxury food photograph" --steps 2 --seed 2 -q 8 ``` This example uses the more powerful `dev` model with 25 time steps: -``` +```sh mflux-generate --model dev --prompt "Luxury food photograph" --steps 25 --seed 2 -q 8 ``` @@ -111,8 +115,7 @@ mflux-generate --model dev --prompt "Luxury food photograph" --steps 25 --seed 2 Or, with the correct python environment active, create and run a separate script like the following: ```python -from mflux.flux.flux import Flux1 -from mflux.config.config import Config +from mflux import Flux1, Config # Load the model flux = Flux1.from_alias( @@ -140,7 +143,7 @@ For more options on how to configure MFLUX, please see [generate.py](src/mflux/g These numbers are based on the non-quantized `schnell` model, with the configuration provided in the code snippet below. To time your machine, run the following: -``` +```sh time mflux-generate \ --prompt "Luxury food photograph" \ --model schnell \ @@ -219,7 +222,7 @@ Luxury food photograph of an italian Linguine pasta alle vongole dish with lots MFLUX supports running FLUX in 4-bit or 8-bit quantized mode. Running a quantized version can greatly speed up the generation process and reduce the memory consumption by several gigabytes. [Quantized models also take up less disk space](#size-comparisons-for-quantized-models). -``` +```sh mflux-generate \ --model schnell \ --steps 2 \ @@ -252,7 +255,7 @@ The reason weights sizes are not fully cut in half is because a small number of To save a local copy of the quantized weights, run the `mflux-save` command like so: -``` +```sh mflux-save \ --path "/Users/filipstrand/Desktop/schnell_8bit" \ --model schnell \ @@ -265,7 +268,7 @@ mflux-save \ To generate a new image from the quantized model, simply provide a `--path` to where it was saved: -``` +```sh mflux-generate \ --path "/Users/filipstrand/Desktop/schnell_8bit" \ --model schnell \ @@ -290,7 +293,7 @@ In other words, you can reclaim the 34GB diskspace (per model) by deleting the f MFLUX also supports running a non-quantized model directly from a custom location. In the example below, the model is placed in `/Users/filipstrand/Desktop/schnell`: -``` +```sh mflux-generate \ --path "/Users/filipstrand/Desktop/schnell" \ --model schnell \ @@ -339,7 +342,7 @@ MFLUX support loading trained [LoRA](https://huggingface.co/docs/diffusers/en/tr The following example [The_Hound](https://huggingface.co/TheLastBen/The_Hound) LoRA from [@TheLastBen](https://github.com/TheLastBen): -``` +```sh mflux-generate --prompt "sandor clegane" --model dev --steps 20 --seed 43 -q 8 --lora-paths "sandor_clegane_single_layer.safetensors" ``` @@ -348,7 +351,7 @@ mflux-generate --prompt "sandor clegane" --model dev --steps 20 --seed 43 -q 8 - The following example is [Flux_1_Dev_LoRA_Paper-Cutout-Style](https://huggingface.co/Norod78/Flux_1_Dev_LoRA_Paper-Cutout-Style) LoRA from [@Norod78](https://huggingface.co/Norod78): -``` +```sh mflux-generate --prompt "pikachu, Paper Cutout Style" --model schnell --steps 4 --seed 43 -q 8 --lora-paths "Flux_1_Dev_LoRA_Paper-Cutout-Style.safetensors" ``` ![image](src/mflux/assets/lora2.jpg) @@ -361,7 +364,7 @@ mflux-generate --prompt "pikachu, Paper Cutout Style" --model schnell --steps 4 Multiple LoRAs can be sent in to combine the effects of the individual adapters. The following example combines both of the above LoRAs: -``` +```sh mflux-generate \ --prompt "sandor clegane in a forest, Paper Cutout Style" \ --model dev \ @@ -399,5 +402,6 @@ To report additional formats, examples or other any suggestions related to LoRA ### TODO +- [ ] Establish unit test suite - [ ] LoRA fine-tuning - [ ] Frontend support (Gradio/Streamlit/Other?) diff --git a/src/mflux/__init__.py b/src/mflux/__init__.py index e69de29..9547ab8 100644 --- a/src/mflux/__init__.py +++ b/src/mflux/__init__.py @@ -0,0 +1,8 @@ +from mflux.config.config import Config +from mflux.config.config import ConfigControlnet +from mflux.config.model_config import ModelConfig +from mflux.controlnet.flux_controlnet import Flux1Controlnet +from mflux.flux.flux import Flux1 +from mflux.post_processing.image_util import ImageUtil + +__all__ = ["Flux1", "Flux1Controlnet", "Config", "ConfigControlnet", "ModelConfig", "ImageUtil"] diff --git a/src/mflux/controlnet/transformer_controlnet.py b/src/mflux/controlnet/transformer_controlnet.py index 33bbe84..0406d8b 100644 --- a/src/mflux/controlnet/transformer_controlnet.py +++ b/src/mflux/controlnet/transformer_controlnet.py @@ -1,5 +1,3 @@ -from typing import Tuple - import mlx.core as mx from mlx import nn @@ -77,12 +75,12 @@ class TransformerControlnet(nn.Module): single_block_samples = () for block in self.single_transformer_blocks: - ctrlnet_hidden_states = block.forward( - hidden_states=ctrlnet_hidden_states, + hidden_states = block.forward( + hidden_states=hidden_states, text_embeddings=text_embeddings, rotary_embeddings=image_rotary_emb ) - single_block_samples = single_block_samples + (ctrlnet_hidden_states[:, encoder_hidden_states.shape[1] :],) + single_block_samples = single_block_samples + (hidden_states[:, encoder_hidden_states.shape[1]:],) controlnet_single_block_samples = () for single_block_sample, controlnet_block in zip(single_block_samples, self.controlnet_single_blocks): diff --git a/src/mflux/generate.py b/src/mflux/generate.py index 9bf281d..fdcf8bd 100644 --- a/src/mflux/generate.py +++ b/src/mflux/generate.py @@ -1,13 +1,7 @@ import argparse -import os -import sys import time -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -from mflux.config.model_config import ModelConfig -from mflux.config.config import Config -from mflux.flux.flux import Flux1 +from mflux import Flux1, Config, ModelConfig def main(): diff --git a/src/mflux/generate_controlnet.py b/src/mflux/generate_controlnet.py index 4c5bb1a..2e0784b 100644 --- a/src/mflux/generate_controlnet.py +++ b/src/mflux/generate_controlnet.py @@ -1,14 +1,7 @@ import argparse -import os -import sys import time -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -from mflux.config.model_config import ModelConfig -from mflux.config.config import ConfigControlnet -from mflux.controlnet.flux_controlnet import Flux1Controlnet -from mflux.post_processing.image_util import ImageUtil +from mflux import Flux1Controlnet, ConfigControlnet, ModelConfig, ImageUtil def main(): diff --git a/src/mflux/save.py b/src/mflux/save.py index e34bf8f..49409ca 100644 --- a/src/mflux/save.py +++ b/src/mflux/save.py @@ -1,11 +1,6 @@ import argparse -import os -import sys -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -from mflux.flux.flux import Flux1 -from mflux.config.model_config import ModelConfig +from mflux import Flux1, ModelConfig def main(): diff --git a/tests/test_readme_example.sh b/tests/test_readme_example.sh new file mode 100755 index 0000000..3fc6827 --- /dev/null +++ b/tests/test_readme_example.sh @@ -0,0 +1,7 @@ +mflux-generate \ + --prompt "Luxury food photograph" \ + --model schnell \ + --steps 2 \ + --seed 2 \ + --height 1024 \ + --width 1024