Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6eca44e1a7 | |||
| 53476bfca9 | |||
| 44126bd77e | |||
| 8997e88d85 | |||
| 345864eb85 | |||
| 35e538d46a | |||
| 2dc31677e1 | |||
| 1066de8c69 | |||
| 2d69bacb00 | |||
| 0974b4c606 | |||
| cf4b97b233 |
@@ -72,7 +72,6 @@ jobs:
|
|||||||
image-name:
|
image-name:
|
||||||
- diffusers-pytorch-cpu
|
- diffusers-pytorch-cpu
|
||||||
- diffusers-pytorch-cuda
|
- diffusers-pytorch-cuda
|
||||||
- diffusers-pytorch-cuda
|
|
||||||
- diffusers-pytorch-xformers-cuda
|
- diffusers-pytorch-xformers-cuda
|
||||||
- diffusers-pytorch-minimum-cuda
|
- diffusers-pytorch-minimum-cuda
|
||||||
- diffusers-doc-builder
|
- diffusers-doc-builder
|
||||||
|
|||||||
@@ -286,4 +286,3 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: pr_main_test_reports
|
name: pr_main_test_reports
|
||||||
path: reports
|
path: reports
|
||||||
|
|
||||||
|
|||||||
@@ -1,56 +1,42 @@
|
|||||||
FROM ubuntu:20.04
|
FROM python:3.10-slim
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
LABEL maintainer="Hugging Face"
|
LABEL maintainer="Hugging Face"
|
||||||
LABEL repository="diffusers"
|
LABEL repository="diffusers"
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
RUN apt-get -y update \
|
RUN apt-get -y update && apt-get install -y bash \
|
||||||
&& apt-get install -y software-properties-common \
|
build-essential \
|
||||||
&& add-apt-repository ppa:deadsnakes/ppa
|
git \
|
||||||
|
git-lfs \
|
||||||
|
curl \
|
||||||
|
ca-certificates \
|
||||||
|
libsndfile1-dev \
|
||||||
|
libgl1
|
||||||
|
|
||||||
RUN apt install -y bash \
|
ENV UV_PYTHON=/usr/local/bin/python
|
||||||
build-essential \
|
|
||||||
git \
|
|
||||||
git-lfs \
|
|
||||||
curl \
|
|
||||||
ca-certificates \
|
|
||||||
libsndfile1-dev \
|
|
||||||
python3.10 \
|
|
||||||
python3-pip \
|
|
||||||
libgl1 \
|
|
||||||
zip \
|
|
||||||
wget \
|
|
||||||
python3.10-venv && \
|
|
||||||
rm -rf /var/lib/apt/lists
|
|
||||||
|
|
||||||
# make sure to use venv
|
|
||||||
RUN python3.10 -m venv /opt/venv
|
|
||||||
ENV PATH="/opt/venv/bin:$PATH"
|
|
||||||
|
|
||||||
# pre-install the heavy dependencies (these can later be overridden by the deps from setup.py)
|
# pre-install the heavy dependencies (these can later be overridden by the deps from setup.py)
|
||||||
RUN python3.10 -m pip install --no-cache-dir --upgrade pip uv==0.1.11 && \
|
RUN pip install uv
|
||||||
python3.10 -m uv pip install --no-cache-dir \
|
RUN uv pip install --no-cache-dir \
|
||||||
torch \
|
torch \
|
||||||
torchvision \
|
torchvision \
|
||||||
torchaudio \
|
torchaudio \
|
||||||
invisible_watermark \
|
--extra-index-url https://download.pytorch.org/whl/cpu
|
||||||
--extra-index-url https://download.pytorch.org/whl/cpu && \
|
|
||||||
python3.10 -m uv pip install --no-cache-dir \
|
RUN uv pip install --no-cache-dir "git+https://github.com/huggingface/diffusers.git@main#egg=diffusers[test]"
|
||||||
accelerate \
|
|
||||||
datasets \
|
# Extra dependencies
|
||||||
hf-doc-builder \
|
RUN uv pip install --no-cache-dir \
|
||||||
huggingface-hub \
|
accelerate \
|
||||||
Jinja2 \
|
numpy==1.26.4 \
|
||||||
librosa \
|
hf_transfer \
|
||||||
numpy==1.26.4 \
|
setuptools==69.5.1 \
|
||||||
scipy \
|
bitsandbytes \
|
||||||
tensorboard \
|
torchao \
|
||||||
transformers \
|
gguf \
|
||||||
matplotlib \
|
optimum-quanto
|
||||||
setuptools==69.5.1 \
|
|
||||||
bitsandbytes \
|
RUN apt-get clean && rm -rf /var/lib/apt/lists/* && apt-get autoremove && apt-get autoclean
|
||||||
torchao \
|
|
||||||
gguf \
|
|
||||||
optimum-quanto
|
|
||||||
|
|
||||||
CMD ["/bin/bash"]
|
CMD ["/bin/bash"]
|
||||||
|
|||||||
@@ -1,50 +1,37 @@
|
|||||||
FROM ubuntu:20.04
|
FROM python:3.10-slim
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
LABEL maintainer="Hugging Face"
|
LABEL maintainer="Hugging Face"
|
||||||
LABEL repository="diffusers"
|
LABEL repository="diffusers"
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
RUN apt-get -y update \
|
RUN apt-get -y update && apt-get install -y bash \
|
||||||
&& apt-get install -y software-properties-common \
|
build-essential \
|
||||||
&& add-apt-repository ppa:deadsnakes/ppa
|
git \
|
||||||
|
git-lfs \
|
||||||
|
curl \
|
||||||
|
ca-certificates \
|
||||||
|
libsndfile1-dev \
|
||||||
|
libgl1
|
||||||
|
|
||||||
RUN apt install -y bash \
|
ENV UV_PYTHON=/usr/local/bin/python
|
||||||
build-essential \
|
|
||||||
git \
|
|
||||||
git-lfs \
|
|
||||||
curl \
|
|
||||||
ca-certificates \
|
|
||||||
libsndfile1-dev \
|
|
||||||
python3.10 \
|
|
||||||
python3.10-dev \
|
|
||||||
python3-pip \
|
|
||||||
libgl1 \
|
|
||||||
python3.10-venv && \
|
|
||||||
rm -rf /var/lib/apt/lists
|
|
||||||
|
|
||||||
# make sure to use venv
|
|
||||||
RUN python3.10 -m venv /opt/venv
|
|
||||||
ENV PATH="/opt/venv/bin:$PATH"
|
|
||||||
|
|
||||||
# pre-install the heavy dependencies (these can later be overridden by the deps from setup.py)
|
# pre-install the heavy dependencies (these can later be overridden by the deps from setup.py)
|
||||||
RUN python3.10 -m pip install --no-cache-dir --upgrade pip uv==0.1.11 && \
|
RUN pip install uv
|
||||||
python3.10 -m uv pip install --no-cache-dir \
|
RUN uv pip install --no-cache-dir \
|
||||||
torch \
|
torch \
|
||||||
torchvision \
|
torchvision \
|
||||||
torchaudio \
|
torchaudio \
|
||||||
invisible_watermark \
|
--extra-index-url https://download.pytorch.org/whl/cpu
|
||||||
--extra-index-url https://download.pytorch.org/whl/cpu && \
|
|
||||||
python3.10 -m uv pip install --no-cache-dir \
|
RUN uv pip install --no-cache-dir "git+https://github.com/huggingface/diffusers.git@main#egg=diffusers[test]"
|
||||||
accelerate \
|
|
||||||
datasets \
|
# Extra dependencies
|
||||||
hf-doc-builder \
|
RUN uv pip install --no-cache-dir \
|
||||||
huggingface-hub \
|
accelerate \
|
||||||
Jinja2 \
|
numpy==1.26.4 \
|
||||||
librosa \
|
hf_transfer
|
||||||
numpy==1.26.4 \
|
|
||||||
scipy \
|
RUN apt-get clean && rm -rf /var/lib/apt/lists/* && apt-get autoremove && apt-get autoclean
|
||||||
tensorboard \
|
|
||||||
transformers matplotlib \
|
|
||||||
hf_transfer
|
|
||||||
|
|
||||||
CMD ["/bin/bash"]
|
CMD ["/bin/bash"]
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ FROM nvidia/cuda:12.1.0-runtime-ubuntu20.04
|
|||||||
LABEL maintainer="Hugging Face"
|
LABEL maintainer="Hugging Face"
|
||||||
LABEL repository="diffusers"
|
LABEL repository="diffusers"
|
||||||
|
|
||||||
|
ARG PYTHON_VERSION=3.12
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
RUN apt-get -y update \
|
RUN apt-get -y update \
|
||||||
&& apt-get install -y software-properties-common \
|
&& apt-get install -y software-properties-common \
|
||||||
&& add-apt-repository ppa:deadsnakes/ppa
|
&& add-apt-repository ppa:deadsnakes/ppa && \
|
||||||
|
apt-get update
|
||||||
|
|
||||||
RUN apt install -y bash \
|
RUN apt install -y bash \
|
||||||
build-essential \
|
build-essential \
|
||||||
@@ -16,36 +18,31 @@ RUN apt install -y bash \
|
|||||||
ca-certificates \
|
ca-certificates \
|
||||||
libsndfile1-dev \
|
libsndfile1-dev \
|
||||||
libgl1 \
|
libgl1 \
|
||||||
python3.10 \
|
python3 \
|
||||||
python3.10-dev \
|
|
||||||
python3-pip \
|
python3-pip \
|
||||||
python3.10-venv && \
|
&& apt-get clean \
|
||||||
rm -rf /var/lib/apt/lists
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# make sure to use venv
|
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
RUN python3.10 -m venv /opt/venv
|
ENV PATH="/root/.local/bin:$PATH"
|
||||||
ENV PATH="/opt/venv/bin:$PATH"
|
ENV VIRTUAL_ENV="/opt/venv"
|
||||||
|
ENV UV_PYTHON_INSTALL_DIR=/opt/uv/python
|
||||||
|
RUN uv venv --python ${PYTHON_VERSION} --seed ${VIRTUAL_ENV}
|
||||||
|
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||||
|
|
||||||
# pre-install the heavy dependencies (these can later be overridden by the deps from setup.py)
|
# pre-install the heavy dependencies (these can later be overridden by the deps from setup.py)
|
||||||
RUN python3.10 -m pip install --no-cache-dir --upgrade pip uv==0.1.11 && \
|
RUN uv pip install --no-cache-dir \
|
||||||
python3.10 -m uv pip install --no-cache-dir \
|
|
||||||
torch \
|
torch \
|
||||||
torchvision \
|
torchvision \
|
||||||
torchaudio \
|
torchaudio
|
||||||
invisible_watermark && \
|
|
||||||
python3.10 -m pip install --no-cache-dir \
|
RUN uv pip install --no-cache-dir "git+https://github.com/huggingface/diffusers.git@main#egg=diffusers[test]"
|
||||||
|
|
||||||
|
# Extra dependencies
|
||||||
|
RUN uv pip install --no-cache-dir \
|
||||||
accelerate \
|
accelerate \
|
||||||
datasets \
|
|
||||||
hf-doc-builder \
|
|
||||||
huggingface-hub \
|
|
||||||
hf_transfer \
|
|
||||||
Jinja2 \
|
|
||||||
librosa \
|
|
||||||
numpy==1.26.4 \
|
numpy==1.26.4 \
|
||||||
scipy \
|
pytorch-lightning \
|
||||||
tensorboard \
|
|
||||||
transformers \
|
|
||||||
pytorch-lightning \
|
|
||||||
hf_transfer
|
hf_transfer
|
||||||
|
|
||||||
CMD ["/bin/bash"]
|
CMD ["/bin/bash"]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ FROM nvidia/cuda:12.1.0-runtime-ubuntu20.04
|
|||||||
LABEL maintainer="Hugging Face"
|
LABEL maintainer="Hugging Face"
|
||||||
LABEL repository="diffusers"
|
LABEL repository="diffusers"
|
||||||
|
|
||||||
|
ARG PYTHON_VERSION=3.10
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
ENV MINIMUM_SUPPORTED_TORCH_VERSION="2.1.0"
|
ENV MINIMUM_SUPPORTED_TORCH_VERSION="2.1.0"
|
||||||
ENV MINIMUM_SUPPORTED_TORCHVISION_VERSION="0.16.0"
|
ENV MINIMUM_SUPPORTED_TORCHVISION_VERSION="0.16.0"
|
||||||
@@ -9,7 +10,8 @@ ENV MINIMUM_SUPPORTED_TORCHAUDIO_VERSION="2.1.0"
|
|||||||
|
|
||||||
RUN apt-get -y update \
|
RUN apt-get -y update \
|
||||||
&& apt-get install -y software-properties-common \
|
&& apt-get install -y software-properties-common \
|
||||||
&& add-apt-repository ppa:deadsnakes/ppa
|
&& add-apt-repository ppa:deadsnakes/ppa && \
|
||||||
|
apt-get update
|
||||||
|
|
||||||
RUN apt install -y bash \
|
RUN apt install -y bash \
|
||||||
build-essential \
|
build-essential \
|
||||||
@@ -19,35 +21,31 @@ RUN apt install -y bash \
|
|||||||
ca-certificates \
|
ca-certificates \
|
||||||
libsndfile1-dev \
|
libsndfile1-dev \
|
||||||
libgl1 \
|
libgl1 \
|
||||||
python3.10 \
|
python3 \
|
||||||
python3.10-dev \
|
|
||||||
python3-pip \
|
python3-pip \
|
||||||
python3.10-venv && \
|
&& apt-get clean \
|
||||||
rm -rf /var/lib/apt/lists
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# make sure to use venv
|
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
RUN python3.10 -m venv /opt/venv
|
ENV PATH="/root/.local/bin:$PATH"
|
||||||
ENV PATH="/opt/venv/bin:$PATH"
|
ENV VIRTUAL_ENV="/opt/venv"
|
||||||
|
ENV UV_PYTHON_INSTALL_DIR=/opt/uv/python
|
||||||
|
RUN uv venv --python ${PYTHON_VERSION} --seed ${VIRTUAL_ENV}
|
||||||
|
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||||
|
|
||||||
# pre-install the heavy dependencies (these can later be overridden by the deps from setup.py)
|
# pre-install the heavy dependencies (these can later be overridden by the deps from setup.py)
|
||||||
RUN python3.10 -m pip install --no-cache-dir --upgrade pip uv==0.1.11 && \
|
RUN uv pip install --no-cache-dir \
|
||||||
python3.10 -m uv pip install --no-cache-dir \
|
|
||||||
torch==$MINIMUM_SUPPORTED_TORCH_VERSION \
|
torch==$MINIMUM_SUPPORTED_TORCH_VERSION \
|
||||||
torchvision==$MINIMUM_SUPPORTED_TORCHVISION_VERSION \
|
torchvision==$MINIMUM_SUPPORTED_TORCHVISION_VERSION \
|
||||||
torchaudio==$MINIMUM_SUPPORTED_TORCHAUDIO_VERSION \
|
torchaudio==$MINIMUM_SUPPORTED_TORCHAUDIO_VERSION
|
||||||
invisible_watermark && \
|
|
||||||
python3.10 -m pip install --no-cache-dir \
|
RUN uv pip install --no-cache-dir "git+https://github.com/huggingface/diffusers.git@main#egg=diffusers[test]"
|
||||||
|
|
||||||
|
# Extra dependencies
|
||||||
|
RUN uv pip install --no-cache-dir \
|
||||||
accelerate \
|
accelerate \
|
||||||
datasets \
|
|
||||||
hf-doc-builder \
|
|
||||||
huggingface-hub \
|
|
||||||
hf_transfer \
|
|
||||||
Jinja2 \
|
|
||||||
librosa \
|
|
||||||
numpy==1.26.4 \
|
numpy==1.26.4 \
|
||||||
scipy \
|
pytorch-lightning \
|
||||||
tensorboard \
|
|
||||||
transformers \
|
|
||||||
hf_transfer
|
hf_transfer
|
||||||
|
|
||||||
CMD ["/bin/bash"]
|
CMD ["/bin/bash"]
|
||||||
|
|||||||
@@ -2,50 +2,48 @@ FROM nvidia/cuda:12.1.0-runtime-ubuntu20.04
|
|||||||
LABEL maintainer="Hugging Face"
|
LABEL maintainer="Hugging Face"
|
||||||
LABEL repository="diffusers"
|
LABEL repository="diffusers"
|
||||||
|
|
||||||
|
ARG PYTHON_VERSION=3.12
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
RUN apt-get -y update \
|
RUN apt-get -y update \
|
||||||
&& apt-get install -y software-properties-common \
|
&& apt-get install -y software-properties-common \
|
||||||
&& add-apt-repository ppa:deadsnakes/ppa
|
&& add-apt-repository ppa:deadsnakes/ppa && \
|
||||||
|
apt-get update
|
||||||
|
|
||||||
RUN apt install -y bash \
|
RUN apt install -y bash \
|
||||||
build-essential \
|
build-essential \
|
||||||
git \
|
git \
|
||||||
git-lfs \
|
git-lfs \
|
||||||
curl \
|
curl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
libsndfile1-dev \
|
libsndfile1-dev \
|
||||||
libgl1 \
|
libgl1 \
|
||||||
python3.10 \
|
python3 \
|
||||||
python3.10-dev \
|
python3-pip \
|
||||||
python3-pip \
|
&& apt-get clean \
|
||||||
python3.10-venv && \
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
rm -rf /var/lib/apt/lists
|
|
||||||
|
|
||||||
# make sure to use venv
|
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
RUN python3.10 -m venv /opt/venv
|
ENV PATH="/root/.local/bin:$PATH"
|
||||||
ENV PATH="/opt/venv/bin:$PATH"
|
ENV VIRTUAL_ENV="/opt/venv"
|
||||||
|
ENV UV_PYTHON_INSTALL_DIR=/opt/uv/python
|
||||||
|
RUN uv venv --python ${PYTHON_VERSION} --seed ${VIRTUAL_ENV}
|
||||||
|
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||||
|
|
||||||
# pre-install the heavy dependencies (these can later be overridden by the deps from setup.py)
|
# pre-install the heavy dependencies (these can later be overridden by the deps from setup.py)
|
||||||
RUN python3.10 -m pip install --no-cache-dir --upgrade pip uv==0.1.11 && \
|
RUN uv pip install --no-cache-dir \
|
||||||
python3.10 -m pip install --no-cache-dir \
|
torch \
|
||||||
torch \
|
torchvision \
|
||||||
torchvision \
|
torchaudio
|
||||||
torchaudio \
|
|
||||||
invisible_watermark && \
|
RUN uv pip install --no-cache-dir "git+https://github.com/huggingface/diffusers.git@main#egg=diffusers[test]"
|
||||||
python3.10 -m uv pip install --no-cache-dir \
|
|
||||||
accelerate \
|
# Extra dependencies
|
||||||
datasets \
|
RUN uv pip install --no-cache-dir \
|
||||||
hf-doc-builder \
|
accelerate \
|
||||||
huggingface-hub \
|
numpy==1.26.4 \
|
||||||
hf_transfer \
|
pytorch-lightning \
|
||||||
Jinja2 \
|
hf_transfer \
|
||||||
librosa \
|
xformers
|
||||||
numpy==1.26.4 \
|
|
||||||
scipy \
|
|
||||||
tensorboard \
|
|
||||||
transformers \
|
|
||||||
xformers \
|
|
||||||
hf_transfer
|
|
||||||
|
|
||||||
CMD ["/bin/bash"]
|
CMD ["/bin/bash"]
|
||||||
|
|||||||
@@ -14,51 +14,47 @@ specific language governing permissions and limitations under the License.
|
|||||||
|
|
||||||
## 서문 [[preamble]]
|
## 서문 [[preamble]]
|
||||||
|
|
||||||
[Diffusers](https://huggingface.co/docs/diffusers/index)는 사전 훈련된 diffusion 모델을 제공하며 추론 및 훈련을 위한 모듈식 툴박스로 사용됩니다.
|
[Diffusers](https://huggingface.co/docs/diffusers/index)는 사전 훈련된 diffusion 모델을 제공하며, 추론과 훈련을 위한 모듈형 툴박스로 활용됩니다.
|
||||||
|
|
||||||
이 기술의 실제 적용과 사회에 미칠 수 있는 부정적인 영향을 고려하여 Diffusers 라이브러리의 개발, 사용자 기여 및 사용에 윤리 지침을 제공하는 것이 중요하다고 생각합니다.
|
이 기술의 실제 적용 사례와 사회에 미칠 수 있는 잠재적 부정적 영향을 고려할 때, Diffusers 라이브러리의 개발, 사용자 기여, 사용에 윤리 지침을 제공하는 것이 중요하다고 생각합니다.
|
||||||
|
|
||||||
이이 기술을 사용함에 따른 위험은 여전히 검토 중이지만, 몇 가지 예를 들면: 예술가들에 대한 저작권 문제; 딥 페이크의 악용; 부적절한 맥락에서의 성적 콘텐츠 생성; 동의 없는 사칭; 소수자 집단의 억압을 영속화하는 유해한 사회적 편견 등이 있습니다.
|
|
||||||
|
|
||||||
우리는 위험을 지속적으로 추적하고 커뮤니티의 응답과 소중한 피드백에 따라 다음 지침을 조정할 것입니다.
|
|
||||||
|
|
||||||
|
이 기술 사용과 관련된 위험은 여전히 검토 중이지만, 예를 들면: 예술가의 저작권 문제, 딥페이크 악용, 부적절한 맥락에서의 성적 콘텐츠 생성, 비동의 사칭, 소수자 집단 억압을 영속화하는 유해한 사회적 편견 등이 있습니다.
|
||||||
|
우리는 이러한 위험을 지속적으로 추적하고, 커뮤니티의 반응과 소중한 피드백에 따라 아래 지침을 조정할 것입니다.
|
||||||
|
|
||||||
## 범위 [[scope]]
|
## 범위 [[scope]]
|
||||||
|
|
||||||
Diffusers 커뮤니티는 프로젝트의 개발에 다음과 같은 윤리 지침을 적용하며, 특히 윤리적 문제와 관련된 민감한 주제에 대한 커뮤니티의 기여를 조정하는 데 도움을 줄 것입니다.
|
Diffusers 커뮤니티는 프로젝트 개발에 다음 윤리 지침을 적용하며, 특히 윤리적 문제와 관련된 민감한 주제에 대해 커뮤니티의 기여를 조정하는 데 도움을 줄 것입니다.
|
||||||
|
|
||||||
|
|
||||||
## 윤리 지침 [[ethical-guidelines]]
|
## 윤리 지침 [[ethical-guidelines]]
|
||||||
|
|
||||||
다음 윤리 지침은 일반적으로 적용되지만, 민감한 윤리적 문제와 관련하여 기술적 선택을 할 때 이를 우선적으로 적용할 것입니다. 나아가, 해당 기술의 최신 동향과 관련된 새로운 위험이 발생함에 따라 이러한 윤리 원칙을 조정할 것을 약속드립니다.
|
다음 윤리 지침은 일반적으로 적용되지만, 윤리적으로 민감한 문제와 관련된 기술적 선택을 할 때 우선적으로 적용됩니다. 또한, 해당 기술의 최신 동향과 관련된 새로운 위험이 발생함에 따라 이러한 윤리 원칙을 지속적으로 조정할 것을 약속합니다.
|
||||||
|
|
||||||
- **투명성**: 우리는 PR을 관리하고, 사용자에게 우리의 선택을 설명하며, 기술적 의사결정을 내릴 때 투명성을 유지할 것을 약속합니다.
|
- **투명성**: 우리는 PR 관리, 사용자에게 선택의 이유 설명, 기술적 의사결정 과정에서 투명성을 유지할 것을 약속합니다.
|
||||||
|
|
||||||
- **일관성**: 우리는 프로젝트 관리에서 사용자들에게 동일한 수준의 관심을 보장하고 기술적으로 안정되고 일관된 상태를 유지할 것을 약속합니다.
|
- **일관성**: 프로젝트 관리에서 모든 사용자에게 동일한 수준의 관심을 보장하고, 기술적으로 안정적이고 일관된 상태를 유지할 것을 약속합니다.
|
||||||
|
|
||||||
- **간결성**: Diffusers 라이브러리를 사용하고 활용하기 쉽게 만들기 위해, 프로젝트의 목표를 간결하고 일관성 있게 유지할 것을 약속합니다.
|
- **간결성**: Diffusers 라이브러리를 쉽게 사용하고 활용할 수 있도록, 프로젝트의 목표를 간결하고 일관성 있게 유지할 것을 약속합니다.
|
||||||
|
|
||||||
- **접근성**: Diffusers 프로젝트는 기술적 전문 지식 없어도 프로젝트 운영에 참여할 수 있는 기여자의 진입장벽을 낮춥니다. 이를 통해 연구 결과물이 커뮤니티에 더 잘 접근할 수 있게 됩니다.
|
- **접근성**: Diffusers 프로젝트는 기술적 전문지식이 없어도 기여할 수 있도록 진입장벽을 낮춥니다. 이를 통해 연구 결과물이 커뮤니티에 더 잘 접근될 수 있습니다.
|
||||||
|
|
||||||
- **재현성**: 우리는 Diffusers 라이브러리를 통해 제공되는 업스트림(upstream) 코드, 모델 및 데이터셋의 재현성에 대해 투명하게 공개할 것을 목표로 합니다.
|
- **재현성**: 우리는 Diffusers 라이브러리를 통해 제공되는 업스트림 코드, 모델, 데이터셋의 재현성에 대해 투명하게 공개하는 것을 목표로 합니다.
|
||||||
|
|
||||||
- **책임**: 우리는 커뮤니티와 팀워크를 통해, 이 기술의 잠재적인 위험과 위험을 예측하고 완화하는 데 대한 공동 책임을 가지고 있습니다.
|
|
||||||
|
|
||||||
|
- **책임**: 커뮤니티와 팀워크를 통해, 이 기술의 잠재적 위험을 예측하고 완화하는 데 공동 책임을 집니다.
|
||||||
|
|
||||||
## 구현 사례: 안전 기능과 메커니즘 [[examples-of-implementations-safety-features-and-mechanisms]]
|
## 구현 사례: 안전 기능과 메커니즘 [[examples-of-implementations-safety-features-and-mechanisms]]
|
||||||
|
|
||||||
팀은 diffusion 기술과 관련된 잠재적인 윤리 및 사회적 위험에 대처하기 위한 기술적 및 비기술적 도구를 제공하고자 하고 있습니다. 또한, 커뮤니티의 참여는 이러한 기능의 구현하고 우리와 함께 인식을 높이는 데 매우 중요합니다.
|
팀은 diffusion 기술과 관련된 잠재적 윤리 및 사회적 위험에 대응하기 위해 기술적·비기술적 도구를 제공하고자 노력하고 있습니다. 또한, 커뮤니티의 참여는 이러한 기능 구현과 인식 제고에 매우 중요합니다.
|
||||||
|
|
||||||
- [**커뮤니티 탭**](https://huggingface.co/docs/hub/repositories-pull-requests-discussions): 이를 통해 커뮤니티는 프로젝트에 대해 토론하고 더 나은 협력을 할 수 있습니다.
|
- [**커뮤니티 탭**](https://huggingface.co/docs/hub/repositories-pull-requests-discussions): 커뮤니티가 프로젝트에 대해 토론하고 더 나은 협업을 할 수 있도록 지원합니다.
|
||||||
|
|
||||||
- **편향 탐색 및 평가**: Hugging Face 팀은 Stable Diffusion 모델의 편향성을 대화형으로 보여주는 [space](https://huggingface.co/spaces/society-ethics/DiffusionBiasExplorer)을 제공합니다. 이런 의미에서, 우리는 편향 탐색 및 평가를 지원하고 장려합니다.
|
- **편향 탐색 및 평가**: Hugging Face 팀은 Stable Diffusion 모델의 편향성을 대화형으로 보여주는 [space](https://huggingface.co/spaces/society-ethics/DiffusionBiasExplorer)를 제공합니다. 우리는 이러한 편향 탐색과 평가를 지원하고 장려합니다.
|
||||||
|
|
||||||
- **배포에서의 안전 유도**
|
- **배포에서의 안전 유도**
|
||||||
|
|
||||||
- [**안전한 Stable Diffusion**](https://huggingface.co/docs/diffusers/main/en/api/pipelines/stable_diffusion/stable_diffusion_safe): 이는 필터되지 않은 웹 크롤링 데이터셋으로 훈련된 Stable Diffusion과 같은 모델이 부적절한 변질에 취약한 문제를 완화합니다. 관련 논문: [Safe Latent Diffusion: Mitigating Inappropriate Degeneration in Diffusion Models](https://huggingface.co/papers/2211.05105).
|
- [**안전한 Stable Diffusion**](https://huggingface.co/docs/diffusers/main/en/api/pipelines/stable_diffusion/stable_diffusion_safe): 필터링되지 않은 웹 크롤링 데이터셋으로 훈련된 Stable Diffusion과 같은 모델이 부적절하게 변질되는 문제를 완화합니다. 관련 논문: [Safe Latent Diffusion: Mitigating Inappropriate Degeneration in Diffusion Models](https://huggingface.co/papers/2211.05105).
|
||||||
|
|
||||||
- [**안전 검사기**](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/stable_diffusion/safety_checker.py): 이미지가 생성된 후에 이미자가 임베딩 공간에서 일련의 하드코딩된 유해 개념의 클래스일 확률을 확인하고 비교합니다. 유해 개념은 역공학을 방지하기 위해 의도적으로 숨겨져 있습니다.
|
- [**안전 검사기**](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/stable_diffusion/safety_checker.py): 생성된 이미지가 임베딩 공간에서 하드코딩된 유해 개념 클래스와 일치할 확률을 확인하고 비교합니다. 유해 개념은 역공학을 방지하기 위해 의도적으로 숨겨져 있습니다.
|
||||||
|
|
||||||
- **Hub에서의 단계적인 배포**: 특히 민감한 상황에서는 일부 리포지토리에 대한 접근을 제한해야 합니다. 이 단계적인 배포는 중간 단계로, 리포지토리 작성자가 사용에 대한 더 많은 통제력을 갖게 합니다.
|
- **Hub에서의 단계적 배포**: 특히 민감한 상황에서는 일부 리포지토리에 대한 접근을 제한할 수 있습니다. 단계적 배포는 리포지토리 작성자가 사용에 대해 더 많은 통제권을 갖도록 하는 중간 단계입니다.
|
||||||
|
|
||||||
- **라이선싱**: [OpenRAILs](https://huggingface.co/blog/open_rail)와 같은 새로운 유형의 라이선싱을 통해 자유로운 접근을 보장하면서도 더 책임 있는 사용을 위한 일련의 제한을 둘 수 있습니다.
|
- **라이선싱**: [OpenRAILs](https://huggingface.co/blog/open_rail)와 같은 새로운 유형의 라이선스를 통해 자유로운 접근을 보장하면서도 보다 책임 있는 사용을 위한 일련의 제한을 둘 수 있습니다.
|
||||||
|
|||||||
@@ -0,0 +1,244 @@
|
|||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, Union
|
||||||
|
import torch
|
||||||
|
from PIL import Image
|
||||||
|
from reward_scorers import BaseRewardScorer, available_scorers, build_scorer
|
||||||
|
from diffusers import FluxPipeline
|
||||||
|
from diffusers.utils import make_image_grid
|
||||||
|
|
||||||
|
|
||||||
|
class AdversarialFluxPipeline(FluxPipeline):
|
||||||
|
def adversarial_refinement(
|
||||||
|
self,
|
||||||
|
prompt: Union[str, list[str]],
|
||||||
|
reward_model: BaseRewardScorer,
|
||||||
|
reward_prompt: Optional[Union[str, list[str]]] = None,
|
||||||
|
num_rounds: int = 1,
|
||||||
|
step_size: float = 0.1,
|
||||||
|
epsilon: Optional[float] = None,
|
||||||
|
attack_type: str = "pgd",
|
||||||
|
record_intermediate: bool = False,
|
||||||
|
**generate_kwargs,
|
||||||
|
):
|
||||||
|
if num_rounds < 0:
|
||||||
|
raise ValueError("`num_rounds` must be non-negative.")
|
||||||
|
if attack_type not in {"pgd", "fgsm"}:
|
||||||
|
raise ValueError("`attack_type` must be either 'pgd' or 'fgsm'.")
|
||||||
|
|
||||||
|
generate_kwargs = dict(generate_kwargs)
|
||||||
|
height, width = self._resolve_height_width(generate_kwargs.get("height"), generate_kwargs.get("width"))
|
||||||
|
generate_kwargs["height"] = height
|
||||||
|
generate_kwargs["width"] = width
|
||||||
|
generate_kwargs["output_type"] = "latent"
|
||||||
|
generate_kwargs.setdefault("return_dict", True)
|
||||||
|
|
||||||
|
flux_output = super().__call__(prompt=prompt, **generate_kwargs)
|
||||||
|
latents = flux_output.images
|
||||||
|
device = latents.device
|
||||||
|
|
||||||
|
reward_model = reward_model.to(device)
|
||||||
|
reward_model.eval()
|
||||||
|
if getattr(reward_model, "supports_gradients", True) is False and (num_rounds != 0 or attack_type == "fgsm"):
|
||||||
|
raise ValueError(
|
||||||
|
f"Scorer `{reward_model.__class__.__name__}` does not support gradients required for adversarial refinement."
|
||||||
|
)
|
||||||
|
|
||||||
|
reward_prompts = self._expand_prompts(reward_prompt if reward_prompt is not None else prompt, latents.shape[0])
|
||||||
|
|
||||||
|
with torch.no_grad():
|
||||||
|
current_images = self._decode_packed_latents(latents, height, width).to(dtype=torch.float32)
|
||||||
|
current_scores = reward_model(current_images, reward_prompts)
|
||||||
|
|
||||||
|
intermediate_images = []
|
||||||
|
if record_intermediate:
|
||||||
|
intermediate_images.append(self.image_processor.postprocess(current_images, output_type="pil"))
|
||||||
|
|
||||||
|
score_trace = [current_scores.mean().item()]
|
||||||
|
per_sample_scores = [current_scores.detach().cpu().tolist()]
|
||||||
|
|
||||||
|
if num_rounds == 0:
|
||||||
|
max_rounds = 0
|
||||||
|
else:
|
||||||
|
max_rounds = 1 if attack_type == "fgsm" else num_rounds
|
||||||
|
|
||||||
|
for round_index in range(max_rounds):
|
||||||
|
current_images.requires_grad_(True)
|
||||||
|
scores = reward_model(current_images, reward_prompts)
|
||||||
|
total_score = scores.mean()
|
||||||
|
|
||||||
|
grad = torch.autograd.grad(total_score, current_images, retain_graph=False, create_graph=False)[0]
|
||||||
|
|
||||||
|
if attack_type == "fgsm":
|
||||||
|
step = epsilon if epsilon is not None else step_size
|
||||||
|
update = step * grad.sign()
|
||||||
|
else:
|
||||||
|
update = step_size * grad
|
||||||
|
|
||||||
|
with torch.no_grad():
|
||||||
|
current_images = current_images + update
|
||||||
|
current_images = current_images.clamp_(-1.0, 1.0)
|
||||||
|
|
||||||
|
current_images = current_images.detach()
|
||||||
|
|
||||||
|
with torch.no_grad():
|
||||||
|
current_scores = reward_model(current_images, reward_prompts)
|
||||||
|
|
||||||
|
score_trace.append(current_scores.mean().item())
|
||||||
|
per_sample_scores.append(current_scores.detach().cpu().tolist())
|
||||||
|
|
||||||
|
if record_intermediate:
|
||||||
|
intermediate_images.append(self.image_processor.postprocess(current_images, output_type="pil"))
|
||||||
|
|
||||||
|
final_images = self.image_processor.postprocess(current_images, output_type="pil")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"images": final_images,
|
||||||
|
"latents": latents.detach(),
|
||||||
|
"score_trace": score_trace,
|
||||||
|
"score_trace_per_sample": per_sample_scores,
|
||||||
|
"final_scores": current_scores.detach().cpu().tolist(),
|
||||||
|
"intermediate_images": intermediate_images,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _decode_packed_latents(self, latents: torch.Tensor, height: int, width: int) -> torch.Tensor:
|
||||||
|
unpacked = self._unpack_latents(latents, height, width, self.vae_scale_factor)
|
||||||
|
unpacked = (unpacked / self.vae.config.scaling_factor) + self.vae.config.shift_factor
|
||||||
|
decoded = self.vae.decode(unpacked, return_dict=False)[0]
|
||||||
|
return decoded
|
||||||
|
|
||||||
|
def _resolve_height_width(self, height: Optional[int], width: Optional[int]) -> tuple[int, int]:
|
||||||
|
height = height or self.default_sample_size * self.vae_scale_factor
|
||||||
|
width = width or self.default_sample_size * self.vae_scale_factor
|
||||||
|
return height, width
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _expand_prompts(prompts: Union[str, list[str]], batch_size: int) -> list[str]:
|
||||||
|
if isinstance(prompts, str):
|
||||||
|
return [prompts] * batch_size
|
||||||
|
if len(prompts) != batch_size:
|
||||||
|
raise ValueError(f"Expected {batch_size} reward prompts, got {len(prompts)}.")
|
||||||
|
return prompts
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--model-id", type=str, default="black-forest-labs/FLUX.1-dev")
|
||||||
|
parser.add_argument(
|
||||||
|
"--prompt", type=str, default="Photo of a dog sitting near a sea waiting for its companion to come."
|
||||||
|
)
|
||||||
|
parser.add_argument("--reward-prompt", type=str, default=None)
|
||||||
|
parser.add_argument("--output", default="flux_adversarial.png")
|
||||||
|
parser.add_argument("--num-rounds", type=int, default=3)
|
||||||
|
parser.add_argument("--step-size", type=float, default=0.1)
|
||||||
|
parser.add_argument("--epsilon", type=float, help="FGSM epsilon. Falls back to step size when omitted.")
|
||||||
|
parser.add_argument("--attack-type", choices=["pgd", "fgsm"], default="pgd")
|
||||||
|
parser.add_argument("--num-inference-steps", type=int, default=30)
|
||||||
|
parser.add_argument("--guidance-scale", type=float, default=3.5)
|
||||||
|
parser.add_argument("--height", type=int, default=1024)
|
||||||
|
parser.add_argument("--width", type=int, default=1024)
|
||||||
|
parser.add_argument("--seed", type=int, default=0)
|
||||||
|
parser.add_argument("--device", default="cuda" if torch.cuda.is_available() else "cpu")
|
||||||
|
parser.add_argument("--scorer", choices=available_scorers(), default="clip")
|
||||||
|
parser.add_argument("--scorer-model-id", type=str, default=None)
|
||||||
|
parser.add_argument("--record-intermediates", action="store_true")
|
||||||
|
parser.add_argument("--intermediate-dir", type=str, default=None)
|
||||||
|
parser.add_argument("--metadata-output", type=str, default=None)
|
||||||
|
parser.add_argument("--output-root", type=str, required=True)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def save_intermediates(intermediate_dir: Path, rounds: list[list[Image.Image]]) -> None:
|
||||||
|
intermediate_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
for round_index, images in enumerate(rounds):
|
||||||
|
for sample_index, image in enumerate(images):
|
||||||
|
filename = intermediate_dir / f"round_{round_index:02d}_sample_{sample_index:02d}.png"
|
||||||
|
image.save(filename)
|
||||||
|
|
||||||
|
if rounds and len(rounds[0]) == 1:
|
||||||
|
grid = make_image_grid([imgs[0] for imgs in rounds], cols=len(rounds), rows=1)
|
||||||
|
grid.save(intermediate_dir / "grid.png")
|
||||||
|
|
||||||
|
|
||||||
|
def dump_metadata(metadata_path: Path, payload: dict[str, object]) -> None:
|
||||||
|
metadata_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with metadata_path.open("w", encoding="utf-8") as file:
|
||||||
|
json.dump(payload, file)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
args = parse_args()
|
||||||
|
|
||||||
|
generator = None
|
||||||
|
if args.seed is not None:
|
||||||
|
generator = torch.Generator(device=args.device)
|
||||||
|
generator.manual_seed(args.seed)
|
||||||
|
|
||||||
|
dtype = torch.bfloat16 if args.device.startswith("cuda") else torch.float32
|
||||||
|
pipe = AdversarialFluxPipeline.from_pretrained(args.model_id, torch_dtype=dtype)
|
||||||
|
pipe.to(args.device)
|
||||||
|
|
||||||
|
reward_model = build_scorer(name=args.scorer, model_id=args.scorer_model_id, device=args.device)
|
||||||
|
|
||||||
|
record_intermediate = args.record_intermediates or args.intermediate_dir is not None
|
||||||
|
|
||||||
|
result = pipe.adversarial_refinement(
|
||||||
|
prompt=args.prompt,
|
||||||
|
reward_prompt=args.reward_prompt,
|
||||||
|
reward_model=reward_model,
|
||||||
|
num_rounds=args.num_rounds,
|
||||||
|
step_size=args.step_size,
|
||||||
|
epsilon=args.epsilon,
|
||||||
|
attack_type=args.attack_type,
|
||||||
|
num_inference_steps=args.num_inference_steps,
|
||||||
|
guidance_scale=args.guidance_scale,
|
||||||
|
height=args.height,
|
||||||
|
width=args.width,
|
||||||
|
generator=generator,
|
||||||
|
record_intermediate=record_intermediate,
|
||||||
|
)
|
||||||
|
|
||||||
|
images = result["images"]
|
||||||
|
output_path = Path(args.output_root)
|
||||||
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
images[0].save(output_path)
|
||||||
|
|
||||||
|
if args.intermediate_dir:
|
||||||
|
intermediate_dir = output_path / args.intermediate_dir
|
||||||
|
if intermediate_dir and result["intermediate_images"]:
|
||||||
|
save_intermediates(intermediate_dir, result["intermediate_images"])
|
||||||
|
|
||||||
|
if args.metadata_output:
|
||||||
|
metadata_payload = {
|
||||||
|
"prompt": args.prompt,
|
||||||
|
"reward_prompt": args.reward_prompt or "",
|
||||||
|
"scorer": {
|
||||||
|
"name": args.scorer,
|
||||||
|
"model_id": getattr(reward_model, "model_id", args.scorer_model_id or ""),
|
||||||
|
},
|
||||||
|
"attack": {
|
||||||
|
"type": args.attack_type,
|
||||||
|
"num_rounds": args.num_rounds,
|
||||||
|
"step_size": args.step_size,
|
||||||
|
"epsilon": args.epsilon
|
||||||
|
if args.epsilon is not None
|
||||||
|
else args.step_size
|
||||||
|
if args.attack_type == "fgsm"
|
||||||
|
else None,
|
||||||
|
"rounds_executed": len(result["score_trace"]) - 1,
|
||||||
|
},
|
||||||
|
"score_trace": result["score_trace"],
|
||||||
|
"score_trace_per_sample": result["score_trace_per_sample"],
|
||||||
|
"final_scores": result["final_scores"],
|
||||||
|
}
|
||||||
|
metadata_path = output_path / args.metadata_output
|
||||||
|
if metadata_path:
|
||||||
|
dump_metadata(metadata_path, metadata_payload)
|
||||||
|
|
||||||
|
print("Mean score trace:", result["score_trace"])
|
||||||
|
print("Final per-sample scores:", result["final_scores"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
import warnings
|
||||||
|
from typing import Any, Dict, Optional, Sequence, Tuple, Type, Union
|
||||||
|
|
||||||
|
import torch
|
||||||
|
import torch.nn as nn
|
||||||
|
import torch.nn.functional as F
|
||||||
|
from transformers import CLIPImageProcessor, CLIPModel, CLIPTokenizer, SiglipModel, SiglipProcessor
|
||||||
|
|
||||||
|
|
||||||
|
class BaseRewardScorer(nn.Module):
|
||||||
|
"""
|
||||||
|
Base interface for reward scorers.
|
||||||
|
|
||||||
|
Subclasses are expected to implement a differentiable `forward` method that
|
||||||
|
accepts a batch of images in the `[-1, 1]` range and a batch of prompt strings
|
||||||
|
with the same batch dimension.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name: str = "base"
|
||||||
|
default_model_id: Optional[str] = None
|
||||||
|
supports_gradients: bool = True
|
||||||
|
|
||||||
|
def __init__(self, model_id: Optional[str] = None, device: Optional[torch.device] = None):
|
||||||
|
super().__init__()
|
||||||
|
self.model_id = model_id or self.default_model_id
|
||||||
|
if self.model_id is None:
|
||||||
|
raise ValueError(f"{self.__class__.__name__} requires `model_id` to be specified.")
|
||||||
|
self._requested_device = torch.device(device) if device is not None else None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device(self) -> torch.device:
|
||||||
|
parameters = list(self.parameters())
|
||||||
|
if parameters:
|
||||||
|
return parameters[0].device
|
||||||
|
return self._requested_device or torch.device("cpu")
|
||||||
|
|
||||||
|
def ensure_device(self) -> None:
|
||||||
|
if self._requested_device is not None:
|
||||||
|
self.to(self._requested_device)
|
||||||
|
|
||||||
|
def forward(self, images: torch.Tensor, prompts: Sequence[str]) -> torch.Tensor:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class ClipScorer(BaseRewardScorer):
|
||||||
|
name = "clip"
|
||||||
|
default_model_id = "openai/clip-vit-large-patch14"
|
||||||
|
|
||||||
|
def __init__(self, model_id: Optional[str] = None, device: Optional[torch.device] = None):
|
||||||
|
super().__init__(model_id=model_id, device=device)
|
||||||
|
|
||||||
|
self.model = CLIPModel.from_pretrained(self.model_id)
|
||||||
|
self.tokenizer = CLIPTokenizer.from_pretrained(self.model_id)
|
||||||
|
self.image_processor = CLIPImageProcessor.from_pretrained(self.model_id)
|
||||||
|
if self._requested_device is not None:
|
||||||
|
self.model = self.model.to(self._requested_device)
|
||||||
|
self.model = self.model.to(dtype=torch.float32)
|
||||||
|
self.model.eval()
|
||||||
|
for parameter in self.model.parameters():
|
||||||
|
parameter.requires_grad_(False)
|
||||||
|
self.eval()
|
||||||
|
|
||||||
|
def forward(self, images: torch.Tensor, prompts: Sequence[str]) -> torch.Tensor:
|
||||||
|
device = self.model.device
|
||||||
|
pixel_values = self._preprocess_images(images).to(device=device, dtype=torch.float32)
|
||||||
|
text_inputs = self.tokenizer(list(prompts), padding=True, truncation=True, return_tensors="pt").to(device)
|
||||||
|
|
||||||
|
image_embeds = self.model.get_image_features(pixel_values=pixel_values)
|
||||||
|
text_embeds = self.model.get_text_features(**text_inputs)
|
||||||
|
|
||||||
|
image_embeds = image_embeds / image_embeds.norm(dim=-1, keepdim=True)
|
||||||
|
text_embeds = text_embeds / text_embeds.norm(dim=-1, keepdim=True)
|
||||||
|
return (image_embeds * text_embeds).sum(dim=-1)
|
||||||
|
|
||||||
|
def _preprocess_images(self, images: torch.Tensor) -> torch.Tensor:
|
||||||
|
pixel_values = (images + 1) / 2
|
||||||
|
pixel_values = torch.clamp(pixel_values, 0, 1)
|
||||||
|
|
||||||
|
crop_size = self.image_processor.crop_size
|
||||||
|
if isinstance(crop_size, dict):
|
||||||
|
target_height = crop_size["height"]
|
||||||
|
target_width = crop_size["width"]
|
||||||
|
else:
|
||||||
|
target_height = target_width = crop_size
|
||||||
|
|
||||||
|
pixel_values = F.interpolate(
|
||||||
|
pixel_values, size=(target_height, target_width), mode="bilinear", align_corners=False
|
||||||
|
)
|
||||||
|
|
||||||
|
mean = torch.tensor(
|
||||||
|
self.image_processor.image_mean, device=pixel_values.device, dtype=pixel_values.dtype
|
||||||
|
).view(1, -1, 1, 1)
|
||||||
|
std = torch.tensor(self.image_processor.image_std, device=pixel_values.device, dtype=pixel_values.dtype).view(
|
||||||
|
1, -1, 1, 1
|
||||||
|
)
|
||||||
|
return (pixel_values - mean) / std
|
||||||
|
|
||||||
|
|
||||||
|
class SiglipScorer(BaseRewardScorer):
|
||||||
|
name = "siglip"
|
||||||
|
default_model_id = "google/siglip-so400m-patch14-384"
|
||||||
|
|
||||||
|
def __init__(self, model_id: Optional[str] = None, device: Optional[torch.device] = None):
|
||||||
|
super().__init__(model_id=model_id, device=device)
|
||||||
|
|
||||||
|
self.processor = SiglipProcessor.from_pretrained(self.model_id)
|
||||||
|
self.image_processor = self.processor.image_processor
|
||||||
|
self.text_tokenizer = self.processor.tokenizer
|
||||||
|
self.model = SiglipModel.from_pretrained(self.model_id)
|
||||||
|
if self._requested_device is not None:
|
||||||
|
self.model = self.model.to(self._requested_device)
|
||||||
|
self.model = self.model.to(dtype=torch.float32)
|
||||||
|
self.model.eval()
|
||||||
|
for parameter in self.model.parameters():
|
||||||
|
parameter.requires_grad_(False)
|
||||||
|
self.eval()
|
||||||
|
|
||||||
|
def forward(self, images: torch.Tensor, prompts: Sequence[str]) -> torch.Tensor: # type: ignore[override]
|
||||||
|
device = self.model.device
|
||||||
|
pixel_values = self._preprocess_images(images).to(device=device, dtype=torch.float32)
|
||||||
|
text_inputs = self.text_tokenizer(list(prompts), padding=True, truncation=True, return_tensors="pt").to(device)
|
||||||
|
|
||||||
|
image_embeds = self.model.get_image_features(pixel_values=pixel_values)
|
||||||
|
text_embeds = self.model.get_text_features(**text_inputs)
|
||||||
|
|
||||||
|
image_embeds = image_embeds / image_embeds.norm(dim=-1, keepdim=True)
|
||||||
|
text_embeds = text_embeds / text_embeds.norm(dim=-1, keepdim=True)
|
||||||
|
|
||||||
|
return (image_embeds * text_embeds).sum(dim=-1)
|
||||||
|
|
||||||
|
def _preprocess_images(self, images: torch.Tensor) -> torch.Tensor:
|
||||||
|
pixel_values = (images + 1) / 2
|
||||||
|
pixel_values = torch.clamp(pixel_values, 0, 1)
|
||||||
|
|
||||||
|
size = self.image_processor.size
|
||||||
|
if isinstance(size, dict):
|
||||||
|
target_height = size.get("shortest_edge") or size.get("height") or size.get("width")
|
||||||
|
target_width = size.get("width") or target_height
|
||||||
|
target_height = target_height or target_width
|
||||||
|
else:
|
||||||
|
target_height = target_width = size
|
||||||
|
|
||||||
|
pixel_values = F.interpolate(
|
||||||
|
pixel_values, size=(target_height, target_width), mode="bilinear", align_corners=False
|
||||||
|
)
|
||||||
|
|
||||||
|
mean = torch.tensor(
|
||||||
|
self.image_processor.image_mean, device=pixel_values.device, dtype=pixel_values.dtype
|
||||||
|
).view(1, -1, 1, 1)
|
||||||
|
std = torch.tensor(self.image_processor.image_std, device=pixel_values.device, dtype=pixel_values.dtype).view(
|
||||||
|
1, -1, 1, 1
|
||||||
|
)
|
||||||
|
return (pixel_values - mean) / std
|
||||||
|
|
||||||
|
|
||||||
|
class PlaceholderScorer(BaseRewardScorer):
|
||||||
|
"""
|
||||||
|
Helper scorer that surfaces a friendly error for scorers that require external dependencies.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "placeholder"
|
||||||
|
supports_gradients = False
|
||||||
|
|
||||||
|
def __init__(self, *args: Any, required_package: str, scorer_name: str, **kwargs: Any):
|
||||||
|
self.required_package = required_package
|
||||||
|
self.scorer_name = scorer_name
|
||||||
|
raise ImportError(f"{scorer_name} requires the external package `{required_package}` which is not installed.")
|
||||||
|
|
||||||
|
|
||||||
|
SCORER_REGISTRY: Dict[str, Type[BaseRewardScorer]] = {ClipScorer.name: ClipScorer, SiglipScorer.name: SiglipScorer}
|
||||||
|
|
||||||
|
|
||||||
|
def available_scorers() -> Tuple[str, ...]:
|
||||||
|
return tuple(sorted(SCORER_REGISTRY.keys()))
|
||||||
|
|
||||||
|
|
||||||
|
def build_scorer(
|
||||||
|
name: str,
|
||||||
|
model_id: Optional[str] = None,
|
||||||
|
device: Optional[Union[str, torch.device]] = None,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> BaseRewardScorer:
|
||||||
|
if name not in SCORER_REGISTRY:
|
||||||
|
raise ValueError(f"Unknown scorer `{name}`. Available scorers: {', '.join(available_scorers())}.")
|
||||||
|
scorer_cls = SCORER_REGISTRY[name]
|
||||||
|
device_obj = torch.device(device) if device is not None else None
|
||||||
|
|
||||||
|
scorer = scorer_cls(model_id=model_id, device=device_obj, **kwargs)
|
||||||
|
|
||||||
|
if not scorer.supports_gradients:
|
||||||
|
warnings.warn(
|
||||||
|
f"Scorer `{name}` does not declare gradient support. Adversarial refinement may not work as expected.",
|
||||||
|
UserWarning,
|
||||||
|
)
|
||||||
|
|
||||||
|
return scorer
|
||||||
@@ -1338,7 +1338,7 @@ def main(args):
|
|||||||
batch["pixel_values"] = batch["pixel_values"].to(
|
batch["pixel_values"] = batch["pixel_values"].to(
|
||||||
accelerator.device, non_blocking=True, dtype=vae.dtype
|
accelerator.device, non_blocking=True, dtype=vae.dtype
|
||||||
)
|
)
|
||||||
latents_cache.append(vae.encode(batch["pixel_values"]).latent_dist)
|
latents_cache.append(vae.encode(batch["pixel_values"]).latent_dist)
|
||||||
if train_dataset.custom_instance_prompts:
|
if train_dataset.custom_instance_prompts:
|
||||||
with offload_models(text_encoding_pipeline, device=accelerator.device, offload=args.offload):
|
with offload_models(text_encoding_pipeline, device=accelerator.device, offload=args.offload):
|
||||||
prompt_embeds, prompt_embeds_mask = compute_text_embeddings(
|
prompt_embeds, prompt_embeds_mask = compute_text_embeddings(
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ from dataclasses import dataclass
|
|||||||
from typing import Dict, List, Type, Union
|
from typing import Dict, List, Type, Union
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
import torch.distributed._functional_collectives as funcol
|
|
||||||
|
|
||||||
|
if torch.distributed.is_available():
|
||||||
|
import torch.distributed._functional_collectives as funcol
|
||||||
|
|
||||||
from ..models._modeling_parallel import (
|
from ..models._modeling_parallel import (
|
||||||
ContextParallelConfig,
|
ContextParallelConfig,
|
||||||
|
|||||||
@@ -13,12 +13,12 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
from typing import Any, List, Optional, Tuple, Union
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from ...models import AutoencoderKL
|
from ...pipelines import FluxPipeline
|
||||||
from ...schedulers import FlowMatchEulerDiscreteScheduler
|
from ...schedulers import FlowMatchEulerDiscreteScheduler
|
||||||
from ...utils import logging
|
from ...utils import logging
|
||||||
from ...utils.torch_utils import randn_tensor
|
from ...utils.torch_utils import randn_tensor
|
||||||
@@ -104,48 +104,6 @@ def calculate_shift(
|
|||||||
return mu
|
return mu
|
||||||
|
|
||||||
|
|
||||||
# Adapted from the original implementation.
|
|
||||||
def prepare_latents_img2img(
|
|
||||||
vae, scheduler, image, timestep, batch_size, num_channels_latents, height, width, dtype, device, generator
|
|
||||||
):
|
|
||||||
if isinstance(generator, list) and len(generator) != batch_size:
|
|
||||||
raise ValueError(
|
|
||||||
f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
|
|
||||||
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
|
|
||||||
)
|
|
||||||
|
|
||||||
vae_scale_factor = 2 ** (len(vae.config.block_out_channels) - 1)
|
|
||||||
latent_channels = vae.config.latent_channels
|
|
||||||
|
|
||||||
# VAE applies 8x compression on images but we must also account for packing which requires
|
|
||||||
# latent height and width to be divisible by 2.
|
|
||||||
height = 2 * (int(height) // (vae_scale_factor * 2))
|
|
||||||
width = 2 * (int(width) // (vae_scale_factor * 2))
|
|
||||||
shape = (batch_size, num_channels_latents, height, width)
|
|
||||||
latent_image_ids = _prepare_latent_image_ids(batch_size, height // 2, width // 2, device, dtype)
|
|
||||||
|
|
||||||
image = image.to(device=device, dtype=dtype)
|
|
||||||
if image.shape[1] != latent_channels:
|
|
||||||
image_latents = _encode_vae_image(image=image, generator=generator)
|
|
||||||
else:
|
|
||||||
image_latents = image
|
|
||||||
if batch_size > image_latents.shape[0] and batch_size % image_latents.shape[0] == 0:
|
|
||||||
# expand init_latents for batch_size
|
|
||||||
additional_image_per_prompt = batch_size // image_latents.shape[0]
|
|
||||||
image_latents = torch.cat([image_latents] * additional_image_per_prompt, dim=0)
|
|
||||||
elif batch_size > image_latents.shape[0] and batch_size % image_latents.shape[0] != 0:
|
|
||||||
raise ValueError(
|
|
||||||
f"Cannot duplicate `image` of batch size {image_latents.shape[0]} to {batch_size} text prompts."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
image_latents = torch.cat([image_latents], dim=0)
|
|
||||||
|
|
||||||
noise = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
|
|
||||||
latents = scheduler.scale_noise(image_latents, timestep, noise)
|
|
||||||
latents = _pack_latents(latents, batch_size, num_channels_latents, height, width)
|
|
||||||
return latents, latent_image_ids
|
|
||||||
|
|
||||||
|
|
||||||
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_img2img.retrieve_latents
|
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_img2img.retrieve_latents
|
||||||
def retrieve_latents(
|
def retrieve_latents(
|
||||||
encoder_output: torch.Tensor, generator: Optional[torch.Generator] = None, sample_mode: str = "sample"
|
encoder_output: torch.Tensor, generator: Optional[torch.Generator] = None, sample_mode: str = "sample"
|
||||||
@@ -160,6 +118,7 @@ def retrieve_latents(
|
|||||||
raise AttributeError("Could not access latents of provided encoder_output")
|
raise AttributeError("Could not access latents of provided encoder_output")
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: align this with Qwen patchifier
|
||||||
def _pack_latents(latents, batch_size, num_channels_latents, height, width):
|
def _pack_latents(latents, batch_size, num_channels_latents, height, width):
|
||||||
latents = latents.view(batch_size, num_channels_latents, height // 2, 2, width // 2, 2)
|
latents = latents.view(batch_size, num_channels_latents, height // 2, 2, width // 2, 2)
|
||||||
latents = latents.permute(0, 2, 4, 1, 3, 5)
|
latents = latents.permute(0, 2, 4, 1, 3, 5)
|
||||||
@@ -168,35 +127,6 @@ def _pack_latents(latents, batch_size, num_channels_latents, height, width):
|
|||||||
return latents
|
return latents
|
||||||
|
|
||||||
|
|
||||||
def _prepare_latent_image_ids(batch_size, height, width, device, dtype):
|
|
||||||
latent_image_ids = torch.zeros(height, width, 3)
|
|
||||||
latent_image_ids[..., 1] = latent_image_ids[..., 1] + torch.arange(height)[:, None]
|
|
||||||
latent_image_ids[..., 2] = latent_image_ids[..., 2] + torch.arange(width)[None, :]
|
|
||||||
|
|
||||||
latent_image_id_height, latent_image_id_width, latent_image_id_channels = latent_image_ids.shape
|
|
||||||
|
|
||||||
latent_image_ids = latent_image_ids.reshape(
|
|
||||||
latent_image_id_height * latent_image_id_width, latent_image_id_channels
|
|
||||||
)
|
|
||||||
|
|
||||||
return latent_image_ids.to(device=device, dtype=dtype)
|
|
||||||
|
|
||||||
|
|
||||||
# Cannot use "# Copied from" because it introduces weird indentation errors.
|
|
||||||
def _encode_vae_image(vae, image: torch.Tensor, generator: torch.Generator):
|
|
||||||
if isinstance(generator, list):
|
|
||||||
image_latents = [
|
|
||||||
retrieve_latents(vae.encode(image[i : i + 1]), generator=generator[i]) for i in range(image.shape[0])
|
|
||||||
]
|
|
||||||
image_latents = torch.cat(image_latents, dim=0)
|
|
||||||
else:
|
|
||||||
image_latents = retrieve_latents(vae.encode(image), generator=generator)
|
|
||||||
|
|
||||||
image_latents = (image_latents - vae.config.shift_factor) * vae.config.scaling_factor
|
|
||||||
|
|
||||||
return image_latents
|
|
||||||
|
|
||||||
|
|
||||||
def _get_initial_timesteps_and_optionals(
|
def _get_initial_timesteps_and_optionals(
|
||||||
transformer,
|
transformer,
|
||||||
scheduler,
|
scheduler,
|
||||||
@@ -231,96 +161,6 @@ def _get_initial_timesteps_and_optionals(
|
|||||||
return timesteps, num_inference_steps, sigmas, guidance
|
return timesteps, num_inference_steps, sigmas, guidance
|
||||||
|
|
||||||
|
|
||||||
class FluxInputStep(ModularPipelineBlocks):
|
|
||||||
model_name = "flux"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def description(self) -> str:
|
|
||||||
return (
|
|
||||||
"Input processing step that:\n"
|
|
||||||
" 1. Determines `batch_size` and `dtype` based on `prompt_embeds`\n"
|
|
||||||
" 2. Adjusts input tensor shapes based on `batch_size` (number of prompts) and `num_images_per_prompt`\n\n"
|
|
||||||
"All input tensors are expected to have either batch_size=1 or match the batch_size\n"
|
|
||||||
"of prompt_embeds. The tensors will be duplicated across the batch dimension to\n"
|
|
||||||
"have a final batch_size of batch_size * num_images_per_prompt."
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inputs(self) -> List[InputParam]:
|
|
||||||
return [
|
|
||||||
InputParam("num_images_per_prompt", default=1),
|
|
||||||
InputParam(
|
|
||||||
"prompt_embeds",
|
|
||||||
required=True,
|
|
||||||
kwargs_type="denoiser_input_fields",
|
|
||||||
type_hint=torch.Tensor,
|
|
||||||
description="Pre-generated text embeddings. Can be generated from text_encoder step.",
|
|
||||||
),
|
|
||||||
InputParam(
|
|
||||||
"pooled_prompt_embeds",
|
|
||||||
kwargs_type="denoiser_input_fields",
|
|
||||||
type_hint=torch.Tensor,
|
|
||||||
description="Pre-generated pooled text embeddings. Can be generated from text_encoder step.",
|
|
||||||
),
|
|
||||||
# TODO: support negative embeddings?
|
|
||||||
]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def intermediate_outputs(self) -> List[str]:
|
|
||||||
return [
|
|
||||||
OutputParam(
|
|
||||||
"batch_size",
|
|
||||||
type_hint=int,
|
|
||||||
description="Number of prompts, the final batch size of model inputs should be batch_size * num_images_per_prompt",
|
|
||||||
),
|
|
||||||
OutputParam(
|
|
||||||
"dtype",
|
|
||||||
type_hint=torch.dtype,
|
|
||||||
description="Data type of model tensor inputs (determined by `prompt_embeds`)",
|
|
||||||
),
|
|
||||||
OutputParam(
|
|
||||||
"prompt_embeds",
|
|
||||||
type_hint=torch.Tensor,
|
|
||||||
kwargs_type="denoiser_input_fields",
|
|
||||||
description="text embeddings used to guide the image generation",
|
|
||||||
),
|
|
||||||
OutputParam(
|
|
||||||
"pooled_prompt_embeds",
|
|
||||||
type_hint=torch.Tensor,
|
|
||||||
kwargs_type="denoiser_input_fields",
|
|
||||||
description="pooled text embeddings used to guide the image generation",
|
|
||||||
),
|
|
||||||
# TODO: support negative embeddings?
|
|
||||||
]
|
|
||||||
|
|
||||||
def check_inputs(self, components, block_state):
|
|
||||||
if block_state.prompt_embeds is not None and block_state.pooled_prompt_embeds is not None:
|
|
||||||
if block_state.prompt_embeds.shape[0] != block_state.pooled_prompt_embeds.shape[0]:
|
|
||||||
raise ValueError(
|
|
||||||
"`prompt_embeds` and `pooled_prompt_embeds` must have the same batch size when passed directly, but"
|
|
||||||
f" got: `prompt_embeds` {block_state.prompt_embeds.shape} != `pooled_prompt_embeds`"
|
|
||||||
f" {block_state.pooled_prompt_embeds.shape}."
|
|
||||||
)
|
|
||||||
|
|
||||||
@torch.no_grad()
|
|
||||||
def __call__(self, components: FluxModularPipeline, state: PipelineState) -> PipelineState:
|
|
||||||
# TODO: consider adding negative embeddings?
|
|
||||||
block_state = self.get_block_state(state)
|
|
||||||
self.check_inputs(components, block_state)
|
|
||||||
|
|
||||||
block_state.batch_size = block_state.prompt_embeds.shape[0]
|
|
||||||
block_state.dtype = block_state.prompt_embeds.dtype
|
|
||||||
|
|
||||||
_, seq_len, _ = block_state.prompt_embeds.shape
|
|
||||||
block_state.prompt_embeds = block_state.prompt_embeds.repeat(1, block_state.num_images_per_prompt, 1)
|
|
||||||
block_state.prompt_embeds = block_state.prompt_embeds.view(
|
|
||||||
block_state.batch_size * block_state.num_images_per_prompt, seq_len, -1
|
|
||||||
)
|
|
||||||
self.set_block_state(state, block_state)
|
|
||||||
|
|
||||||
return components, state
|
|
||||||
|
|
||||||
|
|
||||||
class FluxSetTimestepsStep(ModularPipelineBlocks):
|
class FluxSetTimestepsStep(ModularPipelineBlocks):
|
||||||
model_name = "flux"
|
model_name = "flux"
|
||||||
|
|
||||||
@@ -389,6 +229,10 @@ class FluxSetTimestepsStep(ModularPipelineBlocks):
|
|||||||
block_state.sigmas = sigmas
|
block_state.sigmas = sigmas
|
||||||
block_state.guidance = guidance
|
block_state.guidance = guidance
|
||||||
|
|
||||||
|
# We set the index here to remove DtoH sync, helpful especially during compilation.
|
||||||
|
# Check out more details here: https://github.com/huggingface/diffusers/pull/11696
|
||||||
|
components.scheduler.set_begin_index(0)
|
||||||
|
|
||||||
self.set_block_state(state, block_state)
|
self.set_block_state(state, block_state)
|
||||||
return components, state
|
return components, state
|
||||||
|
|
||||||
@@ -432,11 +276,6 @@ class FluxImg2ImgSetTimestepsStep(ModularPipelineBlocks):
|
|||||||
type_hint=int,
|
type_hint=int,
|
||||||
description="The number of denoising steps to perform at inference time",
|
description="The number of denoising steps to perform at inference time",
|
||||||
),
|
),
|
||||||
OutputParam(
|
|
||||||
"latent_timestep",
|
|
||||||
type_hint=torch.Tensor,
|
|
||||||
description="The timestep that represents the initial noise level for image-to-image generation",
|
|
||||||
),
|
|
||||||
OutputParam("guidance", type_hint=torch.Tensor, description="Optional guidance to be used."),
|
OutputParam("guidance", type_hint=torch.Tensor, description="Optional guidance to be used."),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -484,8 +323,6 @@ class FluxImg2ImgSetTimestepsStep(ModularPipelineBlocks):
|
|||||||
block_state.sigmas = sigmas
|
block_state.sigmas = sigmas
|
||||||
block_state.guidance = guidance
|
block_state.guidance = guidance
|
||||||
|
|
||||||
block_state.latent_timestep = timesteps[:1].repeat(batch_size)
|
|
||||||
|
|
||||||
self.set_block_state(state, block_state)
|
self.set_block_state(state, block_state)
|
||||||
return components, state
|
return components, state
|
||||||
|
|
||||||
@@ -524,11 +361,6 @@ class FluxPrepareLatentsStep(ModularPipelineBlocks):
|
|||||||
OutputParam(
|
OutputParam(
|
||||||
"latents", type_hint=torch.Tensor, description="The initial latents to use for the denoising process"
|
"latents", type_hint=torch.Tensor, description="The initial latents to use for the denoising process"
|
||||||
),
|
),
|
||||||
OutputParam(
|
|
||||||
"latent_image_ids",
|
|
||||||
type_hint=torch.Tensor,
|
|
||||||
description="IDs computed from the image sequence needed for RoPE",
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -552,20 +384,13 @@ class FluxPrepareLatentsStep(ModularPipelineBlocks):
|
|||||||
generator,
|
generator,
|
||||||
latents=None,
|
latents=None,
|
||||||
):
|
):
|
||||||
# Couldn't use the `prepare_latents` method directly from Flux because I decided to copy over
|
|
||||||
# the packing methods here. So, for example, `comp._pack_latents()` won't work if we were
|
|
||||||
# to go with the "# Copied from ..." approach. Or maybe there's a way?
|
|
||||||
|
|
||||||
# VAE applies 8x compression on images but we must also account for packing which requires
|
|
||||||
# latent height and width to be divisible by 2.
|
|
||||||
height = 2 * (int(height) // (comp.vae_scale_factor * 2))
|
height = 2 * (int(height) // (comp.vae_scale_factor * 2))
|
||||||
width = 2 * (int(width) // (comp.vae_scale_factor * 2))
|
width = 2 * (int(width) // (comp.vae_scale_factor * 2))
|
||||||
|
|
||||||
shape = (batch_size, num_channels_latents, height, width)
|
shape = (batch_size, num_channels_latents, height, width)
|
||||||
|
|
||||||
if latents is not None:
|
if latents is not None:
|
||||||
latent_image_ids = _prepare_latent_image_ids(batch_size, height // 2, width // 2, device, dtype)
|
return latents.to(device=device, dtype=dtype)
|
||||||
return latents.to(device=device, dtype=dtype), latent_image_ids
|
|
||||||
|
|
||||||
if isinstance(generator, list) and len(generator) != batch_size:
|
if isinstance(generator, list) and len(generator) != batch_size:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -573,12 +398,11 @@ class FluxPrepareLatentsStep(ModularPipelineBlocks):
|
|||||||
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
|
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TODO: move packing latents code to a patchifier
|
||||||
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
|
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
|
||||||
latents = _pack_latents(latents, batch_size, num_channels_latents, height, width)
|
latents = _pack_latents(latents, batch_size, num_channels_latents, height, width)
|
||||||
|
|
||||||
latent_image_ids = _prepare_latent_image_ids(batch_size, height // 2, width // 2, device, dtype)
|
return latents
|
||||||
|
|
||||||
return latents, latent_image_ids
|
|
||||||
|
|
||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
def __call__(self, components: FluxModularPipeline, state: PipelineState) -> PipelineState:
|
def __call__(self, components: FluxModularPipeline, state: PipelineState) -> PipelineState:
|
||||||
@@ -587,12 +411,11 @@ class FluxPrepareLatentsStep(ModularPipelineBlocks):
|
|||||||
block_state.height = block_state.height or components.default_height
|
block_state.height = block_state.height or components.default_height
|
||||||
block_state.width = block_state.width or components.default_width
|
block_state.width = block_state.width or components.default_width
|
||||||
block_state.device = components._execution_device
|
block_state.device = components._execution_device
|
||||||
block_state.dtype = torch.bfloat16 # TODO: okay to hardcode this?
|
|
||||||
block_state.num_channels_latents = components.num_channels_latents
|
block_state.num_channels_latents = components.num_channels_latents
|
||||||
|
|
||||||
self.check_inputs(components, block_state)
|
self.check_inputs(components, block_state)
|
||||||
batch_size = block_state.batch_size * block_state.num_images_per_prompt
|
batch_size = block_state.batch_size * block_state.num_images_per_prompt
|
||||||
block_state.latents, block_state.latent_image_ids = self.prepare_latents(
|
block_state.latents = self.prepare_latents(
|
||||||
components,
|
components,
|
||||||
batch_size,
|
batch_size,
|
||||||
block_state.num_channels_latents,
|
block_state.num_channels_latents,
|
||||||
@@ -612,82 +435,124 @@ class FluxPrepareLatentsStep(ModularPipelineBlocks):
|
|||||||
class FluxImg2ImgPrepareLatentsStep(ModularPipelineBlocks):
|
class FluxImg2ImgPrepareLatentsStep(ModularPipelineBlocks):
|
||||||
model_name = "flux"
|
model_name = "flux"
|
||||||
|
|
||||||
@property
|
|
||||||
def expected_components(self) -> List[ComponentSpec]:
|
|
||||||
return [ComponentSpec("vae", AutoencoderKL), ComponentSpec("scheduler", FlowMatchEulerDiscreteScheduler)]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self) -> str:
|
def description(self) -> str:
|
||||||
return "Step that prepares the latents for the image-to-image generation process"
|
return "Step that adds noise to image latents for image-to-image. Should be run after `set_timesteps`,"
|
||||||
|
" `prepare_latents`. Both noise and image latents should already be patchified."
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def inputs(self) -> List[Tuple[str, Any]]:
|
def expected_components(self) -> List[ComponentSpec]:
|
||||||
|
return [ComponentSpec("scheduler", FlowMatchEulerDiscreteScheduler)]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def inputs(self) -> List[InputParam]:
|
||||||
return [
|
return [
|
||||||
InputParam("height", type_hint=int),
|
|
||||||
InputParam("width", type_hint=int),
|
|
||||||
InputParam("latents", type_hint=Optional[torch.Tensor]),
|
|
||||||
InputParam("num_images_per_prompt", type_hint=int, default=1),
|
|
||||||
InputParam("generator"),
|
|
||||||
InputParam(
|
InputParam(
|
||||||
"image_latents",
|
name="latents",
|
||||||
required=True,
|
required=True,
|
||||||
type_hint=torch.Tensor,
|
type_hint=torch.Tensor,
|
||||||
description="The latents representing the reference image for image-to-image/inpainting generation. Can be generated in vae_encode step.",
|
description="The initial random noised, can be generated in prepare latent step.",
|
||||||
),
|
),
|
||||||
InputParam(
|
InputParam(
|
||||||
"latent_timestep",
|
name="image_latents",
|
||||||
required=True,
|
required=True,
|
||||||
type_hint=torch.Tensor,
|
type_hint=torch.Tensor,
|
||||||
description="The timestep that represents the initial noise level for image-to-image/inpainting generation. Can be generated in set_timesteps step.",
|
description="The image latents to use for the denoising process. Can be generated in vae encoder and packed in input step.",
|
||||||
),
|
),
|
||||||
InputParam(
|
InputParam(
|
||||||
"batch_size",
|
name="timesteps",
|
||||||
required=True,
|
required=True,
|
||||||
type_hint=int,
|
type_hint=torch.Tensor,
|
||||||
description="Number of prompts, the final batch size of model inputs should be batch_size * num_images_per_prompt. Can be generated in input step.",
|
description="The timesteps to use for the denoising process. Can be generated in set_timesteps step.",
|
||||||
),
|
),
|
||||||
InputParam("dtype", required=True, type_hint=torch.dtype, description="The dtype of the model inputs"),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def intermediate_outputs(self) -> List[OutputParam]:
|
def intermediate_outputs(self) -> List[OutputParam]:
|
||||||
return [
|
return [
|
||||||
OutputParam(
|
OutputParam(
|
||||||
"latents", type_hint=torch.Tensor, description="The initial latents to use for the denoising process"
|
name="initial_noise",
|
||||||
),
|
|
||||||
OutputParam(
|
|
||||||
"latent_image_ids",
|
|
||||||
type_hint=torch.Tensor,
|
type_hint=torch.Tensor,
|
||||||
description="IDs computed from the image sequence needed for RoPE",
|
description="The initial random noised used for inpainting denoising.",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_inputs(image_latents, latents):
|
||||||
|
if image_latents.shape[0] != latents.shape[0]:
|
||||||
|
raise ValueError(
|
||||||
|
f"`image_latents` must have have same batch size as `latents`, but got {image_latents.shape[0]} and {latents.shape[0]}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if image_latents.ndim != 3:
|
||||||
|
raise ValueError(f"`image_latents` must have 3 dimensions (patchified), but got {image_latents.ndim}")
|
||||||
|
|
||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
def __call__(self, components: FluxModularPipeline, state: PipelineState) -> PipelineState:
|
def __call__(self, components: FluxModularPipeline, state: PipelineState) -> PipelineState:
|
||||||
block_state = self.get_block_state(state)
|
block_state = self.get_block_state(state)
|
||||||
|
|
||||||
block_state.device = components._execution_device
|
self.check_inputs(image_latents=block_state.image_latents, latents=block_state.latents)
|
||||||
block_state.dtype = torch.bfloat16 # TODO: okay to hardcode this?
|
|
||||||
block_state.num_channels_latents = components.num_channels_latents
|
|
||||||
block_state.dtype = block_state.dtype if block_state.dtype is not None else components.vae.dtype
|
|
||||||
block_state.device = components._execution_device
|
|
||||||
|
|
||||||
# TODO: implement `check_inputs`
|
# prepare latent timestep
|
||||||
batch_size = block_state.batch_size * block_state.num_images_per_prompt
|
latent_timestep = block_state.timesteps[:1].repeat(block_state.latents.shape[0])
|
||||||
if block_state.latents is None:
|
|
||||||
block_state.latents, block_state.latent_image_ids = prepare_latents_img2img(
|
# make copy of initial_noise
|
||||||
components.vae,
|
block_state.initial_noise = block_state.latents
|
||||||
components.scheduler,
|
|
||||||
block_state.image_latents,
|
# scale noise
|
||||||
block_state.latent_timestep,
|
block_state.latents = components.scheduler.scale_noise(
|
||||||
batch_size,
|
block_state.image_latents, latent_timestep, block_state.latents
|
||||||
block_state.num_channels_latents,
|
)
|
||||||
block_state.height,
|
|
||||||
block_state.width,
|
self.set_block_state(state, block_state)
|
||||||
block_state.dtype,
|
|
||||||
block_state.device,
|
return components, state
|
||||||
block_state.generator,
|
|
||||||
)
|
|
||||||
|
class FluxRoPEInputsStep(ModularPipelineBlocks):
|
||||||
|
model_name = "flux"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
return "Step that prepares the RoPE inputs for the denoising process. Should be placed after text encoder and latent preparation steps."
|
||||||
|
|
||||||
|
@property
|
||||||
|
def inputs(self) -> List[InputParam]:
|
||||||
|
return [
|
||||||
|
InputParam(name="height", required=True),
|
||||||
|
InputParam(name="width", required=True),
|
||||||
|
InputParam(name="prompt_embeds"),
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def intermediate_outputs(self) -> List[OutputParam]:
|
||||||
|
return [
|
||||||
|
OutputParam(
|
||||||
|
name="txt_ids",
|
||||||
|
kwargs_type="denoiser_input_fields",
|
||||||
|
type_hint=List[int],
|
||||||
|
description="The sequence lengths of the prompt embeds, used for RoPE calculation.",
|
||||||
|
),
|
||||||
|
OutputParam(
|
||||||
|
name="img_ids",
|
||||||
|
kwargs_type="denoiser_input_fields",
|
||||||
|
type_hint=List[int],
|
||||||
|
description="The sequence lengths of the image latents, used for RoPE calculation.",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
def __call__(self, components: FluxModularPipeline, state: PipelineState) -> PipelineState:
|
||||||
|
block_state = self.get_block_state(state)
|
||||||
|
|
||||||
|
prompt_embeds = block_state.prompt_embeds
|
||||||
|
device, dtype = prompt_embeds.device, prompt_embeds.dtype
|
||||||
|
block_state.txt_ids = torch.zeros(prompt_embeds.shape[1], 3).to(
|
||||||
|
device=prompt_embeds.device, dtype=prompt_embeds.dtype
|
||||||
|
)
|
||||||
|
|
||||||
|
height = 2 * (int(block_state.height) // (components.vae_scale_factor * 2))
|
||||||
|
width = 2 * (int(block_state.width) // (components.vae_scale_factor * 2))
|
||||||
|
block_state.img_ids = FluxPipeline._prepare_latent_image_ids(None, height // 2, width // 2, device, dtype)
|
||||||
|
|
||||||
self.set_block_state(state, block_state)
|
self.set_block_state(state, block_state)
|
||||||
|
|
||||||
|
|||||||
@@ -76,18 +76,17 @@ class FluxLoopDenoiser(ModularPipelineBlocks):
|
|||||||
description="Pooled prompt embeddings",
|
description="Pooled prompt embeddings",
|
||||||
),
|
),
|
||||||
InputParam(
|
InputParam(
|
||||||
"text_ids",
|
"txt_ids",
|
||||||
required=True,
|
required=True,
|
||||||
type_hint=torch.Tensor,
|
type_hint=torch.Tensor,
|
||||||
description="IDs computed from text sequence needed for RoPE",
|
description="IDs computed from text sequence needed for RoPE",
|
||||||
),
|
),
|
||||||
InputParam(
|
InputParam(
|
||||||
"latent_image_ids",
|
"img_ids",
|
||||||
required=True,
|
required=True,
|
||||||
type_hint=torch.Tensor,
|
type_hint=torch.Tensor,
|
||||||
description="IDs computed from image sequence needed for RoPE",
|
description="IDs computed from image sequence needed for RoPE",
|
||||||
),
|
),
|
||||||
# TODO: guidance
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
@@ -101,8 +100,8 @@ class FluxLoopDenoiser(ModularPipelineBlocks):
|
|||||||
encoder_hidden_states=block_state.prompt_embeds,
|
encoder_hidden_states=block_state.prompt_embeds,
|
||||||
pooled_projections=block_state.pooled_prompt_embeds,
|
pooled_projections=block_state.pooled_prompt_embeds,
|
||||||
joint_attention_kwargs=block_state.joint_attention_kwargs,
|
joint_attention_kwargs=block_state.joint_attention_kwargs,
|
||||||
txt_ids=block_state.text_ids,
|
txt_ids=block_state.txt_ids,
|
||||||
img_ids=block_state.latent_image_ids,
|
img_ids=block_state.img_ids,
|
||||||
return_dict=False,
|
return_dict=False,
|
||||||
)[0]
|
)[0]
|
||||||
block_state.noise_pred = noise_pred
|
block_state.noise_pred = noise_pred
|
||||||
@@ -195,9 +194,6 @@ class FluxDenoiseLoopWrapper(LoopSequentialPipelineBlocks):
|
|||||||
block_state.num_warmup_steps = max(
|
block_state.num_warmup_steps = max(
|
||||||
len(block_state.timesteps) - block_state.num_inference_steps * components.scheduler.order, 0
|
len(block_state.timesteps) - block_state.num_inference_steps * components.scheduler.order, 0
|
||||||
)
|
)
|
||||||
# We set the index here to remove DtoH sync, helpful especially during compilation.
|
|
||||||
# Check out more details here: https://github.com/huggingface/diffusers/pull/11696
|
|
||||||
components.scheduler.set_begin_index(0)
|
|
||||||
with self.progress_bar(total=block_state.num_inference_steps) as progress_bar:
|
with self.progress_bar(total=block_state.num_inference_steps) as progress_bar:
|
||||||
for i, t in enumerate(block_state.timesteps):
|
for i, t in enumerate(block_state.timesteps):
|
||||||
components, block_state = self.loop_step(components, block_state, i=i, t=t)
|
components, block_state = self.loop_step(components, block_state, i=i, t=t)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from ...loaders import FluxLoraLoaderMixin, TextualInversionLoaderMixin
|
|||||||
from ...models import AutoencoderKL
|
from ...models import AutoencoderKL
|
||||||
from ...utils import USE_PEFT_BACKEND, is_ftfy_available, logging, scale_lora_layers, unscale_lora_layers
|
from ...utils import USE_PEFT_BACKEND, is_ftfy_available, logging, scale_lora_layers, unscale_lora_layers
|
||||||
from ..modular_pipeline import ModularPipelineBlocks, PipelineState
|
from ..modular_pipeline import ModularPipelineBlocks, PipelineState
|
||||||
from ..modular_pipeline_utils import ComponentSpec, ConfigSpec, InputParam, OutputParam
|
from ..modular_pipeline_utils import ComponentSpec, InputParam, OutputParam
|
||||||
from .modular_pipeline import FluxModularPipeline
|
from .modular_pipeline import FluxModularPipeline
|
||||||
|
|
||||||
|
|
||||||
@@ -67,89 +67,148 @@ def retrieve_latents(
|
|||||||
raise AttributeError("Could not access latents of provided encoder_output")
|
raise AttributeError("Could not access latents of provided encoder_output")
|
||||||
|
|
||||||
|
|
||||||
class FluxVaeEncoderStep(ModularPipelineBlocks):
|
def encode_vae_image(vae: AutoencoderKL, image: torch.Tensor, generator: torch.Generator, sample_mode="sample"):
|
||||||
model_name = "flux"
|
if isinstance(generator, list):
|
||||||
|
image_latents = [
|
||||||
|
retrieve_latents(vae.encode(image[i : i + 1]), generator=generator[i], sample_mode=sample_mode)
|
||||||
|
for i in range(image.shape[0])
|
||||||
|
]
|
||||||
|
image_latents = torch.cat(image_latents, dim=0)
|
||||||
|
else:
|
||||||
|
image_latents = retrieve_latents(vae.encode(image), generator=generator, sample_mode=sample_mode)
|
||||||
|
|
||||||
|
image_latents = (image_latents - vae.config.shift_factor) * vae.config.scaling_factor
|
||||||
|
|
||||||
|
return image_latents
|
||||||
|
|
||||||
|
|
||||||
|
class FluxProcessImagesInputStep(ModularPipelineBlocks):
|
||||||
|
model_name = "Flux"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self) -> str:
|
def description(self) -> str:
|
||||||
return "Vae Encoder step that encode the input image into a latent representation"
|
return "Image Preprocess step. Resizing is needed in Flux Kontext (will be implemented later.)"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def expected_components(self) -> List[ComponentSpec]:
|
def expected_components(self) -> List[ComponentSpec]:
|
||||||
return [
|
return [
|
||||||
ComponentSpec("vae", AutoencoderKL),
|
|
||||||
ComponentSpec(
|
ComponentSpec(
|
||||||
"image_processor",
|
"image_processor",
|
||||||
VaeImageProcessor,
|
VaeImageProcessor,
|
||||||
config=FrozenDict({"vae_scale_factor": 16, "vae_latent_channels": 16}),
|
config=FrozenDict({"vae_scale_factor": 16}),
|
||||||
default_creation_method="from_config",
|
default_creation_method="from_config",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def inputs(self) -> List[InputParam]:
|
def inputs(self) -> List[InputParam]:
|
||||||
|
return [InputParam("resized_image"), InputParam("image"), InputParam("height"), InputParam("width")]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def intermediate_outputs(self) -> List[OutputParam]:
|
||||||
return [
|
return [
|
||||||
InputParam("image", required=True),
|
OutputParam(name="processed_image"),
|
||||||
InputParam("height"),
|
|
||||||
InputParam("width"),
|
|
||||||
InputParam("generator"),
|
|
||||||
InputParam("dtype", type_hint=torch.dtype, description="Data type of model tensor inputs"),
|
|
||||||
InputParam(
|
|
||||||
"preprocess_kwargs",
|
|
||||||
type_hint=Optional[dict],
|
|
||||||
description="A kwargs dictionary that if specified is passed along to the `ImageProcessor` as defined under `self.image_processor` in [diffusers.image_processor.VaeImageProcessor]",
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_inputs(height, width, vae_scale_factor):
|
||||||
|
if height is not None and height % (vae_scale_factor * 2) != 0:
|
||||||
|
raise ValueError(f"Height must be divisible by {vae_scale_factor * 2} but is {height}")
|
||||||
|
|
||||||
|
if width is not None and width % (vae_scale_factor * 2) != 0:
|
||||||
|
raise ValueError(f"Width must be divisible by {vae_scale_factor * 2} but is {width}")
|
||||||
|
|
||||||
|
@torch.no_grad()
|
||||||
|
def __call__(self, components: FluxModularPipeline, state: PipelineState):
|
||||||
|
block_state = self.get_block_state(state)
|
||||||
|
|
||||||
|
if block_state.resized_image is None and block_state.image is None:
|
||||||
|
raise ValueError("`resized_image` and `image` cannot be None at the same time")
|
||||||
|
|
||||||
|
if block_state.resized_image is None:
|
||||||
|
image = block_state.image
|
||||||
|
self.check_inputs(
|
||||||
|
height=block_state.height, width=block_state.width, vae_scale_factor=components.vae_scale_factor
|
||||||
|
)
|
||||||
|
height = block_state.height or components.default_height
|
||||||
|
width = block_state.width or components.default_width
|
||||||
|
else:
|
||||||
|
width, height = block_state.resized_image[0].size
|
||||||
|
image = block_state.resized_image
|
||||||
|
|
||||||
|
block_state.processed_image = components.image_processor.preprocess(image=image, height=height, width=width)
|
||||||
|
|
||||||
|
self.set_block_state(state, block_state)
|
||||||
|
return components, state
|
||||||
|
|
||||||
|
|
||||||
|
class FluxVaeEncoderDynamicStep(ModularPipelineBlocks):
|
||||||
|
model_name = "flux"
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
input_name: str = "processed_image",
|
||||||
|
output_name: str = "image_latents",
|
||||||
|
):
|
||||||
|
"""Initialize a VAE encoder step for converting images to latent representations.
|
||||||
|
|
||||||
|
Both the input and output names are configurable so this block can be configured to process to different image
|
||||||
|
inputs (e.g., "processed_image" -> "image_latents", "processed_control_image" -> "control_image_latents").
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_name (str, optional): Name of the input image tensor. Defaults to "processed_image".
|
||||||
|
Examples: "processed_image" or "processed_control_image"
|
||||||
|
output_name (str, optional): Name of the output latent tensor. Defaults to "image_latents".
|
||||||
|
Examples: "image_latents" or "control_image_latents"
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
# Basic usage with default settings (includes image processor): # FluxImageVaeEncoderDynamicStep()
|
||||||
|
|
||||||
|
# Custom input/output names for control image: # FluxImageVaeEncoderDynamicStep(
|
||||||
|
input_name="processed_control_image", output_name="control_image_latents"
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
self._image_input_name = input_name
|
||||||
|
self._image_latents_output_name = output_name
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
return f"Dynamic VAE Encoder step that converts {self._image_input_name} into latent representations {self._image_latents_output_name}.\n"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def expected_components(self) -> List[ComponentSpec]:
|
||||||
|
components = [ComponentSpec("vae", AutoencoderKL)]
|
||||||
|
return components
|
||||||
|
|
||||||
|
@property
|
||||||
|
def inputs(self) -> List[InputParam]:
|
||||||
|
inputs = [InputParam(self._image_input_name, required=True), InputParam("generator")]
|
||||||
|
return inputs
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def intermediate_outputs(self) -> List[OutputParam]:
|
def intermediate_outputs(self) -> List[OutputParam]:
|
||||||
return [
|
return [
|
||||||
OutputParam(
|
OutputParam(
|
||||||
"image_latents",
|
self._image_latents_output_name,
|
||||||
type_hint=torch.Tensor,
|
type_hint=torch.Tensor,
|
||||||
description="The latents representing the reference image for image-to-image/inpainting generation",
|
description="The latents representing the reference image",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
# Copied from diffusers.pipelines.stable_diffusion_3.pipeline_stable_diffusion_3_inpaint.StableDiffusion3InpaintPipeline._encode_vae_image with self.vae->vae
|
|
||||||
def _encode_vae_image(vae, image: torch.Tensor, generator: torch.Generator):
|
|
||||||
if isinstance(generator, list):
|
|
||||||
image_latents = [
|
|
||||||
retrieve_latents(vae.encode(image[i : i + 1]), generator=generator[i]) for i in range(image.shape[0])
|
|
||||||
]
|
|
||||||
image_latents = torch.cat(image_latents, dim=0)
|
|
||||||
else:
|
|
||||||
image_latents = retrieve_latents(vae.encode(image), generator=generator)
|
|
||||||
|
|
||||||
image_latents = (image_latents - vae.config.shift_factor) * vae.config.scaling_factor
|
|
||||||
|
|
||||||
return image_latents
|
|
||||||
|
|
||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
def __call__(self, components: FluxModularPipeline, state: PipelineState) -> PipelineState:
|
def __call__(self, components: FluxModularPipeline, state: PipelineState) -> PipelineState:
|
||||||
block_state = self.get_block_state(state)
|
block_state = self.get_block_state(state)
|
||||||
block_state.preprocess_kwargs = block_state.preprocess_kwargs or {}
|
|
||||||
block_state.device = components._execution_device
|
|
||||||
block_state.dtype = block_state.dtype if block_state.dtype is not None else components.vae.dtype
|
|
||||||
|
|
||||||
block_state.image = components.image_processor.preprocess(
|
device = components._execution_device
|
||||||
block_state.image, height=block_state.height, width=block_state.width, **block_state.preprocess_kwargs
|
dtype = components.vae.dtype
|
||||||
)
|
|
||||||
block_state.image = block_state.image.to(device=block_state.device, dtype=block_state.dtype)
|
|
||||||
|
|
||||||
block_state.batch_size = block_state.image.shape[0]
|
image = getattr(block_state, self._image_input_name)
|
||||||
|
image = image.to(device=device, dtype=dtype)
|
||||||
|
|
||||||
# if generator is a list, make sure the length of it matches the length of images (both should be batch_size)
|
# Encode image into latents
|
||||||
if isinstance(block_state.generator, list) and len(block_state.generator) != block_state.batch_size:
|
image_latents = encode_vae_image(image=image, vae=components.vae, generator=block_state.generator)
|
||||||
raise ValueError(
|
setattr(block_state, self._image_latents_output_name, image_latents)
|
||||||
f"You have passed a list of generators of length {len(block_state.generator)}, but requested an effective batch"
|
|
||||||
f" size of {block_state.batch_size}. Make sure the batch size matches the length of the generators."
|
|
||||||
)
|
|
||||||
|
|
||||||
block_state.image_latents = self._encode_vae_image(
|
|
||||||
components.vae, image=block_state.image, generator=block_state.generator
|
|
||||||
)
|
|
||||||
|
|
||||||
self.set_block_state(state, block_state)
|
self.set_block_state(state, block_state)
|
||||||
|
|
||||||
@@ -161,7 +220,7 @@ class FluxTextEncoderStep(ModularPipelineBlocks):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self) -> str:
|
def description(self) -> str:
|
||||||
return "Text Encoder step that generate text_embeddings to guide the video generation"
|
return "Text Encoder step that generate text_embeddings to guide the image generation"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def expected_components(self) -> List[ComponentSpec]:
|
def expected_components(self) -> List[ComponentSpec]:
|
||||||
@@ -172,10 +231,6 @@ class FluxTextEncoderStep(ModularPipelineBlocks):
|
|||||||
ComponentSpec("tokenizer_2", T5TokenizerFast),
|
ComponentSpec("tokenizer_2", T5TokenizerFast),
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
|
||||||
def expected_configs(self) -> List[ConfigSpec]:
|
|
||||||
return []
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def inputs(self) -> List[InputParam]:
|
def inputs(self) -> List[InputParam]:
|
||||||
return [
|
return [
|
||||||
@@ -200,12 +255,6 @@ class FluxTextEncoderStep(ModularPipelineBlocks):
|
|||||||
type_hint=torch.Tensor,
|
type_hint=torch.Tensor,
|
||||||
description="pooled text embeddings used to guide the image generation",
|
description="pooled text embeddings used to guide the image generation",
|
||||||
),
|
),
|
||||||
OutputParam(
|
|
||||||
"text_ids",
|
|
||||||
kwargs_type="denoiser_input_fields",
|
|
||||||
type_hint=torch.Tensor,
|
|
||||||
description="ids from the text sequence for RoPE",
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -216,16 +265,10 @@ class FluxTextEncoderStep(ModularPipelineBlocks):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_t5_prompt_embeds(
|
def _get_t5_prompt_embeds(
|
||||||
components,
|
components, prompt: Union[str, List[str]], max_sequence_length: int, device: torch.device
|
||||||
prompt: Union[str, List[str]],
|
|
||||||
num_images_per_prompt: int,
|
|
||||||
max_sequence_length: int,
|
|
||||||
device: torch.device,
|
|
||||||
):
|
):
|
||||||
dtype = components.text_encoder_2.dtype
|
dtype = components.text_encoder_2.dtype
|
||||||
|
|
||||||
prompt = [prompt] if isinstance(prompt, str) else prompt
|
prompt = [prompt] if isinstance(prompt, str) else prompt
|
||||||
batch_size = len(prompt)
|
|
||||||
|
|
||||||
if isinstance(components, TextualInversionLoaderMixin):
|
if isinstance(components, TextualInversionLoaderMixin):
|
||||||
prompt = components.maybe_convert_prompt(prompt, components.tokenizer_2)
|
prompt = components.maybe_convert_prompt(prompt, components.tokenizer_2)
|
||||||
@@ -251,23 +294,11 @@ class FluxTextEncoderStep(ModularPipelineBlocks):
|
|||||||
|
|
||||||
prompt_embeds = components.text_encoder_2(text_input_ids.to(device), output_hidden_states=False)[0]
|
prompt_embeds = components.text_encoder_2(text_input_ids.to(device), output_hidden_states=False)[0]
|
||||||
prompt_embeds = prompt_embeds.to(dtype=dtype, device=device)
|
prompt_embeds = prompt_embeds.to(dtype=dtype, device=device)
|
||||||
_, seq_len, _ = prompt_embeds.shape
|
|
||||||
|
|
||||||
# duplicate text embeddings and attention mask for each generation per prompt, using mps friendly method
|
|
||||||
prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)
|
|
||||||
prompt_embeds = prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)
|
|
||||||
|
|
||||||
return prompt_embeds
|
return prompt_embeds
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_clip_prompt_embeds(
|
def _get_clip_prompt_embeds(components, prompt: Union[str, List[str]], device: torch.device):
|
||||||
components,
|
|
||||||
prompt: Union[str, List[str]],
|
|
||||||
num_images_per_prompt: int,
|
|
||||||
device: torch.device,
|
|
||||||
):
|
|
||||||
prompt = [prompt] if isinstance(prompt, str) else prompt
|
prompt = [prompt] if isinstance(prompt, str) else prompt
|
||||||
batch_size = len(prompt)
|
|
||||||
|
|
||||||
if isinstance(components, TextualInversionLoaderMixin):
|
if isinstance(components, TextualInversionLoaderMixin):
|
||||||
prompt = components.maybe_convert_prompt(prompt, components.tokenizer)
|
prompt = components.maybe_convert_prompt(prompt, components.tokenizer)
|
||||||
@@ -297,10 +328,6 @@ class FluxTextEncoderStep(ModularPipelineBlocks):
|
|||||||
prompt_embeds = prompt_embeds.pooler_output
|
prompt_embeds = prompt_embeds.pooler_output
|
||||||
prompt_embeds = prompt_embeds.to(dtype=components.text_encoder.dtype, device=device)
|
prompt_embeds = prompt_embeds.to(dtype=components.text_encoder.dtype, device=device)
|
||||||
|
|
||||||
# duplicate text embeddings for each generation per prompt, using mps friendly method
|
|
||||||
prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt)
|
|
||||||
prompt_embeds = prompt_embeds.view(batch_size * num_images_per_prompt, -1)
|
|
||||||
|
|
||||||
return prompt_embeds
|
return prompt_embeds
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -309,34 +336,11 @@ class FluxTextEncoderStep(ModularPipelineBlocks):
|
|||||||
prompt: Union[str, List[str]],
|
prompt: Union[str, List[str]],
|
||||||
prompt_2: Union[str, List[str]],
|
prompt_2: Union[str, List[str]],
|
||||||
device: Optional[torch.device] = None,
|
device: Optional[torch.device] = None,
|
||||||
num_images_per_prompt: int = 1,
|
|
||||||
prompt_embeds: Optional[torch.FloatTensor] = None,
|
prompt_embeds: Optional[torch.FloatTensor] = None,
|
||||||
pooled_prompt_embeds: Optional[torch.FloatTensor] = None,
|
pooled_prompt_embeds: Optional[torch.FloatTensor] = None,
|
||||||
max_sequence_length: int = 512,
|
max_sequence_length: int = 512,
|
||||||
lora_scale: Optional[float] = None,
|
lora_scale: Optional[float] = None,
|
||||||
):
|
):
|
||||||
r"""
|
|
||||||
Encodes the prompt into text encoder hidden states.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
prompt (`str` or `List[str]`, *optional*):
|
|
||||||
prompt to be encoded
|
|
||||||
prompt_2 (`str` or `List[str]`, *optional*):
|
|
||||||
The prompt or prompts to be sent to the `tokenizer_2` and `text_encoder_2`. If not defined, `prompt` is
|
|
||||||
used in all text-encoders
|
|
||||||
device: (`torch.device`):
|
|
||||||
torch device
|
|
||||||
num_images_per_prompt (`int`):
|
|
||||||
number of images that should be generated per prompt
|
|
||||||
prompt_embeds (`torch.FloatTensor`, *optional*):
|
|
||||||
Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not
|
|
||||||
provided, text embeddings will be generated from `prompt` input argument.
|
|
||||||
pooled_prompt_embeds (`torch.FloatTensor`, *optional*):
|
|
||||||
Pre-generated pooled text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting.
|
|
||||||
If not provided, pooled text embeddings will be generated from `prompt` input argument.
|
|
||||||
lora_scale (`float`, *optional*):
|
|
||||||
A lora scale that will be applied to all LoRA layers of the text encoder if LoRA layers are loaded.
|
|
||||||
"""
|
|
||||||
device = device or components._execution_device
|
device = device or components._execution_device
|
||||||
|
|
||||||
# set lora scale so that monkey patched LoRA
|
# set lora scale so that monkey patched LoRA
|
||||||
@@ -361,12 +365,10 @@ class FluxTextEncoderStep(ModularPipelineBlocks):
|
|||||||
components,
|
components,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
device=device,
|
device=device,
|
||||||
num_images_per_prompt=num_images_per_prompt,
|
|
||||||
)
|
)
|
||||||
prompt_embeds = FluxTextEncoderStep._get_t5_prompt_embeds(
|
prompt_embeds = FluxTextEncoderStep._get_t5_prompt_embeds(
|
||||||
components,
|
components,
|
||||||
prompt=prompt_2,
|
prompt=prompt_2,
|
||||||
num_images_per_prompt=num_images_per_prompt,
|
|
||||||
max_sequence_length=max_sequence_length,
|
max_sequence_length=max_sequence_length,
|
||||||
device=device,
|
device=device,
|
||||||
)
|
)
|
||||||
@@ -381,10 +383,7 @@ class FluxTextEncoderStep(ModularPipelineBlocks):
|
|||||||
# Retrieve the original scale by scaling back the LoRA layers
|
# Retrieve the original scale by scaling back the LoRA layers
|
||||||
unscale_lora_layers(components.text_encoder_2, lora_scale)
|
unscale_lora_layers(components.text_encoder_2, lora_scale)
|
||||||
|
|
||||||
dtype = components.text_encoder.dtype if components.text_encoder is not None else torch.bfloat16
|
return prompt_embeds, pooled_prompt_embeds
|
||||||
text_ids = torch.zeros(prompt_embeds.shape[1], 3).to(device=device, dtype=dtype)
|
|
||||||
|
|
||||||
return prompt_embeds, pooled_prompt_embeds, text_ids
|
|
||||||
|
|
||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
def __call__(self, components: FluxModularPipeline, state: PipelineState) -> PipelineState:
|
def __call__(self, components: FluxModularPipeline, state: PipelineState) -> PipelineState:
|
||||||
@@ -400,14 +399,13 @@ class FluxTextEncoderStep(ModularPipelineBlocks):
|
|||||||
if block_state.joint_attention_kwargs is not None
|
if block_state.joint_attention_kwargs is not None
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
(block_state.prompt_embeds, block_state.pooled_prompt_embeds, block_state.text_ids) = self.encode_prompt(
|
block_state.prompt_embeds, block_state.pooled_prompt_embeds = self.encode_prompt(
|
||||||
components,
|
components,
|
||||||
prompt=block_state.prompt,
|
prompt=block_state.prompt,
|
||||||
prompt_2=None,
|
prompt_2=None,
|
||||||
prompt_embeds=None,
|
prompt_embeds=None,
|
||||||
pooled_prompt_embeds=None,
|
pooled_prompt_embeds=None,
|
||||||
device=block_state.device,
|
device=block_state.device,
|
||||||
num_images_per_prompt=1, # TODO: hardcoded for now.
|
|
||||||
max_sequence_length=block_state.max_sequence_length,
|
max_sequence_length=block_state.max_sequence_length,
|
||||||
lora_scale=block_state.text_encoder_lora_scale,
|
lora_scale=block_state.text_encoder_lora_scale,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
# Copyright 2025 The HuggingFace Team. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from ...pipelines import FluxPipeline
|
||||||
|
from ..modular_pipeline import ModularPipelineBlocks, PipelineState
|
||||||
|
from ..modular_pipeline_utils import InputParam, OutputParam
|
||||||
|
|
||||||
|
# TODO: consider making these common utilities for modular if they are not pipeline-specific.
|
||||||
|
from ..qwenimage.inputs import calculate_dimension_from_latents, repeat_tensor_to_batch_size
|
||||||
|
from .modular_pipeline import FluxModularPipeline
|
||||||
|
|
||||||
|
|
||||||
|
class FluxTextInputStep(ModularPipelineBlocks):
|
||||||
|
model_name = "flux"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
return (
|
||||||
|
"Text input processing step that standardizes text embeddings for the pipeline.\n"
|
||||||
|
"This step:\n"
|
||||||
|
" 1. Determines `batch_size` and `dtype` based on `prompt_embeds`\n"
|
||||||
|
" 2. Ensures all text embeddings have consistent batch sizes (batch_size * num_images_per_prompt)"
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def inputs(self) -> List[InputParam]:
|
||||||
|
return [
|
||||||
|
InputParam("num_images_per_prompt", default=1),
|
||||||
|
InputParam(
|
||||||
|
"prompt_embeds",
|
||||||
|
required=True,
|
||||||
|
kwargs_type="denoiser_input_fields",
|
||||||
|
type_hint=torch.Tensor,
|
||||||
|
description="Pre-generated text embeddings. Can be generated from text_encoder step.",
|
||||||
|
),
|
||||||
|
InputParam(
|
||||||
|
"pooled_prompt_embeds",
|
||||||
|
kwargs_type="denoiser_input_fields",
|
||||||
|
type_hint=torch.Tensor,
|
||||||
|
description="Pre-generated pooled text embeddings. Can be generated from text_encoder step.",
|
||||||
|
),
|
||||||
|
# TODO: support negative embeddings?
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def intermediate_outputs(self) -> List[str]:
|
||||||
|
return [
|
||||||
|
OutputParam(
|
||||||
|
"batch_size",
|
||||||
|
type_hint=int,
|
||||||
|
description="Number of prompts, the final batch size of model inputs should be batch_size * num_images_per_prompt",
|
||||||
|
),
|
||||||
|
OutputParam(
|
||||||
|
"dtype",
|
||||||
|
type_hint=torch.dtype,
|
||||||
|
description="Data type of model tensor inputs (determined by `prompt_embeds`)",
|
||||||
|
),
|
||||||
|
OutputParam(
|
||||||
|
"prompt_embeds",
|
||||||
|
type_hint=torch.Tensor,
|
||||||
|
kwargs_type="denoiser_input_fields",
|
||||||
|
description="text embeddings used to guide the image generation",
|
||||||
|
),
|
||||||
|
OutputParam(
|
||||||
|
"pooled_prompt_embeds",
|
||||||
|
type_hint=torch.Tensor,
|
||||||
|
kwargs_type="denoiser_input_fields",
|
||||||
|
description="pooled text embeddings used to guide the image generation",
|
||||||
|
),
|
||||||
|
# TODO: support negative embeddings?
|
||||||
|
]
|
||||||
|
|
||||||
|
def check_inputs(self, components, block_state):
|
||||||
|
if block_state.prompt_embeds is not None and block_state.pooled_prompt_embeds is not None:
|
||||||
|
if block_state.prompt_embeds.shape[0] != block_state.pooled_prompt_embeds.shape[0]:
|
||||||
|
raise ValueError(
|
||||||
|
"`prompt_embeds` and `pooled_prompt_embeds` must have the same batch size when passed directly, but"
|
||||||
|
f" got: `prompt_embeds` {block_state.prompt_embeds.shape} != `pooled_prompt_embeds`"
|
||||||
|
f" {block_state.pooled_prompt_embeds.shape}."
|
||||||
|
)
|
||||||
|
|
||||||
|
@torch.no_grad()
|
||||||
|
def __call__(self, components: FluxModularPipeline, state: PipelineState) -> PipelineState:
|
||||||
|
# TODO: consider adding negative embeddings?
|
||||||
|
block_state = self.get_block_state(state)
|
||||||
|
self.check_inputs(components, block_state)
|
||||||
|
|
||||||
|
block_state.batch_size = block_state.prompt_embeds.shape[0]
|
||||||
|
block_state.dtype = block_state.prompt_embeds.dtype
|
||||||
|
|
||||||
|
_, seq_len, _ = block_state.prompt_embeds.shape
|
||||||
|
block_state.prompt_embeds = block_state.prompt_embeds.repeat(1, block_state.num_images_per_prompt, 1)
|
||||||
|
block_state.prompt_embeds = block_state.prompt_embeds.view(
|
||||||
|
block_state.batch_size * block_state.num_images_per_prompt, seq_len, -1
|
||||||
|
)
|
||||||
|
self.set_block_state(state, block_state)
|
||||||
|
|
||||||
|
return components, state
|
||||||
|
|
||||||
|
|
||||||
|
# Adapted from `QwenImageInputsDynamicStep`
|
||||||
|
class FluxInputsDynamicStep(ModularPipelineBlocks):
|
||||||
|
model_name = "flux"
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
image_latent_inputs: List[str] = ["image_latents"],
|
||||||
|
additional_batch_inputs: List[str] = [],
|
||||||
|
):
|
||||||
|
if not isinstance(image_latent_inputs, list):
|
||||||
|
image_latent_inputs = [image_latent_inputs]
|
||||||
|
if not isinstance(additional_batch_inputs, list):
|
||||||
|
additional_batch_inputs = [additional_batch_inputs]
|
||||||
|
|
||||||
|
self._image_latent_inputs = image_latent_inputs
|
||||||
|
self._additional_batch_inputs = additional_batch_inputs
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
# Functionality section
|
||||||
|
summary_section = (
|
||||||
|
"Input processing step that:\n"
|
||||||
|
" 1. For image latent inputs: Updates height/width if None, patchifies latents, and expands batch size\n"
|
||||||
|
" 2. For additional batch inputs: Expands batch dimensions to match final batch size"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Inputs info
|
||||||
|
inputs_info = ""
|
||||||
|
if self._image_latent_inputs or self._additional_batch_inputs:
|
||||||
|
inputs_info = "\n\nConfigured inputs:"
|
||||||
|
if self._image_latent_inputs:
|
||||||
|
inputs_info += f"\n - Image latent inputs: {self._image_latent_inputs}"
|
||||||
|
if self._additional_batch_inputs:
|
||||||
|
inputs_info += f"\n - Additional batch inputs: {self._additional_batch_inputs}"
|
||||||
|
|
||||||
|
# Placement guidance
|
||||||
|
placement_section = "\n\nThis block should be placed after the encoder steps and the text input step."
|
||||||
|
|
||||||
|
return summary_section + inputs_info + placement_section
|
||||||
|
|
||||||
|
@property
|
||||||
|
def inputs(self) -> List[InputParam]:
|
||||||
|
inputs = [
|
||||||
|
InputParam(name="num_images_per_prompt", default=1),
|
||||||
|
InputParam(name="batch_size", required=True),
|
||||||
|
InputParam(name="height"),
|
||||||
|
InputParam(name="width"),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add image latent inputs
|
||||||
|
for image_latent_input_name in self._image_latent_inputs:
|
||||||
|
inputs.append(InputParam(name=image_latent_input_name))
|
||||||
|
|
||||||
|
# Add additional batch inputs
|
||||||
|
for input_name in self._additional_batch_inputs:
|
||||||
|
inputs.append(InputParam(name=input_name))
|
||||||
|
|
||||||
|
return inputs
|
||||||
|
|
||||||
|
@property
|
||||||
|
def intermediate_outputs(self) -> List[OutputParam]:
|
||||||
|
return [
|
||||||
|
OutputParam(name="image_height", type_hint=int, description="The height of the image latents"),
|
||||||
|
OutputParam(name="image_width", type_hint=int, description="The width of the image latents"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def __call__(self, components: FluxModularPipeline, state: PipelineState) -> PipelineState:
|
||||||
|
block_state = self.get_block_state(state)
|
||||||
|
|
||||||
|
# Process image latent inputs (height/width calculation, patchify, and batch expansion)
|
||||||
|
for image_latent_input_name in self._image_latent_inputs:
|
||||||
|
image_latent_tensor = getattr(block_state, image_latent_input_name)
|
||||||
|
if image_latent_tensor is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 1. Calculate height/width from latents
|
||||||
|
height, width = calculate_dimension_from_latents(image_latent_tensor, components.vae_scale_factor)
|
||||||
|
block_state.height = block_state.height or height
|
||||||
|
block_state.width = block_state.width or width
|
||||||
|
|
||||||
|
if not hasattr(block_state, "image_height"):
|
||||||
|
block_state.image_height = height
|
||||||
|
if not hasattr(block_state, "image_width"):
|
||||||
|
block_state.image_width = width
|
||||||
|
|
||||||
|
# 2. Patchify the image latent tensor
|
||||||
|
# TODO: Implement patchifier for Flux.
|
||||||
|
latent_height, latent_width = image_latent_tensor.shape[2:]
|
||||||
|
image_latent_tensor = FluxPipeline._pack_latents(
|
||||||
|
image_latent_tensor, block_state.batch_size, image_latent_tensor.shape[1], latent_height, latent_width
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3. Expand batch size
|
||||||
|
image_latent_tensor = repeat_tensor_to_batch_size(
|
||||||
|
input_name=image_latent_input_name,
|
||||||
|
input_tensor=image_latent_tensor,
|
||||||
|
num_images_per_prompt=block_state.num_images_per_prompt,
|
||||||
|
batch_size=block_state.batch_size,
|
||||||
|
)
|
||||||
|
|
||||||
|
setattr(block_state, image_latent_input_name, image_latent_tensor)
|
||||||
|
|
||||||
|
# Process additional batch inputs (only batch expansion)
|
||||||
|
for input_name in self._additional_batch_inputs:
|
||||||
|
input_tensor = getattr(block_state, input_name)
|
||||||
|
if input_tensor is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Only expand batch size
|
||||||
|
input_tensor = repeat_tensor_to_batch_size(
|
||||||
|
input_name=input_name,
|
||||||
|
input_tensor=input_tensor,
|
||||||
|
num_images_per_prompt=block_state.num_images_per_prompt,
|
||||||
|
batch_size=block_state.batch_size,
|
||||||
|
)
|
||||||
|
|
||||||
|
setattr(block_state, input_name, input_tensor)
|
||||||
|
|
||||||
|
self.set_block_state(state, block_state)
|
||||||
|
return components, state
|
||||||
@@ -18,21 +18,41 @@ from ..modular_pipeline_utils import InsertableDict
|
|||||||
from .before_denoise import (
|
from .before_denoise import (
|
||||||
FluxImg2ImgPrepareLatentsStep,
|
FluxImg2ImgPrepareLatentsStep,
|
||||||
FluxImg2ImgSetTimestepsStep,
|
FluxImg2ImgSetTimestepsStep,
|
||||||
FluxInputStep,
|
|
||||||
FluxPrepareLatentsStep,
|
FluxPrepareLatentsStep,
|
||||||
|
FluxRoPEInputsStep,
|
||||||
FluxSetTimestepsStep,
|
FluxSetTimestepsStep,
|
||||||
)
|
)
|
||||||
from .decoders import FluxDecodeStep
|
from .decoders import FluxDecodeStep
|
||||||
from .denoise import FluxDenoiseStep
|
from .denoise import FluxDenoiseStep
|
||||||
from .encoders import FluxTextEncoderStep, FluxVaeEncoderStep
|
from .encoders import FluxProcessImagesInputStep, FluxTextEncoderStep, FluxVaeEncoderDynamicStep
|
||||||
|
from .inputs import FluxInputsDynamicStep, FluxTextInputStep
|
||||||
|
|
||||||
|
|
||||||
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
|
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
|
||||||
|
|
||||||
|
|
||||||
# vae encoder (run before before_denoise)
|
# vae encoder (run before before_denoise)
|
||||||
|
FluxImg2ImgVaeEncoderBlocks = InsertableDict(
|
||||||
|
[
|
||||||
|
("preprocess", FluxProcessImagesInputStep()),
|
||||||
|
("encode", FluxVaeEncoderDynamicStep()),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FluxImg2ImgVaeEncoderStep(SequentialPipelineBlocks):
|
||||||
|
model_name = "flux"
|
||||||
|
|
||||||
|
block_classes = FluxImg2ImgVaeEncoderBlocks.values()
|
||||||
|
block_names = FluxImg2ImgVaeEncoderBlocks.keys()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
return "Vae encoder step that preprocess andencode the image inputs into their latent representations."
|
||||||
|
|
||||||
|
|
||||||
class FluxAutoVaeEncoderStep(AutoPipelineBlocks):
|
class FluxAutoVaeEncoderStep(AutoPipelineBlocks):
|
||||||
block_classes = [FluxVaeEncoderStep]
|
block_classes = [FluxImg2ImgVaeEncoderStep]
|
||||||
block_names = ["img2img"]
|
block_names = ["img2img"]
|
||||||
block_trigger_inputs = ["image"]
|
block_trigger_inputs = ["image"]
|
||||||
|
|
||||||
@@ -41,45 +61,48 @@ class FluxAutoVaeEncoderStep(AutoPipelineBlocks):
|
|||||||
return (
|
return (
|
||||||
"Vae encoder step that encode the image inputs into their latent representations.\n"
|
"Vae encoder step that encode the image inputs into their latent representations.\n"
|
||||||
+ "This is an auto pipeline block that works for img2img tasks.\n"
|
+ "This is an auto pipeline block that works for img2img tasks.\n"
|
||||||
+ " - `FluxVaeEncoderStep` (img2img) is used when only `image` is provided."
|
+ " - `FluxImg2ImgVaeEncoderStep` (img2img) is used when only `image` is provided."
|
||||||
+ " - if `image` is provided, step will be skipped."
|
+ " - if `image` is not provided, step will be skipped."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# before_denoise: text2img, img2img
|
# before_denoise: text2img
|
||||||
class FluxBeforeDenoiseStep(SequentialPipelineBlocks):
|
FluxBeforeDenoiseBlocks = InsertableDict(
|
||||||
block_classes = [
|
[
|
||||||
FluxInputStep,
|
("prepare_latents", FluxPrepareLatentsStep()),
|
||||||
FluxPrepareLatentsStep,
|
("set_timesteps", FluxSetTimestepsStep()),
|
||||||
FluxSetTimestepsStep,
|
("prepare_rope_inputs", FluxRoPEInputsStep()),
|
||||||
]
|
]
|
||||||
block_names = ["input", "prepare_latents", "set_timesteps"]
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FluxBeforeDenoiseStep(SequentialPipelineBlocks):
|
||||||
|
block_classes = FluxBeforeDenoiseBlocks.values()
|
||||||
|
block_names = FluxBeforeDenoiseBlocks.keys()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
return (
|
return "Before denoise step that prepares the inputs for the denoise step in text-to-image generation."
|
||||||
"Before denoise step that prepare the inputs for the denoise step.\n"
|
|
||||||
+ "This is a sequential pipeline blocks:\n"
|
|
||||||
+ " - `FluxInputStep` is used to adjust the batch size of the model inputs\n"
|
|
||||||
+ " - `FluxPrepareLatentsStep` is used to prepare the latents\n"
|
|
||||||
+ " - `FluxSetTimestepsStep` is used to set the timesteps\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# before_denoise: img2img
|
# before_denoise: img2img
|
||||||
|
FluxImg2ImgBeforeDenoiseBlocks = InsertableDict(
|
||||||
|
[
|
||||||
|
("prepare_latents", FluxPrepareLatentsStep()),
|
||||||
|
("set_timesteps", FluxImg2ImgSetTimestepsStep()),
|
||||||
|
("prepare_img2img_latents", FluxImg2ImgPrepareLatentsStep()),
|
||||||
|
("prepare_rope_inputs", FluxRoPEInputsStep()),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FluxImg2ImgBeforeDenoiseStep(SequentialPipelineBlocks):
|
class FluxImg2ImgBeforeDenoiseStep(SequentialPipelineBlocks):
|
||||||
block_classes = [FluxInputStep, FluxImg2ImgSetTimestepsStep, FluxImg2ImgPrepareLatentsStep]
|
block_classes = FluxImg2ImgBeforeDenoiseBlocks.values()
|
||||||
block_names = ["input", "set_timesteps", "prepare_latents"]
|
block_names = FluxImg2ImgBeforeDenoiseBlocks.keys()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
return (
|
return "Before denoise step that prepare the inputs for the denoise step for img2img task."
|
||||||
"Before denoise step that prepare the inputs for the denoise step for img2img task.\n"
|
|
||||||
+ "This is a sequential pipeline blocks:\n"
|
|
||||||
+ " - `FluxInputStep` is used to adjust the batch size of the model inputs\n"
|
|
||||||
+ " - `FluxImg2ImgSetTimestepsStep` is used to set the timesteps\n"
|
|
||||||
+ " - `FluxImg2ImgPrepareLatentsStep` is used to prepare the latents\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# before_denoise: all task (text2img, img2img)
|
# before_denoise: all task (text2img, img2img)
|
||||||
@@ -113,7 +136,7 @@ class FluxAutoDenoiseStep(AutoPipelineBlocks):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# decode: all task (text2img, img2img, inpainting)
|
# decode: all task (text2img, img2img)
|
||||||
class FluxAutoDecodeStep(AutoPipelineBlocks):
|
class FluxAutoDecodeStep(AutoPipelineBlocks):
|
||||||
block_classes = [FluxDecodeStep]
|
block_classes = [FluxDecodeStep]
|
||||||
block_names = ["non-inpaint"]
|
block_names = ["non-inpaint"]
|
||||||
@@ -124,32 +147,73 @@ class FluxAutoDecodeStep(AutoPipelineBlocks):
|
|||||||
return "Decode step that decode the denoised latents into image outputs.\n - `FluxDecodeStep`"
|
return "Decode step that decode the denoised latents into image outputs.\n - `FluxDecodeStep`"
|
||||||
|
|
||||||
|
|
||||||
|
# inputs: text2image/img2img
|
||||||
|
FluxImg2ImgBlocks = InsertableDict(
|
||||||
|
[("text_inputs", FluxTextInputStep()), ("additional_inputs", FluxInputsDynamicStep())]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FluxImg2ImgInputStep(SequentialPipelineBlocks):
|
||||||
|
model_name = "flux"
|
||||||
|
block_classes = FluxImg2ImgBlocks.values()
|
||||||
|
block_names = FluxImg2ImgBlocks.keys()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self):
|
||||||
|
return "Input step that prepares the inputs for the img2img denoising step. It:\n"
|
||||||
|
" - make sure the text embeddings have consistent batch size as well as the additional inputs (`image_latents`).\n"
|
||||||
|
" - update height/width based `image_latents`, patchify `image_latents`."
|
||||||
|
|
||||||
|
|
||||||
|
class FluxImageAutoInputStep(AutoPipelineBlocks):
|
||||||
|
block_classes = [FluxImg2ImgInputStep, FluxTextInputStep]
|
||||||
|
block_names = ["img2img", "text2image"]
|
||||||
|
block_trigger_inputs = ["image_latents", None]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self):
|
||||||
|
return (
|
||||||
|
"Input step that standardize the inputs for the denoising step, e.g. make sure inputs have consistent batch size, and patchified. \n"
|
||||||
|
" This is an auto pipeline block that works for text2image/img2img tasks.\n"
|
||||||
|
+ " - `FluxImg2ImgInputStep` (img2img) is used when `image_latents` is provided.\n"
|
||||||
|
+ " - `FluxTextInputStep` (text2image) is used when `image_latents` are not provided.\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FluxCoreDenoiseStep(SequentialPipelineBlocks):
|
class FluxCoreDenoiseStep(SequentialPipelineBlocks):
|
||||||
block_classes = [FluxInputStep, FluxAutoBeforeDenoiseStep, FluxAutoDenoiseStep]
|
model_name = "flux"
|
||||||
|
block_classes = [FluxImageAutoInputStep, FluxAutoBeforeDenoiseStep, FluxAutoDenoiseStep]
|
||||||
block_names = ["input", "before_denoise", "denoise"]
|
block_names = ["input", "before_denoise", "denoise"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
return (
|
return (
|
||||||
"Core step that performs the denoising process. \n"
|
"Core step that performs the denoising process. \n"
|
||||||
+ " - `FluxInputStep` (input) standardizes the inputs for the denoising step.\n"
|
+ " - `FluxImageAutoInputStep` (input) standardizes the inputs for the denoising step.\n"
|
||||||
+ " - `FluxAutoBeforeDenoiseStep` (before_denoise) prepares the inputs for the denoising step.\n"
|
+ " - `FluxAutoBeforeDenoiseStep` (before_denoise) prepares the inputs for the denoising step.\n"
|
||||||
+ " - `FluxAutoDenoiseStep` (denoise) iteratively denoises the latents.\n"
|
+ " - `FluxAutoDenoiseStep` (denoise) iteratively denoises the latents.\n"
|
||||||
+ "This step support text-to-image and image-to-image tasks for Flux:\n"
|
+ "This step supports text-to-image and image-to-image tasks for Flux:\n"
|
||||||
+ " - for image-to-image generation, you need to provide `image_latents`\n"
|
+ " - for image-to-image generation, you need to provide `image_latents`\n"
|
||||||
+ " - for text-to-image generation, all you need to provide is prompt embeddings"
|
+ " - for text-to-image generation, all you need to provide is prompt embeddings."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# text2image
|
# Auto blocks (text2image and img2img)
|
||||||
class FluxAutoBlocks(SequentialPipelineBlocks):
|
AUTO_BLOCKS = InsertableDict(
|
||||||
block_classes = [
|
[
|
||||||
FluxTextEncoderStep,
|
("text_encoder", FluxTextEncoderStep()),
|
||||||
FluxAutoVaeEncoderStep,
|
("image_encoder", FluxAutoVaeEncoderStep()),
|
||||||
FluxCoreDenoiseStep,
|
("denoise", FluxCoreDenoiseStep()),
|
||||||
FluxAutoDecodeStep,
|
("decode", FluxDecodeStep()),
|
||||||
]
|
]
|
||||||
block_names = ["text_encoder", "image_encoder", "denoise", "decode"]
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FluxAutoBlocks(SequentialPipelineBlocks):
|
||||||
|
model_name = "flux"
|
||||||
|
|
||||||
|
block_classes = AUTO_BLOCKS.values()
|
||||||
|
block_names = AUTO_BLOCKS.keys()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
@@ -162,35 +226,28 @@ class FluxAutoBlocks(SequentialPipelineBlocks):
|
|||||||
|
|
||||||
TEXT2IMAGE_BLOCKS = InsertableDict(
|
TEXT2IMAGE_BLOCKS = InsertableDict(
|
||||||
[
|
[
|
||||||
("text_encoder", FluxTextEncoderStep),
|
("text_encoder", FluxTextEncoderStep()),
|
||||||
("input", FluxInputStep),
|
("input", FluxTextInputStep()),
|
||||||
("prepare_latents", FluxPrepareLatentsStep),
|
("prepare_latents", FluxPrepareLatentsStep()),
|
||||||
("set_timesteps", FluxSetTimestepsStep),
|
("set_timesteps", FluxSetTimestepsStep()),
|
||||||
("denoise", FluxDenoiseStep),
|
("prepare_rope_inputs", FluxRoPEInputsStep()),
|
||||||
("decode", FluxDecodeStep),
|
("denoise", FluxDenoiseStep()),
|
||||||
|
("decode", FluxDecodeStep()),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
IMAGE2IMAGE_BLOCKS = InsertableDict(
|
IMAGE2IMAGE_BLOCKS = InsertableDict(
|
||||||
[
|
[
|
||||||
("text_encoder", FluxTextEncoderStep),
|
("text_encoder", FluxTextEncoderStep()),
|
||||||
("image_encoder", FluxVaeEncoderStep),
|
("vae_encoder", FluxVaeEncoderDynamicStep()),
|
||||||
("input", FluxInputStep),
|
("input", FluxImg2ImgInputStep()),
|
||||||
("set_timesteps", FluxImg2ImgSetTimestepsStep),
|
("prepare_latents", FluxPrepareLatentsStep()),
|
||||||
("prepare_latents", FluxImg2ImgPrepareLatentsStep),
|
("set_timesteps", FluxImg2ImgSetTimestepsStep()),
|
||||||
("denoise", FluxDenoiseStep),
|
("prepare_img2img_latents", FluxImg2ImgPrepareLatentsStep()),
|
||||||
("decode", FluxDecodeStep),
|
("prepare_rope_inputs", FluxRoPEInputsStep()),
|
||||||
|
("denoise", FluxDenoiseStep()),
|
||||||
|
("decode", FluxDecodeStep()),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
AUTO_BLOCKS = InsertableDict(
|
|
||||||
[
|
|
||||||
("text_encoder", FluxTextEncoderStep),
|
|
||||||
("image_encoder", FluxAutoVaeEncoderStep),
|
|
||||||
("denoise", FluxCoreDenoiseStep),
|
|
||||||
("decode", FluxAutoDecodeStep),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
ALL_BLOCKS = {"text2image": TEXT2IMAGE_BLOCKS, "img2img": IMAGE2IMAGE_BLOCKS, "auto": AUTO_BLOCKS}
|
ALL_BLOCKS = {"text2image": TEXT2IMAGE_BLOCKS, "img2img": IMAGE2IMAGE_BLOCKS, "auto": AUTO_BLOCKS}
|
||||||
|
|||||||
@@ -838,6 +838,9 @@ def load_sub_model(
|
|||||||
else:
|
else:
|
||||||
loading_kwargs["low_cpu_mem_usage"] = False
|
loading_kwargs["low_cpu_mem_usage"] = False
|
||||||
|
|
||||||
|
if is_transformers_model and is_transformers_version(">=", "4.57.0"):
|
||||||
|
loading_kwargs.pop("offload_state_dict")
|
||||||
|
|
||||||
if (
|
if (
|
||||||
quantization_config is not None
|
quantization_config is not None
|
||||||
and isinstance(quantization_config, PipelineQuantizationConfig)
|
and isinstance(quantization_config, PipelineQuantizationConfig)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import operator as op
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from collections import OrderedDict, defaultdict
|
from collections import OrderedDict, defaultdict
|
||||||
|
from functools import lru_cache as cache
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Any, Tuple, Union
|
from typing import Any, Tuple, Union
|
||||||
@@ -673,6 +674,7 @@ def compare_versions(library_or_version: Union[str, Version], operation: str, re
|
|||||||
|
|
||||||
|
|
||||||
# This function was copied from: https://github.com/huggingface/accelerate/blob/874c4967d94badd24f893064cc3bef45f57cadf7/src/accelerate/utils/versions.py#L338
|
# This function was copied from: https://github.com/huggingface/accelerate/blob/874c4967d94badd24f893064cc3bef45f57cadf7/src/accelerate/utils/versions.py#L338
|
||||||
|
@cache
|
||||||
def is_torch_version(operation: str, version: str):
|
def is_torch_version(operation: str, version: str):
|
||||||
"""
|
"""
|
||||||
Compares the current PyTorch version to a given reference with an operation.
|
Compares the current PyTorch version to a given reference with an operation.
|
||||||
@@ -686,6 +688,7 @@ def is_torch_version(operation: str, version: str):
|
|||||||
return compare_versions(parse(_torch_version), operation, version)
|
return compare_versions(parse(_torch_version), operation, version)
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
def is_torch_xla_version(operation: str, version: str):
|
def is_torch_xla_version(operation: str, version: str):
|
||||||
"""
|
"""
|
||||||
Compares the current torch_xla version to a given reference with an operation.
|
Compares the current torch_xla version to a given reference with an operation.
|
||||||
@@ -701,6 +704,7 @@ def is_torch_xla_version(operation: str, version: str):
|
|||||||
return compare_versions(parse(_torch_xla_version), operation, version)
|
return compare_versions(parse(_torch_xla_version), operation, version)
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
def is_transformers_version(operation: str, version: str):
|
def is_transformers_version(operation: str, version: str):
|
||||||
"""
|
"""
|
||||||
Compares the current Transformers version to a given reference with an operation.
|
Compares the current Transformers version to a given reference with an operation.
|
||||||
@@ -716,6 +720,7 @@ def is_transformers_version(operation: str, version: str):
|
|||||||
return compare_versions(parse(_transformers_version), operation, version)
|
return compare_versions(parse(_transformers_version), operation, version)
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
def is_hf_hub_version(operation: str, version: str):
|
def is_hf_hub_version(operation: str, version: str):
|
||||||
"""
|
"""
|
||||||
Compares the current Hugging Face Hub version to a given reference with an operation.
|
Compares the current Hugging Face Hub version to a given reference with an operation.
|
||||||
@@ -731,6 +736,7 @@ def is_hf_hub_version(operation: str, version: str):
|
|||||||
return compare_versions(parse(_hf_hub_version), operation, version)
|
return compare_versions(parse(_hf_hub_version), operation, version)
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
def is_accelerate_version(operation: str, version: str):
|
def is_accelerate_version(operation: str, version: str):
|
||||||
"""
|
"""
|
||||||
Compares the current Accelerate version to a given reference with an operation.
|
Compares the current Accelerate version to a given reference with an operation.
|
||||||
@@ -746,6 +752,7 @@ def is_accelerate_version(operation: str, version: str):
|
|||||||
return compare_versions(parse(_accelerate_version), operation, version)
|
return compare_versions(parse(_accelerate_version), operation, version)
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
def is_peft_version(operation: str, version: str):
|
def is_peft_version(operation: str, version: str):
|
||||||
"""
|
"""
|
||||||
Compares the current PEFT version to a given reference with an operation.
|
Compares the current PEFT version to a given reference with an operation.
|
||||||
@@ -761,6 +768,7 @@ def is_peft_version(operation: str, version: str):
|
|||||||
return compare_versions(parse(_peft_version), operation, version)
|
return compare_versions(parse(_peft_version), operation, version)
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
def is_bitsandbytes_version(operation: str, version: str):
|
def is_bitsandbytes_version(operation: str, version: str):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
@@ -775,6 +783,7 @@ def is_bitsandbytes_version(operation: str, version: str):
|
|||||||
return compare_versions(parse(_bitsandbytes_version), operation, version)
|
return compare_versions(parse(_bitsandbytes_version), operation, version)
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
def is_gguf_version(operation: str, version: str):
|
def is_gguf_version(operation: str, version: str):
|
||||||
"""
|
"""
|
||||||
Compares the current Accelerate version to a given reference with an operation.
|
Compares the current Accelerate version to a given reference with an operation.
|
||||||
@@ -790,6 +799,7 @@ def is_gguf_version(operation: str, version: str):
|
|||||||
return compare_versions(parse(_gguf_version), operation, version)
|
return compare_versions(parse(_gguf_version), operation, version)
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
def is_torchao_version(operation: str, version: str):
|
def is_torchao_version(operation: str, version: str):
|
||||||
"""
|
"""
|
||||||
Compares the current torchao version to a given reference with an operation.
|
Compares the current torchao version to a given reference with an operation.
|
||||||
@@ -805,6 +815,7 @@ def is_torchao_version(operation: str, version: str):
|
|||||||
return compare_versions(parse(_torchao_version), operation, version)
|
return compare_versions(parse(_torchao_version), operation, version)
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
def is_k_diffusion_version(operation: str, version: str):
|
def is_k_diffusion_version(operation: str, version: str):
|
||||||
"""
|
"""
|
||||||
Compares the current k-diffusion version to a given reference with an operation.
|
Compares the current k-diffusion version to a given reference with an operation.
|
||||||
@@ -820,6 +831,7 @@ def is_k_diffusion_version(operation: str, version: str):
|
|||||||
return compare_versions(parse(_k_diffusion_version), operation, version)
|
return compare_versions(parse(_k_diffusion_version), operation, version)
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
def is_optimum_quanto_version(operation: str, version: str):
|
def is_optimum_quanto_version(operation: str, version: str):
|
||||||
"""
|
"""
|
||||||
Compares the current Accelerate version to a given reference with an operation.
|
Compares the current Accelerate version to a given reference with an operation.
|
||||||
@@ -835,6 +847,7 @@ def is_optimum_quanto_version(operation: str, version: str):
|
|||||||
return compare_versions(parse(_optimum_quanto_version), operation, version)
|
return compare_versions(parse(_optimum_quanto_version), operation, version)
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
def is_nvidia_modelopt_version(operation: str, version: str):
|
def is_nvidia_modelopt_version(operation: str, version: str):
|
||||||
"""
|
"""
|
||||||
Compares the current Nvidia ModelOpt version to a given reference with an operation.
|
Compares the current Nvidia ModelOpt version to a given reference with an operation.
|
||||||
@@ -850,6 +863,7 @@ def is_nvidia_modelopt_version(operation: str, version: str):
|
|||||||
return compare_versions(parse(_nvidia_modelopt_version), operation, version)
|
return compare_versions(parse(_nvidia_modelopt_version), operation, version)
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
def is_xformers_version(operation: str, version: str):
|
def is_xformers_version(operation: str, version: str):
|
||||||
"""
|
"""
|
||||||
Compares the current xformers version to a given reference with an operation.
|
Compares the current xformers version to a given reference with an operation.
|
||||||
@@ -865,6 +879,7 @@ def is_xformers_version(operation: str, version: str):
|
|||||||
return compare_versions(parse(_xformers_version), operation, version)
|
return compare_versions(parse(_xformers_version), operation, version)
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
def is_sageattention_version(operation: str, version: str):
|
def is_sageattention_version(operation: str, version: str):
|
||||||
"""
|
"""
|
||||||
Compares the current sageattention version to a given reference with an operation.
|
Compares the current sageattention version to a given reference with an operation.
|
||||||
@@ -880,6 +895,7 @@ def is_sageattention_version(operation: str, version: str):
|
|||||||
return compare_versions(parse(_sageattention_version), operation, version)
|
return compare_versions(parse(_sageattention_version), operation, version)
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
def is_flash_attn_version(operation: str, version: str):
|
def is_flash_attn_version(operation: str, version: str):
|
||||||
"""
|
"""
|
||||||
Compares the current flash-attention version to a given reference with an operation.
|
Compares the current flash-attention version to a given reference with an operation.
|
||||||
|
|||||||
Reference in New Issue
Block a user