rupeshs commited on
Commit
4189926
·
1 Parent(s): f254d4c

docker file added

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +8 -0
  2. Dockerfile +22 -0
  3. LICENSE +21 -0
  4. README.md +2 -5
  5. THIRD-PARTY-LICENSES +143 -0
  6. benchmark-openvino.bat +23 -0
  7. benchmark.bat +23 -0
  8. configs/lcm-lora-models.txt +4 -0
  9. configs/lcm-models.txt +6 -0
  10. configs/openvino-lcm-models.txt +10 -0
  11. configs/stable-diffusion-models.txt +7 -0
  12. controlnet_models/Readme.txt +3 -0
  13. install-mac.sh +36 -0
  14. install.bat +38 -0
  15. install.sh +44 -0
  16. lora_models/Readme.txt +3 -0
  17. models/gguf/clip/readme.txt +1 -0
  18. models/gguf/diffusion/readme.txt +1 -0
  19. models/gguf/t5xxl/readme.txt +1 -0
  20. models/gguf/vae/readme.txt +1 -0
  21. requirements.txt +23 -0
  22. src/__init__.py +0 -0
  23. src/app.py +554 -0
  24. src/app_settings.py +124 -0
  25. src/backend/__init__.py +0 -0
  26. src/backend/annotators/canny_control.py +15 -0
  27. src/backend/annotators/control_interface.py +12 -0
  28. src/backend/annotators/depth_control.py +15 -0
  29. src/backend/annotators/image_control_factory.py +31 -0
  30. src/backend/annotators/lineart_control.py +11 -0
  31. src/backend/annotators/mlsd_control.py +10 -0
  32. src/backend/annotators/normal_control.py +10 -0
  33. src/backend/annotators/pose_control.py +10 -0
  34. src/backend/annotators/shuffle_control.py +10 -0
  35. src/backend/annotators/softedge_control.py +10 -0
  36. src/backend/api/mcp_server.py +100 -0
  37. src/backend/api/models/response.py +18 -0
  38. src/backend/api/web.py +116 -0
  39. src/backend/base64_image.py +21 -0
  40. src/backend/controlnet.py +156 -0
  41. src/backend/device.py +23 -0
  42. src/backend/gguf/gguf_diffusion.py +319 -0
  43. src/backend/gguf/sdcpp_types.py +104 -0
  44. src/backend/image_saver.py +75 -0
  45. src/backend/lcm_text_to_image.py +686 -0
  46. src/backend/lora.py +170 -0
  47. src/backend/models/device.py +9 -0
  48. src/backend/models/gen_images.py +17 -0
  49. src/backend/models/lcmdiffusion_setting.py +77 -0
  50. src/backend/models/upscale.py +9 -0
.gitignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ env
2
+ env_old
3
+ *.bak
4
+ *.pyc
5
+ __pycache__
6
+ results
7
+ # excluding user settings for the GUI frontend
8
+ configs/settings.yaml
Dockerfile ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM ghcr.io/astral-sh/uv:python3.11-trixie-slim
2
+
3
+ RUN apt-get update && apt-get install -y \
4
+ git curl build-essential libgl1 libglib2.0-0 \
5
+ && rm -rf /var/lib/apt/lists/*
6
+
7
+ ENV DEVICE=cpu
8
+
9
+ WORKDIR /app
10
+ COPY . .
11
+
12
+ RUN uv pip install --system --no-cache-dir \
13
+ torch==2.8.0 torchvision==0.23.0 \
14
+ --index-url https://download.pytorch.org/whl/cpu
15
+
16
+ RUN uv pip install --system --no-cache-dir -r requirements.txt
17
+
18
+ EXPOSE 7860
19
+
20
+ ENV GRADIO_SERVER_NAME="0.0.0.0"
21
+
22
+ CMD ["python", "src/frontend/webui/hf_demo.py"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Rupesh Sreeraman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -3,11 +3,8 @@ title: Fastsdcpu
3
  emoji: 📉
4
  colorFrom: blue
5
  colorTo: blue
6
- sdk: gradio
7
- sdk_version: 5.6.0
8
- python_version: "3.11.9"
9
- app_file: app.py
10
- pinned: false
11
  license: mit
12
  ---
13
 
 
3
  emoji: 📉
4
  colorFrom: blue
5
  colorTo: blue
6
+ sdk: docker
7
+ app_port: 7860
 
 
 
8
  license: mit
9
  ---
10
 
THIRD-PARTY-LICENSES ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ stablediffusion.cpp - MIT
2
+
3
+ OpenVINO stablediffusion engine - Apache 2
4
+
5
+ SD Turbo - STABILITY AI NON-COMMERCIAL RESEARCH COMMUNITY LICENSE AGREEMENT
6
+
7
+ MIT License
8
+
9
+ Copyright (c) 2023 leejet
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+
29
+ ERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
30
+
31
+ Definitions.
32
+
33
+ "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
34
+
35
+ "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
36
+
37
+ "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
38
+
39
+ "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
40
+
41
+ "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
42
+
43
+ "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
44
+
45
+ "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
46
+
47
+ "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
50
+
51
+ "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
52
+
53
+ Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
54
+
55
+ Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
56
+
57
+ Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
58
+
59
+ (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
60
+
61
+ (b) You must cause any modified files to carry prominent notices stating that You changed the files; and
62
+
63
+ (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
64
+
65
+ (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
66
+
67
+ You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
68
+
69
+ Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
70
+
71
+ Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
72
+
73
+ Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
74
+
75
+ Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
76
+
77
+ Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
78
+
79
+ END OF TERMS AND CONDITIONS
80
+
81
+ APPENDIX: How to apply the Apache License to your work.
82
+
83
+ To apply the Apache License to your work, attach the following
84
+ boilerplate notice, with the fields enclosed by brackets "[]"
85
+ replaced with your own identifying information. (Don't include
86
+ the brackets!) The text should be enclosed in the appropriate
87
+ comment syntax for the file format. We also recommend that a
88
+ file or class name and description of purpose be included on the
89
+ same "printed page" as the copyright notice for easier
90
+ identification within third-party archives.
91
+ Copyright [yyyy] [name of copyright owner]
92
+
93
+ 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
94
+
95
+ <http://www.apache.org/licenses/LICENSE-2.0>
96
+ 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.
97
+
98
+ STABILITY AI NON-COMMERCIAL RESEARCH COMMUNITY LICENSE AGREEMENT
99
+ Dated: November 28, 2023
100
+
101
+ By using or distributing any portion or element of the Models, Software, Software Products or Derivative Works, you agree to be bound by this Agreement.
102
+
103
+ "Agreement" means this Stable Non-Commercial Research Community License Agreement.
104
+
105
+ “AUP” means the Stability AI Acceptable Use Policy available at <https://stability.ai/use-policy>, as may be updated from time to time.
106
+
107
+ "Derivative Work(s)” means (a) any derivative work of the Software Products as recognized by U.S. copyright laws and (b) any modifications to a Model, and any other model created which is based on or derived from the Model or the Model’s output. For clarity, Derivative Works do not include the output of any Model.
108
+
109
+ “Documentation” means any specifications, manuals, documentation, and other written information provided by Stability AI related to the Software.
110
+
111
+ "Licensee" or "you" means you, or your employer or any other person or entity (if you are entering into this Agreement on such person or entity's behalf), of the age required under applicable laws, rules or regulations to provide legal consent and that has legal authority to bind your employer or such other person or entity if you are entering in this Agreement on their behalf.
112
+
113
+ “Model(s)" means, collectively, Stability AI’s proprietary models and algorithms, including machine-learning models, trained model weights and other elements of the foregoing, made available under this Agreement.
114
+
115
+ “Non-Commercial Uses” means exercising any of the rights granted herein for the purpose of research or non-commercial purposes. Non-Commercial Uses does not include any production use of the Software Products or any Derivative Works.
116
+
117
+ "Stability AI" or "we" means Stability AI Ltd. and its affiliates.
118
+
119
+ "Software" means Stability AI’s proprietary software made available under this Agreement.
120
+
121
+ “Software Products” means the Models, Software and Documentation, individually or in any combination.
122
+
123
+ 1. License Rights and Redistribution.
124
+
125
+ a. Subject to your compliance with this Agreement, the AUP (which is hereby incorporated herein by reference), and the Documentation, Stability AI grants you a non-exclusive, worldwide, non-transferable, non-sublicensable, revocable, royalty free and limited license under Stability AI’s intellectual property or other rights owned or controlled by Stability AI embodied in the Software Products to use, reproduce, distribute, and create Derivative Works of, the Software Products, in each case for Non-Commercial Uses only.
126
+
127
+ b. You may not use the Software Products or Derivative Works to enable third parties to use the Software Products or Derivative Works as part of your hosted service or via your APIs, whether you are adding substantial additional functionality thereto or not. Merely distributing the Software Products or Derivative Works for download online without offering any related service (ex. by distributing the Models on HuggingFace) is not a violation of this subsection. If you wish to use the Software Products or any Derivative Works for commercial or production use or you wish to make the Software Products or any Derivative Works available to third parties via your hosted service or your APIs, contact Stability AI at <https://stability.ai/contact>.
128
+
129
+ c. If you distribute or make the Software Products, or any Derivative Works thereof, available to a third party, the Software Products, Derivative Works, or any portion thereof, respectively, will remain subject to this Agreement and you must (i) provide a copy of this Agreement to such third party, and (ii) retain the following attribution notice within a "Notice" text file distributed as a part of such copies: "This Stability AI Model is licensed under the Stability AI Non-Commercial Research Community License, Copyright (c) Stability AI Ltd. All Rights Reserved.” If you create a Derivative Work of a Software Product, you may add your own attribution notices to the Notice file included with the Software Product, provided that you clearly indicate which attributions apply to the Software Product and you must state in the NOTICE file that you changed the Software Product and how it was modified.
130
+
131
+ 2. Disclaimer of Warranty. UNLESS REQUIRED BY APPLICABLE LAW, THE SOFTWARE PRODUCTS AND ANY OUTPUT AND RESULTS THERE FROM ARE PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE APPROPRIATENESS OF USING OR REDISTRIBUTING THE SOFTWARE PRODUCTS, DERIVATIVE WORKS OR ANY OUTPUT OR RESULTS AND ASSUME ANY RISKS ASSOCIATED WITH YOUR USE OF THE SOFTWARE PRODUCTS, DERIVATIVE WORKS AND ANY OUTPUT AND RESULTS.
132
+
133
+ 3. Limitation of Liability. IN NO EVENT WILL STABILITY AI OR ITS AFFILIATES BE LIABLE UNDER ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, TORT, NEGLIGENCE, PRODUCTS LIABILITY, OR OTHERWISE, ARISING OUT OF THIS AGREEMENT, FOR ANY LOST PROFITS OR ANY DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL, EXEMPLARY OR PUNITIVE DAMAGES, EVEN IF STABILITY AI OR ITS AFFILIATES HAVE BEEN ADVISED OF THE POSSIBILITY OF ANY OF THE FOREGOING.
134
+
135
+ 4. Intellectual Property.
136
+
137
+ a. No trademark licenses are granted under this Agreement, and in connection with the Software Products or Derivative Works, neither Stability AI nor Licensee may use any name or mark owned by or associated with the other or any of its affiliates, except as required for reasonable and customary use in describing and redistributing the Software Products or Derivative Works.
138
+
139
+ b. Subject to Stability AI’s ownership of the Software Products and Derivative Works made by or for Stability AI, with respect to any Derivative Works that are made by you, as between you and Stability AI, you are and will be the owner of such Derivative Works
140
+
141
+ c. If you institute litigation or other proceedings against Stability AI (including a cross-claim or counterclaim in a lawsuit) alleging that the Software Products, Derivative Works or associated outputs or results, or any portion of any of the foregoing, constitutes infringement of intellectual property or other rights owned or licensable by you, then any licenses granted to you under this Agreement shall terminate as of the date such litigation or claim is filed or instituted. You will indemnify and hold harmless Stability AI from and against any claim by any third party arising out of or related to your use or distribution of the Software Products or Derivative Works in violation of this Agreement.
142
+
143
+ 5. Term and Termination. The term of this Agreement will commence upon your acceptance of this Agreement or access to the Software Products and will continue in full force and effect until terminated in accordance with the terms and conditions herein. Stability AI may terminate this Agreement if you are in breach of any term or condition of this Agreement. Upon termination of this Agreement, you shall delete and cease use of any Software Products or Derivative Works. Sections 2-4 shall survive the termination of this Agreement.
benchmark-openvino.bat ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @echo off
2
+ setlocal
3
+
4
+ set "PYTHON_COMMAND=python"
5
+
6
+ call python --version > nul 2>&1
7
+ if %errorlevel% equ 0 (
8
+ echo Python command check :OK
9
+ ) else (
10
+ echo "Error: Python command not found, please install Python (Recommended : Python 3.10 or Python 3.11) and try again"
11
+ pause
12
+ exit /b 1
13
+
14
+ )
15
+
16
+ :check_python_version
17
+ for /f "tokens=2" %%I in ('%PYTHON_COMMAND% --version 2^>^&1') do (
18
+ set "python_version=%%I"
19
+ )
20
+
21
+ echo Python version: %python_version%
22
+
23
+ call "%~dp0env\Scripts\activate.bat" && %PYTHON_COMMAND% src/app.py -b --use_openvino --openvino_lcm_model_id "rupeshs/sd-turbo-openvino"
benchmark.bat ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @echo off
2
+ setlocal
3
+
4
+ set "PYTHON_COMMAND=python"
5
+
6
+ call python --version > nul 2>&1
7
+ if %errorlevel% equ 0 (
8
+ echo Python command check :OK
9
+ ) else (
10
+ echo "Error: Python command not found, please install Python (Recommended : Python 3.10 or Python 3.11) and try again"
11
+ pause
12
+ exit /b 1
13
+
14
+ )
15
+
16
+ :check_python_version
17
+ for /f "tokens=2" %%I in ('%PYTHON_COMMAND% --version 2^>^&1') do (
18
+ set "python_version=%%I"
19
+ )
20
+
21
+ echo Python version: %python_version%
22
+
23
+ call "%~dp0env\Scripts\activate.bat" && %PYTHON_COMMAND% src/app.py -b
configs/lcm-lora-models.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ latent-consistency/lcm-lora-sdv1-5
2
+ latent-consistency/lcm-lora-sdxl
3
+ latent-consistency/lcm-lora-ssd-1b
4
+ rupeshs/hypersd-sd1-5-1-step-lora
configs/lcm-models.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ stabilityai/sd-turbo
2
+ rupeshs/sdxs-512-0.9-orig-vae
3
+ rupeshs/hyper-sd-sdxl-1-step
4
+ rupeshs/SDXL-Lightning-2steps
5
+ stabilityai/sdxl-turbo
6
+ SimianLuo/LCM_Dreamshaper_v7
configs/openvino-lcm-models.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ rupeshs/sd-turbo-openvino
2
+ rupeshs/sdxs-512-0.9-openvino
3
+ rupeshs/hyper-sd-sdxl-1-step-openvino-int8
4
+ rupeshs/SDXL-Lightning-2steps-openvino-int8
5
+ rupeshs/sdxl-turbo-openvino-int8
6
+ rupeshs/LCM-dreamshaper-v7-openvino
7
+ Disty0/LCM_SoteMix
8
+ rupeshs/sd15-lcm-square-openvino-int8
9
+ OpenVINO/FLUX.1-schnell-int4-ov
10
+ rupeshs/sana-sprint-0.6b-openvino-int4
configs/stable-diffusion-models.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ Lykon/dreamshaper-8
2
+ Fictiverse/Stable_Diffusion_PaperCut_Model
3
+ stabilityai/stable-diffusion-xl-base-1.0
4
+ runwayml/stable-diffusion-v1-5
5
+ segmind/SSD-1B
6
+ stablediffusionapi/anything-v5
7
+ prompthero/openjourney-v4
controlnet_models/Readme.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ Place your ControlNet models in this folder.
2
+ You can download controlnet model (.safetensors) from https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/tree/main
3
+ E.g: https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/blob/main/control_v11p_sd15_canny_fp16.safetensors
install-mac.sh ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ echo Starting FastSD CPU env installation...
3
+ set -e
4
+ PYTHON_COMMAND="python3"
5
+
6
+ if ! command -v python3 &>/dev/null; then
7
+ if ! command -v python &>/dev/null; then
8
+ echo "Error: Python not found, please install python 3.8 or higher and try again"
9
+ exit 1
10
+ fi
11
+ fi
12
+
13
+ if command -v python &>/dev/null; then
14
+ PYTHON_COMMAND="python"
15
+ fi
16
+
17
+ echo "Found $PYTHON_COMMAND command"
18
+
19
+ python_version=$($PYTHON_COMMAND --version 2>&1 | awk '{print $2}')
20
+ echo "Python version : $python_version"
21
+
22
+ if ! command -v uv &>/dev/null; then
23
+ echo "Error: uv command not found,please install https://docs.astral.sh/uv/getting-started/installation/#__tabbed_1_1 and try again."
24
+ exit 1
25
+ fi
26
+
27
+ BASEDIR=$(pwd)
28
+
29
+ uv venv --python 3.11.6 "$BASEDIR/env"
30
+ # shellcheck disable=SC1091
31
+ source "$BASEDIR/env/bin/activate"
32
+ uv pip install torch==2.8.0
33
+ uv pip install -r "$BASEDIR/requirements.txt"
34
+ chmod +x "start.sh"
35
+ chmod +x "start-webui.sh"
36
+ read -n1 -r -p "FastSD CPU installation completed,press any key to continue..." key
install.bat ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ @echo off
3
+ setlocal
4
+ echo Starting FastSD CPU env installation...
5
+
6
+ set "PYTHON_COMMAND=python"
7
+
8
+ call python --version > nul 2>&1
9
+ if %errorlevel% equ 0 (
10
+ echo Python command check :OK
11
+ ) else (
12
+ echo "Error: Python command not found,please install Python(Recommended : Python 3.10 or Python 3.11) and try again."
13
+ pause
14
+ exit /b 1
15
+
16
+ )
17
+
18
+ call uv --version > nul 2>&1
19
+ if %errorlevel% equ 0 (
20
+ echo uv command check :OK
21
+ ) else (
22
+ echo "Error: uv command not found,please install https://docs.astral.sh/uv/getting-started/installation/#__tabbed_1_2 and try again."
23
+ pause
24
+ exit /b 1
25
+
26
+ )
27
+ :check_python_version
28
+ for /f "tokens=2" %%I in ('%PYTHON_COMMAND% --version 2^>^&1') do (
29
+ set "python_version=%%I"
30
+ )
31
+
32
+ echo Python version: %python_version%
33
+
34
+ uv venv --python 3.11.6 "%~dp0env"
35
+ call "%~dp0env\Scripts\activate.bat" && uv pip install torch==2.8.0 --index-url https://download.pytorch.org/whl/cpu
36
+ call "%~dp0env\Scripts\activate.bat" && uv pip install -r "%~dp0requirements.txt"
37
+ echo FastSD CPU env installation completed.
38
+ pause
install.sh ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ echo Starting FastSD CPU env installation...
3
+ set -e
4
+ PYTHON_COMMAND="python3"
5
+
6
+ if ! command -v python3 &>/dev/null; then
7
+ if ! command -v python &>/dev/null; then
8
+ echo "Error: Python not found, please install python 3.8 or higher and try again"
9
+ exit 1
10
+ fi
11
+ fi
12
+
13
+ if command -v python &>/dev/null; then
14
+ PYTHON_COMMAND="python"
15
+ fi
16
+
17
+ echo "Found $PYTHON_COMMAND command"
18
+
19
+ python_version=$($PYTHON_COMMAND --version 2>&1 | awk '{print $2}')
20
+ echo "Python version : $python_version"
21
+
22
+ if ! command -v uv &>/dev/null; then
23
+ echo "Error: uv command not found,please install https://docs.astral.sh/uv/getting-started/installation/#__tabbed_1_1 and try again."
24
+ exit 1
25
+ fi
26
+
27
+ BASEDIR=$(pwd)
28
+
29
+ uv venv --python 3.11.6 "$BASEDIR/env"
30
+ # shellcheck disable=SC1091
31
+ source "$BASEDIR/env/bin/activate"
32
+ uv pip install torch==2.8.0 torchvision==0.23.0 --index-url https://download.pytorch.org/whl/cpu
33
+ if [[ "$1" == "--disable-gui" ]]; then
34
+ #! For termux , we don't need Qt based GUI
35
+ packages="$(grep -v "^ *#\|^PyQt5" requirements.txt | grep .)"
36
+ # shellcheck disable=SC2086
37
+ uv pip install $packages
38
+ else
39
+ uv pip install -r "$BASEDIR/requirements.txt"
40
+ fi
41
+
42
+ chmod +x "start.sh"
43
+ chmod +x "start-webui.sh"
44
+ read -n1 -r -p "FastSD CPU installation completed,press any key to continue..." key
lora_models/Readme.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ Place your lora models in this folder.
2
+ You can download lora model (.safetensors/Safetensor) from Civitai (https://civitai.com/) or Hugging Face(https://huggingface.co/)
3
+ E.g: https://civitai.com/models/207984/cutecartoonredmond-15v-cute-cartoon-lora-for-liberteredmond-sd-15?modelVersionId=234192
models/gguf/clip/readme.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Place CLIP model files here"
models/gguf/diffusion/readme.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Place your diffusion gguf model files here
models/gguf/t5xxl/readme.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Place T5-XXL model files here
models/gguf/vae/readme.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Place VAE model files here
requirements.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ accelerate==1.6.0
2
+ diffusers==0.33.0
3
+ transformers==4.48.0
4
+ PyQt5
5
+ Pillow==9.4.0
6
+ openvino==2025.1.0
7
+ optimum-intel==1.23.0
8
+ onnx==1.16.0
9
+ numpy==1.26.4
10
+ onnxruntime==1.17.3
11
+ pydantic
12
+ typing-extensions==4.10.0
13
+ pyyaml==6.0.1
14
+ gradio==5.6.0
15
+ peft==0.6.1
16
+ opencv-python==4.8.1.78
17
+ omegaconf==2.3.0
18
+ controlnet-aux==0.0.7
19
+ mediapipe==0.10.18
20
+ tomesd==0.1.3
21
+ mcp==1.6.0
22
+ fastapi-mcp==0.3.0
23
+ hf_xet
src/__init__.py ADDED
File without changes
src/app.py ADDED
@@ -0,0 +1,554 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from argparse import ArgumentParser
3
+
4
+ from PIL import Image
5
+
6
+ import constants
7
+ from backend.controlnet import controlnet_settings_from_dict
8
+ from backend.device import get_device_name
9
+ from backend.models.gen_images import ImageFormat
10
+ from backend.models.lcmdiffusion_setting import DiffusionTask
11
+ from backend.upscale.tiled_upscale import generate_upscaled_image
12
+ from constants import APP_VERSION, DEVICE
13
+ from frontend.webui.image_variations_ui import generate_image_variations
14
+ from models.interface_types import InterfaceType
15
+ from paths import FastStableDiffusionPaths, ensure_path
16
+ from state import get_context, get_settings
17
+ from utils import show_system_info
18
+
19
+ parser = ArgumentParser(description=f"FAST SD CPU {constants.APP_VERSION}")
20
+ parser.add_argument(
21
+ "-s",
22
+ "--share",
23
+ action="store_true",
24
+ help="Create sharable link(Web UI)",
25
+ required=False,
26
+ )
27
+ group = parser.add_mutually_exclusive_group(required=False)
28
+ group.add_argument(
29
+ "-g",
30
+ "--gui",
31
+ action="store_true",
32
+ help="Start desktop GUI",
33
+ )
34
+ group.add_argument(
35
+ "-w",
36
+ "--webui",
37
+ action="store_true",
38
+ help="Start Web UI",
39
+ )
40
+ group.add_argument(
41
+ "-a",
42
+ "--api",
43
+ action="store_true",
44
+ help="Start Web API server",
45
+ )
46
+ group.add_argument(
47
+ "-m",
48
+ "--mcp",
49
+ action="store_true",
50
+ help="Start MCP(Model Context Protocol) server",
51
+ )
52
+ group.add_argument(
53
+ "-r",
54
+ "--realtime",
55
+ action="store_true",
56
+ help="Start realtime inference UI(experimental)",
57
+ )
58
+ group.add_argument(
59
+ "-v",
60
+ "--version",
61
+ action="store_true",
62
+ help="Version",
63
+ )
64
+
65
+ parser.add_argument(
66
+ "-b",
67
+ "--benchmark",
68
+ action="store_true",
69
+ help="Run inference benchmark on the selected device",
70
+ )
71
+ parser.add_argument(
72
+ "--lcm_model_id",
73
+ type=str,
74
+ help="Model ID or path,Default stabilityai/sd-turbo",
75
+ default="stabilityai/sd-turbo",
76
+ )
77
+ parser.add_argument(
78
+ "--openvino_lcm_model_id",
79
+ type=str,
80
+ help="OpenVINO Model ID or path,Default rupeshs/sd-turbo-openvino",
81
+ default="rupeshs/sd-turbo-openvino",
82
+ )
83
+ parser.add_argument(
84
+ "--prompt",
85
+ type=str,
86
+ help="Describe the image you want to generate",
87
+ default="",
88
+ )
89
+ parser.add_argument(
90
+ "--negative_prompt",
91
+ type=str,
92
+ help="Describe what you want to exclude from the generation",
93
+ default="",
94
+ )
95
+ parser.add_argument(
96
+ "--image_height",
97
+ type=int,
98
+ help="Height of the image",
99
+ default=512,
100
+ )
101
+ parser.add_argument(
102
+ "--image_width",
103
+ type=int,
104
+ help="Width of the image",
105
+ default=512,
106
+ )
107
+ parser.add_argument(
108
+ "--inference_steps",
109
+ type=int,
110
+ help="Number of steps,default : 1",
111
+ default=1,
112
+ )
113
+ parser.add_argument(
114
+ "--guidance_scale",
115
+ type=float,
116
+ help="Guidance scale,default : 1.0",
117
+ default=1.0,
118
+ )
119
+
120
+ parser.add_argument(
121
+ "--number_of_images",
122
+ type=int,
123
+ help="Number of images to generate ,default : 1",
124
+ default=1,
125
+ )
126
+ parser.add_argument(
127
+ "--seed",
128
+ type=int,
129
+ help="Seed,default : -1 (disabled) ",
130
+ default=-1,
131
+ )
132
+ parser.add_argument(
133
+ "--use_openvino",
134
+ action="store_true",
135
+ help="Use OpenVINO model",
136
+ )
137
+
138
+ parser.add_argument(
139
+ "--use_offline_model",
140
+ action="store_true",
141
+ help="Use offline model",
142
+ )
143
+ parser.add_argument(
144
+ "--clip_skip",
145
+ type=int,
146
+ help="CLIP Skip (1-12), default : 1 (disabled) ",
147
+ default=1,
148
+ )
149
+ parser.add_argument(
150
+ "--token_merging",
151
+ type=float,
152
+ help="Token merging scale, 0.0 - 1.0, default : 0.0",
153
+ default=0.0,
154
+ )
155
+
156
+ parser.add_argument(
157
+ "--use_safety_checker",
158
+ action="store_true",
159
+ help="Use safety checker",
160
+ )
161
+ parser.add_argument(
162
+ "--use_lcm_lora",
163
+ action="store_true",
164
+ help="Use LCM-LoRA",
165
+ )
166
+ parser.add_argument(
167
+ "--base_model_id",
168
+ type=str,
169
+ help="LCM LoRA base model ID,Default Lykon/dreamshaper-8",
170
+ default="Lykon/dreamshaper-8",
171
+ )
172
+ parser.add_argument(
173
+ "--lcm_lora_id",
174
+ type=str,
175
+ help="LCM LoRA model ID,Default latent-consistency/lcm-lora-sdv1-5",
176
+ default="latent-consistency/lcm-lora-sdv1-5",
177
+ )
178
+ parser.add_argument(
179
+ "-i",
180
+ "--interactive",
181
+ action="store_true",
182
+ help="Interactive CLI mode",
183
+ )
184
+ parser.add_argument(
185
+ "-t",
186
+ "--use_tiny_auto_encoder",
187
+ action="store_true",
188
+ help="Use Tiny AutoEncoder for TAESD/TAESDXL/TAEF1",
189
+ )
190
+ parser.add_argument(
191
+ "-f",
192
+ "--file",
193
+ type=str,
194
+ help="Input image for img2img mode",
195
+ default="",
196
+ )
197
+ parser.add_argument(
198
+ "--img2img",
199
+ action="store_true",
200
+ help="img2img mode; requires input file via -f argument",
201
+ )
202
+ parser.add_argument(
203
+ "--batch_count",
204
+ type=int,
205
+ help="Number of sequential generations",
206
+ default=1,
207
+ )
208
+ parser.add_argument(
209
+ "--strength",
210
+ type=float,
211
+ help="Denoising strength for img2img and Image variations",
212
+ default=0.3,
213
+ )
214
+ parser.add_argument(
215
+ "--sdupscale",
216
+ action="store_true",
217
+ help="Tiled SD upscale,works only for the resolution 512x512,(2x upscale)",
218
+ )
219
+ parser.add_argument(
220
+ "--upscale",
221
+ action="store_true",
222
+ help="EDSR SD upscale ",
223
+ )
224
+ parser.add_argument(
225
+ "--custom_settings",
226
+ type=str,
227
+ help="JSON file containing custom generation settings",
228
+ default=None,
229
+ )
230
+ parser.add_argument(
231
+ "--usejpeg",
232
+ action="store_true",
233
+ help="Images will be saved as JPEG format",
234
+ )
235
+ parser.add_argument(
236
+ "--noimagesave",
237
+ action="store_true",
238
+ help="Disable image saving",
239
+ )
240
+ parser.add_argument(
241
+ "--imagequality", type=int, help="Output image quality [0 to 100]", default=90
242
+ )
243
+ parser.add_argument(
244
+ "--lora",
245
+ type=str,
246
+ help="LoRA model full path e.g D:\lora_models\CuteCartoon15V-LiberteRedmodModel-Cartoon-CuteCartoonAF.safetensors",
247
+ default=None,
248
+ )
249
+ parser.add_argument(
250
+ "--lora_weight",
251
+ type=float,
252
+ help="LoRA adapter weight [0 to 1.0]",
253
+ default=0.5,
254
+ )
255
+ parser.add_argument(
256
+ "--port",
257
+ type=int,
258
+ help="Web server port",
259
+ default=8000,
260
+ )
261
+
262
+ args = parser.parse_args()
263
+
264
+ if args.version:
265
+ print(APP_VERSION)
266
+ exit()
267
+
268
+ # parser.print_help()
269
+ print("FastSD CPU - ", APP_VERSION)
270
+ show_system_info()
271
+ print(f"Using device : {constants.DEVICE}")
272
+
273
+
274
+ if args.webui:
275
+ app_settings = get_settings()
276
+ else:
277
+ app_settings = get_settings()
278
+
279
+ print(f"Output path : {app_settings.settings.generated_images.path}")
280
+ ensure_path(app_settings.settings.generated_images.path)
281
+
282
+ print(f"Found {len(app_settings.lcm_models)} LCM models in config/lcm-models.txt")
283
+ print(
284
+ f"Found {len(app_settings.stable_diffsuion_models)} stable diffusion models in config/stable-diffusion-models.txt"
285
+ )
286
+ print(
287
+ f"Found {len(app_settings.lcm_lora_models)} LCM-LoRA models in config/lcm-lora-models.txt"
288
+ )
289
+ print(
290
+ f"Found {len(app_settings.openvino_lcm_models)} OpenVINO LCM models in config/openvino-lcm-models.txt"
291
+ )
292
+
293
+ if args.noimagesave:
294
+ app_settings.settings.generated_images.save_image = False
295
+ else:
296
+ app_settings.settings.generated_images.save_image = True
297
+
298
+ app_settings.settings.generated_images.save_image_quality = args.imagequality
299
+
300
+ if not args.realtime:
301
+ # To minimize realtime mode dependencies
302
+ from backend.upscale.upscaler import upscale_image
303
+ from frontend.cli_interactive import interactive_mode
304
+
305
+ if args.gui:
306
+ from frontend.gui.ui import start_gui
307
+
308
+ print("Starting desktop GUI mode(Qt)")
309
+ start_gui(
310
+ [],
311
+ app_settings,
312
+ )
313
+ elif args.webui:
314
+ from frontend.webui.ui import start_webui
315
+
316
+ print("Starting web UI mode")
317
+ start_webui(
318
+ args.share,
319
+ )
320
+ elif args.realtime:
321
+ from frontend.webui.realtime_ui import start_realtime_text_to_image
322
+
323
+ print("Starting realtime text to image(EXPERIMENTAL)")
324
+ start_realtime_text_to_image(args.share)
325
+ elif args.api:
326
+ from backend.api.web import start_web_server
327
+
328
+ start_web_server(args.port)
329
+ elif args.mcp:
330
+ from backend.api.mcp_server import start_mcp_server
331
+
332
+ start_mcp_server(args.port)
333
+ else:
334
+ context = get_context(InterfaceType.CLI)
335
+ config = app_settings.settings
336
+
337
+ if args.use_openvino:
338
+ config.lcm_diffusion_setting.openvino_lcm_model_id = args.openvino_lcm_model_id
339
+ else:
340
+ config.lcm_diffusion_setting.lcm_model_id = args.lcm_model_id
341
+
342
+ config.lcm_diffusion_setting.prompt = args.prompt
343
+ config.lcm_diffusion_setting.negative_prompt = args.negative_prompt
344
+ config.lcm_diffusion_setting.image_height = args.image_height
345
+ config.lcm_diffusion_setting.image_width = args.image_width
346
+ config.lcm_diffusion_setting.guidance_scale = args.guidance_scale
347
+ config.lcm_diffusion_setting.number_of_images = args.number_of_images
348
+ config.lcm_diffusion_setting.inference_steps = args.inference_steps
349
+ config.lcm_diffusion_setting.strength = args.strength
350
+ config.lcm_diffusion_setting.seed = args.seed
351
+ config.lcm_diffusion_setting.use_openvino = args.use_openvino
352
+ config.lcm_diffusion_setting.use_tiny_auto_encoder = args.use_tiny_auto_encoder
353
+ config.lcm_diffusion_setting.use_lcm_lora = args.use_lcm_lora
354
+ config.lcm_diffusion_setting.lcm_lora.base_model_id = args.base_model_id
355
+ config.lcm_diffusion_setting.lcm_lora.lcm_lora_id = args.lcm_lora_id
356
+ config.lcm_diffusion_setting.diffusion_task = DiffusionTask.text_to_image.value
357
+ config.lcm_diffusion_setting.lora.enabled = False
358
+ config.lcm_diffusion_setting.lora.path = args.lora
359
+ config.lcm_diffusion_setting.lora.weight = args.lora_weight
360
+ config.lcm_diffusion_setting.lora.fuse = False
361
+ if config.lcm_diffusion_setting.lora.path:
362
+ config.lcm_diffusion_setting.lora.enabled = True
363
+ if args.usejpeg:
364
+ config.generated_images.format = ImageFormat.JPEG.value.upper()
365
+ if args.seed > -1:
366
+ config.lcm_diffusion_setting.use_seed = True
367
+ else:
368
+ config.lcm_diffusion_setting.use_seed = False
369
+ config.lcm_diffusion_setting.use_offline_model = args.use_offline_model
370
+ config.lcm_diffusion_setting.clip_skip = args.clip_skip
371
+ config.lcm_diffusion_setting.token_merging = args.token_merging
372
+ config.lcm_diffusion_setting.use_safety_checker = args.use_safety_checker
373
+
374
+ # Read custom settings from JSON file
375
+ custom_settings = {}
376
+ if args.custom_settings:
377
+ with open(args.custom_settings) as f:
378
+ custom_settings = json.load(f)
379
+
380
+ # Basic ControlNet settings; if ControlNet is enabled, an image is
381
+ # required even in txt2img mode
382
+ config.lcm_diffusion_setting.controlnet = None
383
+ controlnet_settings_from_dict(
384
+ config.lcm_diffusion_setting,
385
+ custom_settings,
386
+ )
387
+
388
+ # Interactive mode
389
+ if args.interactive:
390
+ # wrapper(interactive_mode, config, context)
391
+ config.lcm_diffusion_setting.lora.fuse = False
392
+ interactive_mode(config, context)
393
+
394
+ # Start of non-interactive CLI image generation
395
+ if args.img2img and args.file != "":
396
+ config.lcm_diffusion_setting.init_image = Image.open(args.file)
397
+ config.lcm_diffusion_setting.diffusion_task = DiffusionTask.image_to_image.value
398
+ elif args.img2img and args.file == "":
399
+ print("Error : You need to specify a file in img2img mode")
400
+ exit()
401
+ elif args.upscale and args.file == "" and args.custom_settings == None:
402
+ print("Error : You need to specify a file in SD upscale mode")
403
+ exit()
404
+ elif (
405
+ args.prompt == ""
406
+ and args.file == ""
407
+ and args.custom_settings == None
408
+ and not args.benchmark
409
+ ):
410
+ print("Error : You need to provide a prompt")
411
+ exit()
412
+
413
+ if args.upscale:
414
+ # image = Image.open(args.file)
415
+ output_path = FastStableDiffusionPaths.get_upscale_filepath(
416
+ args.file,
417
+ 2,
418
+ config.generated_images.format,
419
+ )
420
+ result = upscale_image(
421
+ context,
422
+ args.file,
423
+ output_path,
424
+ 2,
425
+ )
426
+ # Perform Tiled SD upscale (EXPERIMENTAL)
427
+ elif args.sdupscale:
428
+ if args.use_openvino:
429
+ config.lcm_diffusion_setting.strength = 0.3
430
+ upscale_settings = None
431
+ if custom_settings != {}:
432
+ upscale_settings = custom_settings
433
+ filepath = args.file
434
+ output_format = config.generated_images.format
435
+ if upscale_settings:
436
+ filepath = upscale_settings["source_file"]
437
+ output_format = upscale_settings["output_format"].upper()
438
+ output_path = FastStableDiffusionPaths.get_upscale_filepath(
439
+ filepath,
440
+ 2,
441
+ output_format,
442
+ )
443
+
444
+ generate_upscaled_image(
445
+ config,
446
+ filepath,
447
+ config.lcm_diffusion_setting.strength,
448
+ upscale_settings=upscale_settings,
449
+ context=context,
450
+ tile_overlap=32 if config.lcm_diffusion_setting.use_openvino else 16,
451
+ output_path=output_path,
452
+ image_format=output_format,
453
+ )
454
+ exit()
455
+ # If img2img argument is set and prompt is empty, use image variations mode
456
+ elif args.img2img and args.prompt == "":
457
+ for i in range(0, args.batch_count):
458
+ generate_image_variations(
459
+ config.lcm_diffusion_setting.init_image, args.strength
460
+ )
461
+ else:
462
+ if args.benchmark:
463
+ print("Initializing benchmark...")
464
+ bench_lcm_setting = config.lcm_diffusion_setting
465
+ bench_lcm_setting.prompt = "a cat"
466
+ bench_lcm_setting.use_tiny_auto_encoder = False
467
+ context.generate_text_to_image(
468
+ settings=config,
469
+ device=DEVICE,
470
+ )
471
+
472
+ latencies = []
473
+
474
+ print("Starting benchmark please wait...")
475
+ for _ in range(3):
476
+ context.generate_text_to_image(
477
+ settings=config,
478
+ device=DEVICE,
479
+ )
480
+ latencies.append(context.latency)
481
+
482
+ avg_latency = sum(latencies) / 3
483
+
484
+ bench_lcm_setting.use_tiny_auto_encoder = True
485
+
486
+ context.generate_text_to_image(
487
+ settings=config,
488
+ device=DEVICE,
489
+ )
490
+ latencies = []
491
+ for _ in range(3):
492
+ context.generate_text_to_image(
493
+ settings=config,
494
+ device=DEVICE,
495
+ )
496
+ latencies.append(context.latency)
497
+
498
+ avg_latency_taesd = sum(latencies) / 3
499
+
500
+ benchmark_name = ""
501
+
502
+ if config.lcm_diffusion_setting.use_openvino:
503
+ benchmark_name = "OpenVINO"
504
+ else:
505
+ benchmark_name = "PyTorch"
506
+
507
+ bench_model_id = ""
508
+ if bench_lcm_setting.use_openvino:
509
+ bench_model_id = bench_lcm_setting.openvino_lcm_model_id
510
+ elif bench_lcm_setting.use_lcm_lora:
511
+ bench_model_id = bench_lcm_setting.lcm_lora.base_model_id
512
+ else:
513
+ bench_model_id = bench_lcm_setting.lcm_model_id
514
+
515
+ benchmark_result = [
516
+ ["Device", f"{DEVICE.upper()},{get_device_name()}"],
517
+ ["Stable Diffusion Model", bench_model_id],
518
+ [
519
+ "Image Size ",
520
+ f"{bench_lcm_setting.image_width}x{bench_lcm_setting.image_height}",
521
+ ],
522
+ [
523
+ "Inference Steps",
524
+ f"{bench_lcm_setting.inference_steps}",
525
+ ],
526
+ [
527
+ "Benchmark Passes",
528
+ 3,
529
+ ],
530
+ [
531
+ "Average Latency",
532
+ f"{round(avg_latency, 3)} sec",
533
+ ],
534
+ [
535
+ "Average Latency(TAESD* enabled)",
536
+ f"{round(avg_latency_taesd, 3)} sec",
537
+ ],
538
+ ]
539
+ print()
540
+ print(
541
+ f" FastSD Benchmark - {benchmark_name:8} "
542
+ )
543
+ print(f"-" * 80)
544
+ for benchmark in benchmark_result:
545
+ print(f"{benchmark[0]:35} - {benchmark[1]}")
546
+ print(f"-" * 80)
547
+ print("*TAESD - Tiny AutoEncoder for Stable Diffusion")
548
+
549
+ else:
550
+ for i in range(0, args.batch_count):
551
+ context.generate_text_to_image(
552
+ settings=config,
553
+ device=DEVICE,
554
+ )
src/app_settings.py ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from copy import deepcopy
2
+ from os import makedirs, path
3
+
4
+ import yaml
5
+ from constants import (
6
+ LCM_LORA_MODELS_FILE,
7
+ LCM_MODELS_FILE,
8
+ OPENVINO_LCM_MODELS_FILE,
9
+ SD_MODELS_FILE,
10
+ )
11
+ from paths import FastStableDiffusionPaths, join_paths
12
+ from utils import get_files_in_dir, get_models_from_text_file
13
+
14
+ from models.settings import Settings
15
+
16
+
17
+ class AppSettings:
18
+ def __init__(self):
19
+ self.config_path = FastStableDiffusionPaths().get_app_settings_path()
20
+ self._stable_diffsuion_models = get_models_from_text_file(
21
+ FastStableDiffusionPaths().get_models_config_path(SD_MODELS_FILE)
22
+ )
23
+ self._lcm_lora_models = get_models_from_text_file(
24
+ FastStableDiffusionPaths().get_models_config_path(LCM_LORA_MODELS_FILE)
25
+ )
26
+ self._openvino_lcm_models = get_models_from_text_file(
27
+ FastStableDiffusionPaths().get_models_config_path(OPENVINO_LCM_MODELS_FILE)
28
+ )
29
+ self._lcm_models = get_models_from_text_file(
30
+ FastStableDiffusionPaths().get_models_config_path(LCM_MODELS_FILE)
31
+ )
32
+ self._gguf_diffusion_models = get_files_in_dir(
33
+ join_paths(FastStableDiffusionPaths().get_gguf_models_path(), "diffusion")
34
+ )
35
+ self._gguf_clip_models = get_files_in_dir(
36
+ join_paths(FastStableDiffusionPaths().get_gguf_models_path(), "clip")
37
+ )
38
+ self._gguf_vae_models = get_files_in_dir(
39
+ join_paths(FastStableDiffusionPaths().get_gguf_models_path(), "vae")
40
+ )
41
+ self._gguf_t5xxl_models = get_files_in_dir(
42
+ join_paths(FastStableDiffusionPaths().get_gguf_models_path(), "t5xxl")
43
+ )
44
+ self._config = None
45
+
46
+ @property
47
+ def settings(self):
48
+ return self._config
49
+
50
+ @property
51
+ def stable_diffsuion_models(self):
52
+ return self._stable_diffsuion_models
53
+
54
+ @property
55
+ def openvino_lcm_models(self):
56
+ return self._openvino_lcm_models
57
+
58
+ @property
59
+ def lcm_models(self):
60
+ return self._lcm_models
61
+
62
+ @property
63
+ def lcm_lora_models(self):
64
+ return self._lcm_lora_models
65
+
66
+ @property
67
+ def gguf_diffusion_models(self):
68
+ return self._gguf_diffusion_models
69
+
70
+ @property
71
+ def gguf_clip_models(self):
72
+ return self._gguf_clip_models
73
+
74
+ @property
75
+ def gguf_vae_models(self):
76
+ return self._gguf_vae_models
77
+
78
+ @property
79
+ def gguf_t5xxl_models(self):
80
+ return self._gguf_t5xxl_models
81
+
82
+ def load(self, skip_file=False):
83
+ if skip_file:
84
+ print("Skipping config file")
85
+ settings_dict = self._load_default()
86
+ self._config = Settings.model_validate(settings_dict)
87
+ else:
88
+ if not path.exists(self.config_path):
89
+ base_dir = path.dirname(self.config_path)
90
+ if not path.exists(base_dir):
91
+ makedirs(base_dir)
92
+ try:
93
+ print("Settings not found creating default settings")
94
+ with open(self.config_path, "w") as file:
95
+ yaml.dump(
96
+ self._load_default(),
97
+ file,
98
+ )
99
+ except Exception as ex:
100
+ print(f"Error in creating settings : {ex}")
101
+ exit()
102
+ try:
103
+ with open(self.config_path) as file:
104
+ settings_dict = yaml.safe_load(file)
105
+ self._config = Settings.model_validate(settings_dict)
106
+ except Exception as ex:
107
+ print(f"Error in loading settings : {ex}")
108
+
109
+ def save(self):
110
+ try:
111
+ with open(self.config_path, "w") as file:
112
+ tmp_cfg = deepcopy(self._config)
113
+ tmp_cfg.lcm_diffusion_setting.init_image = None
114
+ configurations = tmp_cfg.model_dump(
115
+ exclude=["init_image"],
116
+ )
117
+ if configurations:
118
+ yaml.dump(configurations, file)
119
+ except Exception as ex:
120
+ print(f"Error in saving settings : {ex}")
121
+
122
+ def _load_default(self) -> dict:
123
+ default_config = Settings()
124
+ return default_config.model_dump()
src/backend/__init__.py ADDED
File without changes
src/backend/annotators/canny_control.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from backend.annotators.control_interface import ControlInterface
3
+ from cv2 import Canny
4
+ from PIL import Image
5
+
6
+
7
+ class CannyControl(ControlInterface):
8
+ def get_control_image(self, image: Image) -> Image:
9
+ low_threshold = 100
10
+ high_threshold = 200
11
+ image = np.array(image)
12
+ image = Canny(image, low_threshold, high_threshold)
13
+ image = image[:, :, None]
14
+ image = np.concatenate([image, image, image], axis=2)
15
+ return Image.fromarray(image)
src/backend/annotators/control_interface.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from abc import ABC, abstractmethod
2
+
3
+ from PIL import Image
4
+
5
+
6
+ class ControlInterface(ABC):
7
+ @abstractmethod
8
+ def get_control_image(
9
+ self,
10
+ image: Image,
11
+ ) -> Image:
12
+ pass
src/backend/annotators/depth_control.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from backend.annotators.control_interface import ControlInterface
3
+ from PIL import Image
4
+ from transformers import pipeline
5
+
6
+
7
+ class DepthControl(ControlInterface):
8
+ def get_control_image(self, image: Image) -> Image:
9
+ depth_estimator = pipeline("depth-estimation")
10
+ image = depth_estimator(image)["depth"]
11
+ image = np.array(image)
12
+ image = image[:, :, None]
13
+ image = np.concatenate([image, image, image], axis=2)
14
+ image = Image.fromarray(image)
15
+ return image
src/backend/annotators/image_control_factory.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from backend.annotators.canny_control import CannyControl
2
+ from backend.annotators.depth_control import DepthControl
3
+ from backend.annotators.lineart_control import LineArtControl
4
+ from backend.annotators.mlsd_control import MlsdControl
5
+ from backend.annotators.normal_control import NormalControl
6
+ from backend.annotators.pose_control import PoseControl
7
+ from backend.annotators.shuffle_control import ShuffleControl
8
+ from backend.annotators.softedge_control import SoftEdgeControl
9
+
10
+
11
+ class ImageControlFactory:
12
+ def create_control(self, controlnet_type: str):
13
+ if controlnet_type == "Canny":
14
+ return CannyControl()
15
+ elif controlnet_type == "Pose":
16
+ return PoseControl()
17
+ elif controlnet_type == "MLSD":
18
+ return MlsdControl()
19
+ elif controlnet_type == "Depth":
20
+ return DepthControl()
21
+ elif controlnet_type == "LineArt":
22
+ return LineArtControl()
23
+ elif controlnet_type == "Shuffle":
24
+ return ShuffleControl()
25
+ elif controlnet_type == "NormalBAE":
26
+ return NormalControl()
27
+ elif controlnet_type == "SoftEdge":
28
+ return SoftEdgeControl()
29
+ else:
30
+ print("Error: Control type not implemented!")
31
+ raise Exception("Error: Control type not implemented!")
src/backend/annotators/lineart_control.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from backend.annotators.control_interface import ControlInterface
3
+ from controlnet_aux import LineartDetector
4
+ from PIL import Image
5
+
6
+
7
+ class LineArtControl(ControlInterface):
8
+ def get_control_image(self, image: Image) -> Image:
9
+ processor = LineartDetector.from_pretrained("lllyasviel/Annotators")
10
+ control_image = processor(image)
11
+ return control_image
src/backend/annotators/mlsd_control.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from backend.annotators.control_interface import ControlInterface
2
+ from controlnet_aux import MLSDdetector
3
+ from PIL import Image
4
+
5
+
6
+ class MlsdControl(ControlInterface):
7
+ def get_control_image(self, image: Image) -> Image:
8
+ mlsd = MLSDdetector.from_pretrained("lllyasviel/ControlNet")
9
+ image = mlsd(image)
10
+ return image
src/backend/annotators/normal_control.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from backend.annotators.control_interface import ControlInterface
2
+ from controlnet_aux import NormalBaeDetector
3
+ from PIL import Image
4
+
5
+
6
+ class NormalControl(ControlInterface):
7
+ def get_control_image(self, image: Image) -> Image:
8
+ processor = NormalBaeDetector.from_pretrained("lllyasviel/Annotators")
9
+ control_image = processor(image)
10
+ return control_image
src/backend/annotators/pose_control.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from backend.annotators.control_interface import ControlInterface
2
+ from controlnet_aux import OpenposeDetector
3
+ from PIL import Image
4
+
5
+
6
+ class PoseControl(ControlInterface):
7
+ def get_control_image(self, image: Image) -> Image:
8
+ openpose = OpenposeDetector.from_pretrained("lllyasviel/ControlNet")
9
+ image = openpose(image)
10
+ return image
src/backend/annotators/shuffle_control.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from backend.annotators.control_interface import ControlInterface
2
+ from controlnet_aux import ContentShuffleDetector
3
+ from PIL import Image
4
+
5
+
6
+ class ShuffleControl(ControlInterface):
7
+ def get_control_image(self, image: Image) -> Image:
8
+ shuffle_processor = ContentShuffleDetector()
9
+ image = shuffle_processor(image)
10
+ return image
src/backend/annotators/softedge_control.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from backend.annotators.control_interface import ControlInterface
2
+ from controlnet_aux import PidiNetDetector
3
+ from PIL import Image
4
+
5
+
6
+ class SoftEdgeControl(ControlInterface):
7
+ def get_control_image(self, image: Image) -> Image:
8
+ processor = PidiNetDetector.from_pretrained("lllyasviel/Annotators")
9
+ control_image = processor(image)
10
+ return control_image
src/backend/api/mcp_server.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import platform
2
+
3
+ import uvicorn
4
+ from backend.device import get_device_name
5
+ from backend.models.device import DeviceInfo
6
+ from constants import APP_VERSION, DEVICE
7
+ from context import Context
8
+ from fastapi import FastAPI, Request
9
+ from fastapi_mcp import FastApiMCP
10
+ from state import get_settings
11
+ from fastapi.middleware.cors import CORSMiddleware
12
+ from models.interface_types import InterfaceType
13
+ from fastapi.staticfiles import StaticFiles
14
+
15
+ SERVER_PORT = 8000
16
+
17
+ app_settings = get_settings()
18
+ app = FastAPI(
19
+ title="FastSD CPU",
20
+ description="Fast stable diffusion on CPU",
21
+ version=APP_VERSION,
22
+ license_info={
23
+ "name": "MIT",
24
+ "identifier": "MIT",
25
+ },
26
+ describe_all_responses=True,
27
+ describe_full_response_schema=True,
28
+ )
29
+ origins = ["*"]
30
+
31
+ app.add_middleware(
32
+ CORSMiddleware,
33
+ allow_origins=origins,
34
+ allow_credentials=True,
35
+ allow_methods=["*"],
36
+ allow_headers=["*"],
37
+ )
38
+
39
+ context = Context(InterfaceType.API_SERVER)
40
+ app.mount("/results", StaticFiles(directory="results"), name="results")
41
+
42
+
43
+ @app.get(
44
+ "/info",
45
+ description="Get system information",
46
+ summary="Get system information",
47
+ operation_id="get_system_info",
48
+ )
49
+ async def info() -> dict:
50
+ device_info = DeviceInfo(
51
+ device_type=DEVICE,
52
+ device_name=get_device_name(),
53
+ os=platform.system(),
54
+ platform=platform.platform(),
55
+ processor=platform.processor(),
56
+ )
57
+ return device_info.model_dump()
58
+
59
+
60
+ @app.post(
61
+ "/generate",
62
+ description="Generate image from text prompt",
63
+ summary="Text to image generation",
64
+ operation_id="generate",
65
+ )
66
+ async def generate(
67
+ prompt: str,
68
+ request: Request,
69
+ ) -> str:
70
+ """
71
+ Returns URL of the generated image for text prompt
72
+ """
73
+ app_settings.settings.lcm_diffusion_setting.prompt = prompt
74
+ images = context.generate_text_to_image(app_settings.settings)
75
+ image_names = context.save_images(
76
+ images,
77
+ app_settings.settings,
78
+ )
79
+ # url = request.url_for("results", path=image_names[0]) - Claude Desktop returns api_server
80
+ url = f"http://localhost:{SERVER_PORT}/results/{image_names[0]}"
81
+ image_url = f"The generated image available at the URL {url}"
82
+ return image_url
83
+
84
+
85
+ def start_mcp_server(port: int = 8000):
86
+ global SERVER_PORT
87
+ SERVER_PORT = port
88
+ print(f"Starting MCP server on port {port}...")
89
+ mcp = FastApiMCP(
90
+ app,
91
+ name="FastSDCPU MCP",
92
+ description="MCP server for FastSD CPU API",
93
+ )
94
+
95
+ mcp.mount()
96
+ uvicorn.run(
97
+ app,
98
+ host="0.0.0.0",
99
+ port=port,
100
+ )
src/backend/api/models/response.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class StableDiffusionResponse(BaseModel):
7
+ """
8
+ Stable diffusion response model
9
+
10
+ Attributes:
11
+ images (List[str]): List of JPEG image as base64 encoded
12
+ latency (float): Latency in seconds
13
+ error (str): Error message if any
14
+ """
15
+
16
+ images: List[str]
17
+ latency: float
18
+ error: str = ""
src/backend/api/web.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import platform
2
+
3
+ import uvicorn
4
+ from fastapi import FastAPI
5
+ from fastapi.middleware.cors import CORSMiddleware
6
+
7
+ from backend.api.models.response import StableDiffusionResponse
8
+ from backend.base64_image import base64_image_to_pil, pil_image_to_base64_str
9
+ from backend.device import get_device_name
10
+ from backend.models.device import DeviceInfo
11
+ from backend.models.lcmdiffusion_setting import DiffusionTask, LCMDiffusionSetting
12
+ from constants import APP_VERSION, DEVICE
13
+ from context import Context
14
+ from models.interface_types import InterfaceType
15
+ from state import get_settings
16
+
17
+ app_settings = get_settings()
18
+ app = FastAPI(
19
+ title="FastSD CPU",
20
+ description="Fast stable diffusion on CPU",
21
+ version=APP_VERSION,
22
+ license_info={
23
+ "name": "MIT",
24
+ "identifier": "MIT",
25
+ },
26
+ docs_url="/api/docs",
27
+ redoc_url="/api/redoc",
28
+ openapi_url="/api/openapi.json",
29
+ )
30
+ print(app_settings.settings.lcm_diffusion_setting)
31
+ origins = ["*"]
32
+ app.add_middleware(
33
+ CORSMiddleware,
34
+ allow_origins=origins,
35
+ allow_credentials=True,
36
+ allow_methods=["*"],
37
+ allow_headers=["*"],
38
+ )
39
+ context = Context(InterfaceType.API_SERVER)
40
+
41
+
42
+ @app.get("/api/")
43
+ async def root():
44
+ return {"message": "Welcome to FastSD CPU API"}
45
+
46
+
47
+ @app.get(
48
+ "/api/info",
49
+ description="Get system information",
50
+ summary="Get system information",
51
+ )
52
+ async def info():
53
+ device_info = DeviceInfo(
54
+ device_type=DEVICE,
55
+ device_name=get_device_name(),
56
+ os=platform.system(),
57
+ platform=platform.platform(),
58
+ processor=platform.processor(),
59
+ )
60
+ return device_info.model_dump()
61
+
62
+
63
+ @app.get(
64
+ "/api/config",
65
+ description="Get current configuration",
66
+ summary="Get configurations",
67
+ )
68
+ async def config():
69
+ return app_settings.settings
70
+
71
+
72
+ @app.get(
73
+ "/api/models",
74
+ description="Get available models",
75
+ summary="Get available models",
76
+ )
77
+ async def models():
78
+ return {
79
+ "lcm_lora_models": app_settings.lcm_lora_models,
80
+ "stable_diffusion": app_settings.stable_diffsuion_models,
81
+ "openvino_models": app_settings.openvino_lcm_models,
82
+ "lcm_models": app_settings.lcm_models,
83
+ }
84
+
85
+
86
+ @app.post(
87
+ "/api/generate",
88
+ description="Generate image(Text to image,Image to Image)",
89
+ summary="Generate image(Text to image,Image to Image)",
90
+ )
91
+ async def generate(diffusion_config: LCMDiffusionSetting) -> StableDiffusionResponse:
92
+ app_settings.settings.lcm_diffusion_setting = diffusion_config
93
+ if diffusion_config.diffusion_task == DiffusionTask.image_to_image:
94
+ app_settings.settings.lcm_diffusion_setting.init_image = base64_image_to_pil(
95
+ diffusion_config.init_image
96
+ )
97
+
98
+ images = context.generate_text_to_image(app_settings.settings)
99
+
100
+ if images:
101
+ images_base64 = [pil_image_to_base64_str(img) for img in images]
102
+ else:
103
+ images_base64 = []
104
+ return StableDiffusionResponse(
105
+ latency=round(context.latency, 2),
106
+ images=images_base64,
107
+ error=context.error,
108
+ )
109
+
110
+
111
+ def start_web_server(port: int = 8000):
112
+ uvicorn.run(
113
+ app,
114
+ host="0.0.0.0",
115
+ port=port,
116
+ )
src/backend/base64_image.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from io import BytesIO
2
+ from base64 import b64encode, b64decode
3
+ from PIL import Image
4
+
5
+
6
+ def pil_image_to_base64_str(
7
+ image: Image,
8
+ format: str = "JPEG",
9
+ ) -> str:
10
+ buffer = BytesIO()
11
+ image.save(buffer, format=format)
12
+ buffer.seek(0)
13
+ img_base64 = b64encode(buffer.getvalue()).decode("utf-8")
14
+ return img_base64
15
+
16
+
17
+ def base64_image_to_pil(base64_str) -> Image:
18
+ image_data = b64decode(base64_str)
19
+ image_buffer = BytesIO(image_data)
20
+ image = Image.open(image_buffer)
21
+ return image
src/backend/controlnet.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from PIL import Image
3
+ from typing import Any
4
+ from diffusers import (
5
+ ControlNetModel,
6
+ AutoPipelineForText2Image,
7
+ LCMScheduler,
8
+ AutoencoderKL,
9
+ StableDiffusionPipeline,
10
+ StableDiffusionXLPipeline,
11
+ StableDiffusionControlNetPipeline,
12
+ StableDiffusionXLControlNetPipeline,
13
+ StableDiffusionControlNetImg2ImgPipeline,
14
+ StableDiffusionXLControlNetImg2ImgPipeline,
15
+ )
16
+ from backend.models.lcmdiffusion_setting import (
17
+ DiffusionTask,
18
+ ControlNetSetting,
19
+ )
20
+
21
+
22
+ # Prepares ControlNet adapters for use with FastSD CPU
23
+ #
24
+ # This function loads the ControlNet adapters defined by the
25
+ # _lcm_diffusion_setting.controlnet_ object and returns a dictionary
26
+ # with the pipeline arguments required to use the loaded adapters
27
+ def load_controlnet_adapters(lcm_diffusion_setting) -> dict:
28
+ controlnet_args = {}
29
+ if (
30
+ lcm_diffusion_setting.controlnet is None
31
+ or not lcm_diffusion_setting.controlnet.enabled
32
+ ):
33
+ return controlnet_args
34
+
35
+ logging.info("Loading ControlNet adapter")
36
+ controlnet_adapter = ControlNetModel.from_single_file(
37
+ lcm_diffusion_setting.controlnet.adapter_path,
38
+ # local_files_only=True,
39
+ use_safetensors=True,
40
+ )
41
+ controlnet_args["controlnet"] = controlnet_adapter
42
+ return controlnet_args
43
+
44
+
45
+ # Updates the ControlNet pipeline arguments to use for image generation
46
+ #
47
+ # This function uses the contents of the _lcm_diffusion_setting.controlnet_
48
+ # object to generate a dictionary with the corresponding pipeline arguments
49
+ # to be used for image generation; in particular, it sets the ControlNet control
50
+ # image and conditioning scale
51
+ def update_controlnet_arguments(lcm_diffusion_setting) -> dict:
52
+ controlnet_args = {}
53
+ if (
54
+ lcm_diffusion_setting.controlnet is None
55
+ or not lcm_diffusion_setting.controlnet.enabled
56
+ ):
57
+ return controlnet_args
58
+
59
+ controlnet_args["controlnet_conditioning_scale"] = (
60
+ lcm_diffusion_setting.controlnet.conditioning_scale
61
+ )
62
+ if lcm_diffusion_setting.diffusion_task == DiffusionTask.text_to_image.value:
63
+ controlnet_args["image"] = lcm_diffusion_setting.controlnet._control_image
64
+ elif lcm_diffusion_setting.diffusion_task == DiffusionTask.image_to_image.value:
65
+ controlnet_args["control_image"] = (
66
+ lcm_diffusion_setting.controlnet._control_image
67
+ )
68
+ return controlnet_args
69
+
70
+
71
+ # Helper function to adjust ControlNet settings from a dictionary
72
+ def controlnet_settings_from_dict(
73
+ lcm_diffusion_setting,
74
+ dictionary,
75
+ ) -> None:
76
+ if lcm_diffusion_setting is None or dictionary is None:
77
+ logging.error("Invalid arguments!")
78
+ return
79
+ if (
80
+ "controlnet" not in dictionary
81
+ or dictionary["controlnet"] is None
82
+ or len(dictionary["controlnet"]) == 0
83
+ ):
84
+ logging.warning("ControlNet settings not found, ControlNet will be disabled")
85
+ lcm_diffusion_setting.controlnet = None
86
+ return
87
+
88
+ controlnet = ControlNetSetting()
89
+ controlnet.enabled = dictionary["controlnet"][0]["enabled"]
90
+ controlnet.conditioning_scale = dictionary["controlnet"][0]["conditioning_scale"]
91
+ controlnet.adapter_path = dictionary["controlnet"][0]["adapter_path"]
92
+ controlnet._control_image = None
93
+ image_path = dictionary["controlnet"][0]["control_image"]
94
+ if controlnet.enabled:
95
+ try:
96
+ controlnet._control_image = Image.open(image_path)
97
+ except (AttributeError, FileNotFoundError) as err:
98
+ print(err)
99
+ if controlnet._control_image is None:
100
+ logging.error("Wrong ControlNet control image! Disabling ControlNet")
101
+ controlnet.enabled = False
102
+ lcm_diffusion_setting.controlnet = controlnet
103
+
104
+
105
+ def get_controlnet_pipeline(
106
+ pipeline: Any, lcm_diffusion_setting, diffusion_task: DiffusionTask
107
+ ) -> Any:
108
+ """Creates a ControlNet pipeline from the base txt2img _pipeline_"""
109
+ if (
110
+ lcm_diffusion_setting.controlnet is None
111
+ or not lcm_diffusion_setting.controlnet.enabled
112
+ ):
113
+ return None
114
+ components = pipeline.components
115
+ pipeline_class = pipeline.__class__.__name__
116
+ controlnet_args = load_controlnet_adapters(lcm_diffusion_setting)
117
+ if diffusion_task == DiffusionTask.text_to_image.value:
118
+ if (
119
+ pipeline_class == "LatentConsistencyModelPipeline"
120
+ or pipeline_class == "StableDiffusionPipeline"
121
+ ):
122
+ controlnet_pipeline = StableDiffusionControlNetPipeline.from_pipe(
123
+ pipeline,
124
+ vae=None,
125
+ **controlnet_args,
126
+ )
127
+ controlnet_pipeline.vae = pipeline.vae
128
+ return controlnet_pipeline
129
+ elif pipeline_class == "StableDiffusionXLPipeline":
130
+ controlnet_pipeline = StableDiffusionXLControlNetPipeline.from_pipe(
131
+ pipeline,
132
+ vae=None,
133
+ **controlnet_args,
134
+ )
135
+ controlnet_pipeline.vae = pipeline.vae
136
+ return controlnet_pipeline
137
+ elif diffusion_task == DiffusionTask.image_to_image.value:
138
+ if (
139
+ pipeline_class == "LatentConsistencyModelPipeline"
140
+ or pipeline_class == "StableDiffusionPipeline"
141
+ ):
142
+ controlnet_pipeline = StableDiffusionControlNetImg2ImgPipeline.from_pipe(
143
+ pipeline,
144
+ vae=None,
145
+ **controlnet_args,
146
+ )
147
+ controlnet_pipeline.vae = pipeline.vae
148
+ return controlnet_pipeline
149
+ elif pipeline_class == "StableDiffusionXLPipeline":
150
+ controlnet_pipeline = StableDiffusionXLControlNetImg2ImgPipeline.from_pipe(
151
+ pipeline,
152
+ vae=None,
153
+ **controlnet_args,
154
+ )
155
+ controlnet_pipeline.vae = pipeline.vae
156
+ return controlnet_pipeline
src/backend/device.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import platform
2
+ from constants import DEVICE
3
+ import torch
4
+ import openvino as ov
5
+
6
+ core = ov.Core()
7
+
8
+
9
+ def is_openvino_device() -> bool:
10
+ if DEVICE.lower() == "cpu" or DEVICE.lower()[0] == "g" or DEVICE.lower()[0] == "n":
11
+ return True
12
+ else:
13
+ return False
14
+
15
+
16
+ def get_device_name() -> str:
17
+ if DEVICE == "cuda" or DEVICE == "mps":
18
+ default_gpu_index = torch.cuda.current_device()
19
+ return torch.cuda.get_device_name(default_gpu_index)
20
+ elif platform.system().lower() == "darwin":
21
+ return platform.processor()
22
+ elif is_openvino_device():
23
+ return core.get_property(DEVICE.upper(), "FULL_DEVICE_NAME")
src/backend/gguf/gguf_diffusion.py ADDED
@@ -0,0 +1,319 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Wrapper class to call the stablediffusion.cpp shared library for GGUF support
3
+ """
4
+
5
+ import ctypes
6
+ import platform
7
+ from ctypes import (
8
+ POINTER,
9
+ c_bool,
10
+ c_char_p,
11
+ c_float,
12
+ c_int,
13
+ c_int64,
14
+ c_void_p,
15
+ )
16
+ from dataclasses import dataclass
17
+ from os import path
18
+ from typing import List, Any
19
+
20
+ import numpy as np
21
+ from PIL import Image
22
+
23
+ from backend.gguf.sdcpp_types import (
24
+ RngType,
25
+ SampleMethod,
26
+ Schedule,
27
+ SDCPPLogLevel,
28
+ SDImage,
29
+ SdType,
30
+ )
31
+
32
+
33
+ @dataclass
34
+ class ModelConfig:
35
+ model_path: str = ""
36
+ clip_l_path: str = ""
37
+ t5xxl_path: str = ""
38
+ diffusion_model_path: str = ""
39
+ vae_path: str = ""
40
+ taesd_path: str = ""
41
+ control_net_path: str = ""
42
+ lora_model_dir: str = ""
43
+ embed_dir: str = ""
44
+ stacked_id_embed_dir: str = ""
45
+ vae_decode_only: bool = True
46
+ vae_tiling: bool = False
47
+ free_params_immediately: bool = False
48
+ n_threads: int = 4
49
+ wtype: SdType = SdType.SD_TYPE_Q4_0
50
+ rng_type: RngType = RngType.CUDA_RNG
51
+ schedule: Schedule = Schedule.DEFAULT
52
+ keep_clip_on_cpu: bool = False
53
+ keep_control_net_cpu: bool = False
54
+ keep_vae_on_cpu: bool = False
55
+
56
+
57
+ @dataclass
58
+ class Txt2ImgConfig:
59
+ prompt: str = "a man wearing sun glasses, highly detailed"
60
+ negative_prompt: str = ""
61
+ clip_skip: int = -1
62
+ cfg_scale: float = 2.0
63
+ guidance: float = 3.5
64
+ width: int = 512
65
+ height: int = 512
66
+ sample_method: SampleMethod = SampleMethod.EULER_A
67
+ sample_steps: int = 1
68
+ seed: int = -1
69
+ batch_count: int = 2
70
+ control_cond: Image = None
71
+ control_strength: float = 0.90
72
+ style_strength: float = 0.5
73
+ normalize_input: bool = False
74
+ input_id_images_path: bytes = b""
75
+
76
+
77
+ class GGUFDiffusion:
78
+ """GGUF Diffusion
79
+ To support GGUF diffusion model based on stablediffusion.cpp
80
+ https://github.com/ggerganov/ggml/blob/master/docs/gguf.md
81
+ Implmented based on stablediffusion.h
82
+ """
83
+
84
+ def __init__(
85
+ self,
86
+ libpath: str,
87
+ config: ModelConfig,
88
+ logging_enabled: bool = False,
89
+ ):
90
+ sdcpp_shared_lib_path = self._get_sdcpp_shared_lib_path(libpath)
91
+ try:
92
+ self.libsdcpp = ctypes.CDLL(sdcpp_shared_lib_path)
93
+ except OSError as e:
94
+ print(f"Failed to load library {sdcpp_shared_lib_path}")
95
+ raise ValueError(f"Error: {e}")
96
+
97
+ if not config.clip_l_path or not path.exists(config.clip_l_path):
98
+ raise ValueError(
99
+ "CLIP model file not found,please check readme.md for GGUF model usage"
100
+ )
101
+
102
+ if not config.t5xxl_path or not path.exists(config.t5xxl_path):
103
+ raise ValueError(
104
+ "T5XXL model file not found,please check readme.md for GGUF model usage"
105
+ )
106
+
107
+ if not config.diffusion_model_path or not path.exists(
108
+ config.diffusion_model_path
109
+ ):
110
+ raise ValueError(
111
+ "Diffusion model file not found,please check readme.md for GGUF model usage"
112
+ )
113
+
114
+ if not config.vae_path or not path.exists(config.vae_path):
115
+ raise ValueError(
116
+ "VAE model file not found,please check readme.md for GGUF model usage"
117
+ )
118
+
119
+ self.model_config = config
120
+
121
+ self.libsdcpp.new_sd_ctx.argtypes = [
122
+ c_char_p, # const char* model_path
123
+ c_char_p, # const char* clip_l_path
124
+ c_char_p, # const char* t5xxl_path
125
+ c_char_p, # const char* diffusion_model_path
126
+ c_char_p, # const char* vae_path
127
+ c_char_p, # const char* taesd_path
128
+ c_char_p, # const char* control_net_path_c_str
129
+ c_char_p, # const char* lora_model_dir
130
+ c_char_p, # const char* embed_dir_c_str
131
+ c_char_p, # const char* stacked_id_embed_dir_c_str
132
+ c_bool, # bool vae_decode_only
133
+ c_bool, # bool vae_tiling
134
+ c_bool, # bool free_params_immediately
135
+ c_int, # int n_threads
136
+ SdType, # enum sd_type_t wtype
137
+ RngType, # enum rng_type_t rng_type
138
+ Schedule, # enum schedule_t s
139
+ c_bool, # bool keep_clip_on_cpu
140
+ c_bool, # bool keep_control_net_cpu
141
+ c_bool, # bool keep_vae_on_cpu
142
+ ]
143
+
144
+ self.libsdcpp.new_sd_ctx.restype = POINTER(c_void_p)
145
+
146
+ self.sd_ctx = self.libsdcpp.new_sd_ctx(
147
+ self._str_to_bytes(self.model_config.model_path),
148
+ self._str_to_bytes(self.model_config.clip_l_path),
149
+ self._str_to_bytes(self.model_config.t5xxl_path),
150
+ self._str_to_bytes(self.model_config.diffusion_model_path),
151
+ self._str_to_bytes(self.model_config.vae_path),
152
+ self._str_to_bytes(self.model_config.taesd_path),
153
+ self._str_to_bytes(self.model_config.control_net_path),
154
+ self._str_to_bytes(self.model_config.lora_model_dir),
155
+ self._str_to_bytes(self.model_config.embed_dir),
156
+ self._str_to_bytes(self.model_config.stacked_id_embed_dir),
157
+ self.model_config.vae_decode_only,
158
+ self.model_config.vae_tiling,
159
+ self.model_config.free_params_immediately,
160
+ self.model_config.n_threads,
161
+ self.model_config.wtype,
162
+ self.model_config.rng_type,
163
+ self.model_config.schedule,
164
+ self.model_config.keep_clip_on_cpu,
165
+ self.model_config.keep_control_net_cpu,
166
+ self.model_config.keep_vae_on_cpu,
167
+ )
168
+
169
+ if logging_enabled:
170
+ self._set_logcallback()
171
+
172
+ def _set_logcallback(self):
173
+ print("Setting logging callback")
174
+ # Define function callback
175
+ SdLogCallbackType = ctypes.CFUNCTYPE(
176
+ None,
177
+ SDCPPLogLevel,
178
+ ctypes.c_char_p,
179
+ ctypes.c_void_p,
180
+ )
181
+
182
+ self.libsdcpp.sd_set_log_callback.argtypes = [
183
+ SdLogCallbackType,
184
+ ctypes.c_void_p,
185
+ ]
186
+ self.libsdcpp.sd_set_log_callback.restype = None
187
+ # Convert the Python callback to a C func pointer
188
+ self.c_log_callback = SdLogCallbackType(
189
+ self.log_callback
190
+ ) # prevent GC,keep callback as member variable
191
+ self.libsdcpp.sd_set_log_callback(self.c_log_callback, None)
192
+
193
+ def _get_sdcpp_shared_lib_path(
194
+ self,
195
+ root_path: str,
196
+ ) -> str:
197
+ system_name = platform.system()
198
+ print(f"GGUF Diffusion on {system_name}")
199
+ lib_name = "stable-diffusion.dll"
200
+ sdcpp_lib_path = ""
201
+
202
+ if system_name == "Windows":
203
+ sdcpp_lib_path = path.join(root_path, lib_name)
204
+ elif system_name == "Linux":
205
+ lib_name = "libstable-diffusion.so"
206
+ sdcpp_lib_path = path.join(root_path, lib_name)
207
+ elif system_name == "Darwin":
208
+ lib_name = "libstable-diffusion.dylib"
209
+ sdcpp_lib_path = path.join(root_path, lib_name)
210
+ else:
211
+ print("Unknown platform.")
212
+
213
+ return sdcpp_lib_path
214
+
215
+ @staticmethod
216
+ def log_callback(
217
+ level,
218
+ text,
219
+ data,
220
+ ):
221
+ print(f"{text.decode('utf-8')}", end="")
222
+
223
+ def _str_to_bytes(self, in_str: str, encoding: str = "utf-8") -> bytes:
224
+ if in_str:
225
+ return in_str.encode(encoding)
226
+ else:
227
+ return b""
228
+
229
+ def generate_text2mg(self, txt2img_cfg: Txt2ImgConfig) -> List[Any]:
230
+ self.libsdcpp.txt2img.restype = POINTER(SDImage)
231
+ self.libsdcpp.txt2img.argtypes = [
232
+ c_void_p, # sd_ctx_t* sd_ctx (pointer to context object)
233
+ c_char_p, # const char* prompt
234
+ c_char_p, # const char* negative_prompt
235
+ c_int, # int clip_skip
236
+ c_float, # float cfg_scale
237
+ c_float, # float guidance
238
+ c_int, # int width
239
+ c_int, # int height
240
+ SampleMethod, # enum sample_method_t sample_method
241
+ c_int, # int sample_steps
242
+ c_int64, # int64_t seed
243
+ c_int, # int batch_count
244
+ POINTER(SDImage), # const sd_image_t* control_cond (pointer to SDImage)
245
+ c_float, # float control_strength
246
+ c_float, # float style_strength
247
+ c_bool, # bool normalize_input
248
+ c_char_p, # const char* input_id_images_path
249
+ ]
250
+
251
+ image_buffer = self.libsdcpp.txt2img(
252
+ self.sd_ctx,
253
+ self._str_to_bytes(txt2img_cfg.prompt),
254
+ self._str_to_bytes(txt2img_cfg.negative_prompt),
255
+ txt2img_cfg.clip_skip,
256
+ txt2img_cfg.cfg_scale,
257
+ txt2img_cfg.guidance,
258
+ txt2img_cfg.width,
259
+ txt2img_cfg.height,
260
+ txt2img_cfg.sample_method,
261
+ txt2img_cfg.sample_steps,
262
+ txt2img_cfg.seed,
263
+ txt2img_cfg.batch_count,
264
+ txt2img_cfg.control_cond,
265
+ txt2img_cfg.control_strength,
266
+ txt2img_cfg.style_strength,
267
+ txt2img_cfg.normalize_input,
268
+ txt2img_cfg.input_id_images_path,
269
+ )
270
+
271
+ images = self._get_sd_images_from_buffer(
272
+ image_buffer,
273
+ txt2img_cfg.batch_count,
274
+ )
275
+
276
+ return images
277
+
278
+ def _get_sd_images_from_buffer(
279
+ self,
280
+ image_buffer: Any,
281
+ batch_count: int,
282
+ ) -> List[Any]:
283
+ images = []
284
+ if image_buffer:
285
+ for i in range(batch_count):
286
+ image = image_buffer[i]
287
+ print(
288
+ f"Generated image: {image.width}x{image.height} with {image.channel} channels"
289
+ )
290
+
291
+ width = image.width
292
+ height = image.height
293
+ channels = image.channel
294
+ pixel_data = np.ctypeslib.as_array(
295
+ image.data, shape=(height, width, channels)
296
+ )
297
+
298
+ if channels == 1:
299
+ pil_image = Image.fromarray(pixel_data.squeeze(), mode="L")
300
+ elif channels == 3:
301
+ pil_image = Image.fromarray(pixel_data, mode="RGB")
302
+ elif channels == 4:
303
+ pil_image = Image.fromarray(pixel_data, mode="RGBA")
304
+ else:
305
+ raise ValueError(f"Unsupported number of channels: {channels}")
306
+
307
+ images.append(pil_image)
308
+ return images
309
+
310
+ def terminate(self):
311
+ if self.libsdcpp:
312
+ if self.sd_ctx:
313
+ self.libsdcpp.free_sd_ctx.argtypes = [c_void_p]
314
+ self.libsdcpp.free_sd_ctx.restype = None
315
+ self.libsdcpp.free_sd_ctx(self.sd_ctx)
316
+ del self.sd_ctx
317
+ self.sd_ctx = None
318
+ del self.libsdcpp
319
+ self.libsdcpp = None
src/backend/gguf/sdcpp_types.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Ctypes for stablediffusion.cpp shared library
3
+ This is as per the stablediffusion.h file
4
+ """
5
+
6
+ from enum import IntEnum
7
+ from ctypes import (
8
+ c_int,
9
+ c_uint32,
10
+ c_uint8,
11
+ POINTER,
12
+ Structure,
13
+ )
14
+
15
+
16
+ class CtypesEnum(IntEnum):
17
+ """A ctypes-compatible IntEnum superclass."""
18
+
19
+ @classmethod
20
+ def from_param(cls, obj):
21
+ return int(obj)
22
+
23
+
24
+ class RngType(CtypesEnum):
25
+ STD_DEFAULT_RNG = 0
26
+ CUDA_RNG = 1
27
+
28
+
29
+ class SampleMethod(CtypesEnum):
30
+ EULER_A = 0
31
+ EULER = 1
32
+ HEUN = 2
33
+ DPM2 = 3
34
+ DPMPP2S_A = 4
35
+ DPMPP2M = 5
36
+ DPMPP2Mv2 = 6
37
+ IPNDM = 7
38
+ IPNDM_V = 7
39
+ LCM = 8
40
+ N_SAMPLE_METHODS = 9
41
+
42
+
43
+ class Schedule(CtypesEnum):
44
+ DEFAULT = 0
45
+ DISCRETE = 1
46
+ KARRAS = 2
47
+ EXPONENTIAL = 3
48
+ AYS = 4
49
+ GITS = 5
50
+ N_SCHEDULES = 5
51
+
52
+
53
+ class SdType(CtypesEnum):
54
+ SD_TYPE_F32 = 0
55
+ SD_TYPE_F16 = 1
56
+ SD_TYPE_Q4_0 = 2
57
+ SD_TYPE_Q4_1 = 3
58
+ # SD_TYPE_Q4_2 = 4, support has been removed
59
+ # SD_TYPE_Q4_3 = 5, support has been removed
60
+ SD_TYPE_Q5_0 = 6
61
+ SD_TYPE_Q5_1 = 7
62
+ SD_TYPE_Q8_0 = 8
63
+ SD_TYPE_Q8_1 = 9
64
+ SD_TYPE_Q2_K = 10
65
+ SD_TYPE_Q3_K = 11
66
+ SD_TYPE_Q4_K = 12
67
+ SD_TYPE_Q5_K = 13
68
+ SD_TYPE_Q6_K = 14
69
+ SD_TYPE_Q8_K = 15
70
+ SD_TYPE_IQ2_XXS = 16
71
+ SD_TYPE_IQ2_XS = 17
72
+ SD_TYPE_IQ3_XXS = 18
73
+ SD_TYPE_IQ1_S = 19
74
+ SD_TYPE_IQ4_NL = 20
75
+ SD_TYPE_IQ3_S = 21
76
+ SD_TYPE_IQ2_S = 22
77
+ SD_TYPE_IQ4_XS = 23
78
+ SD_TYPE_I8 = 24
79
+ SD_TYPE_I16 = 25
80
+ SD_TYPE_I32 = 26
81
+ SD_TYPE_I64 = 27
82
+ SD_TYPE_F64 = 28
83
+ SD_TYPE_IQ1_M = 29
84
+ SD_TYPE_BF16 = 30
85
+ SD_TYPE_Q4_0_4_4 = 31
86
+ SD_TYPE_Q4_0_4_8 = 32
87
+ SD_TYPE_Q4_0_8_8 = 33
88
+ SD_TYPE_COUNT = 34
89
+
90
+
91
+ class SDImage(Structure):
92
+ _fields_ = [
93
+ ("width", c_uint32),
94
+ ("height", c_uint32),
95
+ ("channel", c_uint32),
96
+ ("data", POINTER(c_uint8)),
97
+ ]
98
+
99
+
100
+ class SDCPPLogLevel(c_int):
101
+ SD_LOG_LEVEL_DEBUG = 0
102
+ SD_LOG_LEVEL_INFO = 1
103
+ SD_LOG_LEVEL_WARNING = 2
104
+ SD_LOG_LEVEL_ERROR = 3
src/backend/image_saver.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from os import path, mkdir
3
+ from typing import Any
4
+ from uuid import uuid4
5
+ from backend.models.lcmdiffusion_setting import LCMDiffusionSetting
6
+ from utils import get_image_file_extension
7
+
8
+
9
+ def get_exclude_keys():
10
+ exclude_keys = {
11
+ "init_image": True,
12
+ "generated_images": True,
13
+ "lora": {
14
+ "models_dir": True,
15
+ "path": True,
16
+ },
17
+ "dirs": True,
18
+ "controlnet": {
19
+ "adapter_path": True,
20
+ },
21
+ }
22
+ return exclude_keys
23
+
24
+
25
+ class ImageSaver:
26
+ @staticmethod
27
+ def save_images(
28
+ output_path: str,
29
+ images: Any,
30
+ folder_name: str = "",
31
+ format: str = "PNG",
32
+ jpeg_quality: int = 90,
33
+ lcm_diffusion_setting: LCMDiffusionSetting = None,
34
+ ) -> list[str]:
35
+ gen_id = uuid4()
36
+ image_ids = []
37
+
38
+ if images:
39
+ image_seeds = []
40
+
41
+ for index, image in enumerate(images):
42
+
43
+ image_seed = image.info.get('image_seed')
44
+ if image_seed is not None:
45
+ image_seeds.append(image_seed)
46
+
47
+ if not path.exists(output_path):
48
+ mkdir(output_path)
49
+
50
+ if folder_name:
51
+ out_path = path.join(
52
+ output_path,
53
+ folder_name,
54
+ )
55
+ else:
56
+ out_path = output_path
57
+
58
+ if not path.exists(out_path):
59
+ mkdir(out_path)
60
+ image_extension = get_image_file_extension(format)
61
+ image_file_name = f"{gen_id}-{index+1}{image_extension}"
62
+ image_ids.append(image_file_name)
63
+ image.save(path.join(out_path, image_file_name), quality = jpeg_quality)
64
+ if lcm_diffusion_setting:
65
+ data = lcm_diffusion_setting.model_dump(exclude=get_exclude_keys())
66
+ if image_seeds:
67
+ data['image_seeds'] = image_seeds
68
+ with open(path.join(out_path, f"{gen_id}.json"), "w") as json_file:
69
+ json.dump(
70
+ data,
71
+ json_file,
72
+ indent=4,
73
+ )
74
+ return image_ids
75
+
src/backend/lcm_text_to_image.py ADDED
@@ -0,0 +1,686 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gc
2
+ from math import ceil
3
+ from typing import Any, List
4
+ import random
5
+
6
+ import numpy as np
7
+ import torch
8
+ from backend.device import is_openvino_device
9
+ from backend.lora import reset_active_lora_weights
10
+ from backend.controlnet import (
11
+ load_controlnet_adapters,
12
+ update_controlnet_arguments,
13
+ get_controlnet_pipeline,
14
+ )
15
+ from backend.models.lcmdiffusion_setting import (
16
+ DiffusionTask,
17
+ LCMDiffusionSetting,
18
+ LCMLora,
19
+ )
20
+ from backend.openvino.pipelines import (
21
+ get_ov_image_to_image_pipeline,
22
+ get_ov_text_to_image_pipeline,
23
+ ov_load_tiny_autoencoder,
24
+ get_ov_diffusion_pipeline,
25
+ )
26
+ from backend.pipelines.lcm import (
27
+ get_image_to_image_pipeline,
28
+ get_lcm_model_pipeline,
29
+ load_taesd,
30
+ )
31
+ from backend.pipelines.lcm_lora import get_lcm_lora_pipeline
32
+ from constants import DEVICE, GGUF_THREADS
33
+ from diffusers import LCMScheduler
34
+ from image_ops import resize_pil_image
35
+ from backend.openvino.ov_hc_stablediffusion_pipeline import OvHcLatentConsistency
36
+ from backend.gguf.gguf_diffusion import (
37
+ GGUFDiffusion,
38
+ ModelConfig,
39
+ Txt2ImgConfig,
40
+ SampleMethod,
41
+ )
42
+ from paths import get_app_path
43
+ from pprint import pprint
44
+
45
+ try:
46
+ # support for token merging; keeping it optional for now
47
+ import tomesd
48
+ except ImportError:
49
+ print("tomesd library unavailable; disabling token merging support")
50
+ tomesd = None
51
+
52
+
53
+ class LCMTextToImage:
54
+ def __init__(
55
+ self,
56
+ device: str = "cpu",
57
+ ) -> None:
58
+ self.pipeline = None
59
+ self.txt2img_pipeline = None
60
+ self.img2img_pipeline = None
61
+ self.controlnet_pipeline = None
62
+ self.controlnet_img2img_pipeline = None
63
+ self.use_openvino = False
64
+ self.device = ""
65
+ self.previous_model_id = None
66
+ self.previous_use_tae_sd = False
67
+ self.previous_use_lcm_lora = False
68
+ self.previous_ov_model_id = ""
69
+ self.previous_token_merging = 0.0
70
+ self.previous_safety_checker = False
71
+ self.previous_use_openvino = False
72
+ self.img_to_img_pipeline = None
73
+ self.is_openvino_init = False
74
+ self.previous_lora = None
75
+ self.task_type = DiffusionTask.text_to_image
76
+ self.previous_use_gguf_model = False
77
+ self.previous_gguf_model = None
78
+ self.torch_data_type = (
79
+ torch.float32 if is_openvino_device() or DEVICE == "mps" else torch.float16
80
+ )
81
+ self.ov_model_id = None
82
+ print(f"Torch datatype : {self.torch_data_type}")
83
+
84
+ def _pipeline_to_device(self):
85
+ print(f"Pipeline device : {DEVICE}")
86
+ print(f"Pipeline dtype : {self.torch_data_type}")
87
+ self.pipeline.to(
88
+ torch_device=DEVICE,
89
+ torch_dtype=self.torch_data_type,
90
+ )
91
+
92
+ def _add_freeu(self):
93
+ pipeline_class = self.pipeline.__class__.__name__
94
+ if isinstance(self.pipeline.scheduler, LCMScheduler):
95
+ if pipeline_class == "StableDiffusionPipeline":
96
+ print("Add FreeU - SD")
97
+ self.pipeline.enable_freeu(
98
+ s1=0.9,
99
+ s2=0.2,
100
+ b1=1.2,
101
+ b2=1.4,
102
+ )
103
+ elif pipeline_class == "StableDiffusionXLPipeline":
104
+ print("Add FreeU - SDXL")
105
+ self.pipeline.enable_freeu(
106
+ s1=0.6,
107
+ s2=0.4,
108
+ b1=1.1,
109
+ b2=1.2,
110
+ )
111
+
112
+ def _enable_vae_tiling(self):
113
+ self.pipeline.vae.enable_tiling()
114
+
115
+ def _update_lcm_scheduler_params(self):
116
+ if isinstance(self.pipeline.scheduler, LCMScheduler):
117
+ self.pipeline.scheduler = LCMScheduler.from_config(
118
+ self.pipeline.scheduler.config,
119
+ beta_start=0.001,
120
+ beta_end=0.01,
121
+ )
122
+
123
+ def _is_hetero_pipeline(self) -> bool:
124
+ return "square" in self.ov_model_id.lower()
125
+
126
+ def _load_ov_hetero_pipeline(self):
127
+ print("Loading Heterogeneous Compute pipeline")
128
+ if DEVICE.upper() == "NPU":
129
+ device = ["NPU", "NPU", "NPU"]
130
+ self.pipeline = OvHcLatentConsistency(self.ov_model_id, device)
131
+ else:
132
+ self.pipeline = OvHcLatentConsistency(self.ov_model_id)
133
+
134
+ def _generate_images_hetero_compute(
135
+ self,
136
+ lcm_diffusion_setting: LCMDiffusionSetting,
137
+ ):
138
+ print("Using OpenVINO ")
139
+ if lcm_diffusion_setting.diffusion_task == DiffusionTask.text_to_image.value:
140
+ return [
141
+ self.pipeline.generate(
142
+ prompt=lcm_diffusion_setting.prompt,
143
+ neg_prompt=lcm_diffusion_setting.negative_prompt,
144
+ init_image=None,
145
+ strength=1.0,
146
+ num_inference_steps=lcm_diffusion_setting.inference_steps,
147
+ )
148
+ ]
149
+ else:
150
+ return [
151
+ self.pipeline.generate(
152
+ prompt=lcm_diffusion_setting.prompt,
153
+ neg_prompt=lcm_diffusion_setting.negative_prompt,
154
+ init_image=lcm_diffusion_setting.init_image,
155
+ strength=lcm_diffusion_setting.strength,
156
+ num_inference_steps=lcm_diffusion_setting.inference_steps,
157
+ )
158
+ ]
159
+
160
+ def _is_valid_mode(
161
+ self,
162
+ modes: List,
163
+ ) -> bool:
164
+ return modes.count(True) == 1 or modes.count(False) == 3
165
+
166
+ def _validate_mode(
167
+ self,
168
+ modes: List,
169
+ ) -> None:
170
+ if not self._is_valid_mode(modes):
171
+ raise ValueError("Invalid mode,delete configs/settings.yaml and retry!")
172
+
173
+ def _is_sana_model(self) -> bool:
174
+ return "sana" in self.ov_model_id.lower()
175
+
176
+ def init(
177
+ self,
178
+ device: str = "cpu",
179
+ lcm_diffusion_setting: LCMDiffusionSetting = LCMDiffusionSetting(),
180
+ ) -> None:
181
+ # Mode validation either LCM LoRA or OpenVINO or GGUF
182
+
183
+ modes = [
184
+ lcm_diffusion_setting.use_gguf_model,
185
+ lcm_diffusion_setting.use_openvino,
186
+ lcm_diffusion_setting.use_lcm_lora,
187
+ ]
188
+ self._validate_mode(modes)
189
+ self.device = device
190
+ self.use_openvino = lcm_diffusion_setting.use_openvino
191
+ model_id = lcm_diffusion_setting.lcm_model_id
192
+ use_local_model = lcm_diffusion_setting.use_offline_model
193
+ use_tiny_auto_encoder = lcm_diffusion_setting.use_tiny_auto_encoder
194
+ use_lora = lcm_diffusion_setting.use_lcm_lora
195
+ lcm_lora: LCMLora = lcm_diffusion_setting.lcm_lora
196
+ token_merging = lcm_diffusion_setting.token_merging
197
+ self.ov_model_id = lcm_diffusion_setting.openvino_lcm_model_id
198
+
199
+ if lcm_diffusion_setting.diffusion_task == DiffusionTask.image_to_image.value:
200
+ lcm_diffusion_setting.init_image = resize_pil_image(
201
+ lcm_diffusion_setting.init_image,
202
+ lcm_diffusion_setting.image_width,
203
+ lcm_diffusion_setting.image_height,
204
+ )
205
+
206
+ # Reset current pipeline references to allow the garbage
207
+ # collector to correctly free the pipeline when necessary
208
+ if self.txt2img_pipeline: # In LCM or LCM-LoRA modes
209
+ self.pipeline = self.txt2img_pipeline
210
+ self.img_to_img_pipeline = self.img2img_pipeline
211
+ # Regenerate the ControlNet pipelines if the variable
212
+ # lcm_diffusion_setting.rebuild_controlnet_pipeline is set,
213
+ # this is done here because rebuilding the ControlNet pipelines
214
+ # doesn't necessarily implies a full pipeline rebuild.
215
+ if lcm_diffusion_setting.rebuild_controlnet_pipeline:
216
+ if self.controlnet_pipeline:
217
+ del self.controlnet_pipeline
218
+ self.controlnet_pipeline = None
219
+ if self.controlnet_img2img_pipeline:
220
+ del self.controlnet_img2img_pipeline
221
+ self.controlnet_img2img_pipeline = None
222
+ gc.collect()
223
+ self.controlnet_pipeline = get_controlnet_pipeline(
224
+ self.txt2img_pipeline,
225
+ lcm_diffusion_setting,
226
+ DiffusionTask.text_to_image,
227
+ )
228
+ self.controlnet_img2img_pipeline = get_controlnet_pipeline(
229
+ self.txt2img_pipeline,
230
+ lcm_diffusion_setting,
231
+ DiffusionTask.image_to_image,
232
+ )
233
+ lcm_diffusion_setting.rebuild_controlnet_pipeline = False
234
+
235
+ if (
236
+ self.pipeline is None
237
+ or self.previous_model_id != model_id
238
+ or self.previous_use_tae_sd != use_tiny_auto_encoder
239
+ or self.previous_lcm_lora_base_id != lcm_lora.base_model_id
240
+ or self.previous_lcm_lora_id != lcm_lora.lcm_lora_id
241
+ or self.previous_use_lcm_lora != use_lora
242
+ or self.previous_ov_model_id != self.ov_model_id
243
+ or self.previous_token_merging != token_merging
244
+ or self.previous_safety_checker != lcm_diffusion_setting.use_safety_checker
245
+ or self.previous_use_openvino != lcm_diffusion_setting.use_openvino
246
+ or self.previous_use_gguf_model != lcm_diffusion_setting.use_gguf_model
247
+ or self.previous_gguf_model != lcm_diffusion_setting.gguf_model
248
+ or (
249
+ self.use_openvino
250
+ and (
251
+ self.previous_task_type != lcm_diffusion_setting.diffusion_task
252
+ or self.previous_lora != lcm_diffusion_setting.lora
253
+ )
254
+ )
255
+ or lcm_diffusion_setting.rebuild_pipeline
256
+ ):
257
+ if self.use_openvino and is_openvino_device():
258
+ if self.pipeline:
259
+ del self.pipeline
260
+ self.pipeline = None
261
+ gc.collect()
262
+ self.is_openvino_init = True
263
+ if (
264
+ lcm_diffusion_setting.diffusion_task
265
+ == DiffusionTask.text_to_image.value
266
+ ):
267
+ print(
268
+ f"***** Init Text to image (OpenVINO) - {self.ov_model_id} *****"
269
+ )
270
+ if "flux" in self.ov_model_id.lower() or self._is_sana_model():
271
+ if self._is_sana_model():
272
+ print("Loading OpenVINO SANA Sprint pipeline")
273
+ else:
274
+ print("Loading OpenVINO Flux pipeline")
275
+ self.pipeline = get_ov_diffusion_pipeline(self.ov_model_id)
276
+ elif self._is_hetero_pipeline():
277
+ self._load_ov_hetero_pipeline()
278
+ else:
279
+ self.pipeline = get_ov_text_to_image_pipeline(
280
+ self.ov_model_id,
281
+ use_local_model,
282
+ )
283
+ elif (
284
+ lcm_diffusion_setting.diffusion_task
285
+ == DiffusionTask.image_to_image.value
286
+ ):
287
+ if not self.pipeline and self._is_hetero_pipeline():
288
+ self._load_ov_hetero_pipeline()
289
+ else:
290
+ print(
291
+ f"***** Image to image (OpenVINO) - {self.ov_model_id} *****"
292
+ )
293
+ self.pipeline = get_ov_image_to_image_pipeline(
294
+ self.ov_model_id,
295
+ use_local_model,
296
+ )
297
+ elif lcm_diffusion_setting.use_gguf_model:
298
+ model = lcm_diffusion_setting.gguf_model.diffusion_path
299
+ print(f"***** Init Text to image (GGUF) - {model} *****")
300
+ # if self.pipeline:
301
+ # self.pipeline.terminate()
302
+ # del self.pipeline
303
+ # self.pipeline = None
304
+ self._init_gguf_diffusion(lcm_diffusion_setting)
305
+ else:
306
+ # Code for pipeline rebuild in LCM or LCM-LoRA modes
307
+ reset_active_lora_weights()
308
+ if self.txt2img_pipeline: # In LCM or LCM-LoRA modes
309
+ self.pipeline = self.txt2img_pipeline
310
+ if self.txt2img_pipeline:
311
+ del self.txt2img_pipeline
312
+ self.txt2img_pipeline = None
313
+ if self.img2img_pipeline:
314
+ del self.img2img_pipeline
315
+ self.img2img_pipeline = None
316
+ if self.img_to_img_pipeline:
317
+ del self.img_to_img_pipeline
318
+ self.img_to_img_pipeline = None
319
+ if self.controlnet_pipeline:
320
+ del self.controlnet_pipeline
321
+ self.controlnet_pipeline = None
322
+ if self.controlnet_img2img_pipeline:
323
+ del self.controlnet_img2img_pipeline
324
+ self.controlnet_img2img_pipeline = None
325
+ del self.pipeline
326
+ self.pipeline = None
327
+ gc.collect()
328
+
329
+ if use_lora:
330
+ print(
331
+ f"***** Init LCM-LoRA pipeline - {lcm_lora.base_model_id} *****"
332
+ )
333
+ self.pipeline = get_lcm_lora_pipeline(
334
+ lcm_lora.base_model_id,
335
+ lcm_lora.lcm_lora_id,
336
+ use_local_model,
337
+ torch_data_type=self.torch_data_type,
338
+ )
339
+
340
+ else:
341
+ print(f"***** Init LCM Model pipeline - {model_id} *****")
342
+ extra_args = {}
343
+ self.pipeline = get_lcm_model_pipeline(
344
+ model_id,
345
+ use_local_model,
346
+ extra_args,
347
+ )
348
+
349
+ # Prepare alternative generation pipelines using the newly
350
+ # created pipeline from which all extra pipelines are derived
351
+ self.txt2img_pipeline = self.pipeline
352
+ self.img2img_pipeline = get_image_to_image_pipeline(self.pipeline)
353
+ self.controlnet_pipeline = get_controlnet_pipeline(
354
+ self.pipeline,
355
+ lcm_diffusion_setting,
356
+ DiffusionTask.text_to_image,
357
+ )
358
+ self.controlnet_img2img_pipeline = get_controlnet_pipeline(
359
+ self.pipeline,
360
+ lcm_diffusion_setting,
361
+ DiffusionTask.image_to_image,
362
+ )
363
+ self.img_to_img_pipeline = self.img2img_pipeline
364
+
365
+ if tomesd and token_merging > 0.001:
366
+ print(f"***** Token Merging: {token_merging} *****")
367
+ tomesd.apply_patch(self.pipeline, ratio=token_merging)
368
+ tomesd.apply_patch(self.img_to_img_pipeline, ratio=token_merging)
369
+
370
+ if use_tiny_auto_encoder:
371
+ if self.use_openvino and is_openvino_device():
372
+ if not self._is_sana_model():
373
+ print("Using Tiny AutoEncoder (OpenVINO)")
374
+ ov_load_tiny_autoencoder(
375
+ self.pipeline,
376
+ use_local_model,
377
+ )
378
+ else:
379
+ print("Using Tiny Auto Encoder")
380
+ load_taesd(
381
+ self.pipeline,
382
+ use_local_model,
383
+ self.torch_data_type,
384
+ )
385
+ load_taesd(
386
+ self.img_to_img_pipeline,
387
+ use_local_model,
388
+ self.torch_data_type,
389
+ )
390
+
391
+ if not self.use_openvino and not is_openvino_device():
392
+ self._pipeline_to_device()
393
+
394
+ if not self._is_hetero_pipeline():
395
+ if (
396
+ lcm_diffusion_setting.diffusion_task
397
+ == DiffusionTask.image_to_image.value
398
+ and lcm_diffusion_setting.use_openvino
399
+ ):
400
+ self.pipeline.scheduler = LCMScheduler.from_config(
401
+ self.pipeline.scheduler.config,
402
+ )
403
+ else:
404
+ if not lcm_diffusion_setting.use_gguf_model:
405
+ self._update_lcm_scheduler_params()
406
+
407
+ if use_lora:
408
+ self._add_freeu()
409
+
410
+ self.previous_model_id = model_id
411
+ self.previous_ov_model_id = self.ov_model_id
412
+ self.previous_use_tae_sd = use_tiny_auto_encoder
413
+ self.previous_lcm_lora_base_id = lcm_lora.base_model_id
414
+ self.previous_lcm_lora_id = lcm_lora.lcm_lora_id
415
+ self.previous_use_lcm_lora = use_lora
416
+ self.previous_token_merging = lcm_diffusion_setting.token_merging
417
+ self.previous_safety_checker = lcm_diffusion_setting.use_safety_checker
418
+ self.previous_use_openvino = lcm_diffusion_setting.use_openvino
419
+ self.previous_task_type = lcm_diffusion_setting.diffusion_task
420
+ self.previous_lora = lcm_diffusion_setting.lora.model_copy(deep=True)
421
+ self.previous_use_gguf_model = lcm_diffusion_setting.use_gguf_model
422
+ self.previous_gguf_model = lcm_diffusion_setting.gguf_model.model_copy(
423
+ deep=True
424
+ )
425
+ lcm_diffusion_setting.rebuild_pipeline = False
426
+ if (
427
+ lcm_diffusion_setting.diffusion_task
428
+ == DiffusionTask.text_to_image.value
429
+ ):
430
+ print(f"Pipeline : {self.pipeline}")
431
+ elif (
432
+ lcm_diffusion_setting.diffusion_task
433
+ == DiffusionTask.image_to_image.value
434
+ ):
435
+ if self.use_openvino and is_openvino_device():
436
+ print(f"Pipeline : {self.pipeline}")
437
+ else:
438
+ print(f"Pipeline : {self.img_to_img_pipeline}")
439
+ if self.use_openvino:
440
+ if lcm_diffusion_setting.lora.enabled:
441
+ print("Warning: Lora models not supported on OpenVINO mode")
442
+ elif not lcm_diffusion_setting.use_gguf_model:
443
+ adapters = self.pipeline.get_active_adapters()
444
+ print(f"Active adapters : {adapters}")
445
+
446
+ def _get_timesteps(self):
447
+ time_steps = self.pipeline.scheduler.config.get("timesteps")
448
+ time_steps_value = [int(time_steps)] if time_steps else None
449
+ return time_steps_value
450
+
451
+ def _compile_ov_pipeline(
452
+ self,
453
+ lcm_diffusion_setting,
454
+ ):
455
+ self.pipeline.reshape(
456
+ batch_size=-1,
457
+ height=lcm_diffusion_setting.image_height,
458
+ width=lcm_diffusion_setting.image_width,
459
+ num_images_per_prompt=lcm_diffusion_setting.number_of_images,
460
+ )
461
+ self.pipeline.compile()
462
+
463
+ def generate(
464
+ self,
465
+ lcm_diffusion_setting: LCMDiffusionSetting,
466
+ reshape: bool = False,
467
+ ) -> Any:
468
+ guidance_scale = lcm_diffusion_setting.guidance_scale
469
+ img_to_img_inference_steps = lcm_diffusion_setting.inference_steps
470
+ check_step_value = int(
471
+ lcm_diffusion_setting.inference_steps * lcm_diffusion_setting.strength
472
+ )
473
+ if (
474
+ lcm_diffusion_setting.diffusion_task == DiffusionTask.image_to_image.value
475
+ and check_step_value < 1
476
+ ):
477
+ img_to_img_inference_steps = ceil(1 / lcm_diffusion_setting.strength)
478
+ print(
479
+ f"Strength: {lcm_diffusion_setting.strength},{img_to_img_inference_steps}"
480
+ )
481
+
482
+ # In LCM and LCM-LoRA modes, set the pipeline for the current
483
+ # generation by setting self.pipeline and self.img_to_img_pipeline
484
+ # to either the normal generation pipelines or the ControlNet
485
+ # generation pipelines, depending on the user settings. Note that,
486
+ # after generation, the pipelines are reset for compatibility with
487
+ # other generation modes.
488
+ if self.txt2img_pipeline: # In LCM or LCM-LoRA modes
489
+ self.pipeline = self.txt2img_pipeline
490
+ self.img_to_img_pipeline = self.img2img_pipeline
491
+ if (
492
+ lcm_diffusion_setting.controlnet
493
+ and lcm_diffusion_setting.controlnet.enabled
494
+ ):
495
+ if self.controlnet_pipeline != None:
496
+ self.pipeline = self.controlnet_pipeline
497
+ if self.controlnet_img2img_pipeline != None:
498
+ self.img_to_img_pipeline = self.controlnet_img2img_pipeline
499
+ pipeline_extra_args = {}
500
+
501
+ if lcm_diffusion_setting.use_seed:
502
+ cur_seed = lcm_diffusion_setting.seed
503
+ # for multiple images with a fixed seed, use sequential seeds
504
+ seeds = [
505
+ (cur_seed + i) for i in range(lcm_diffusion_setting.number_of_images)
506
+ ]
507
+ else:
508
+ seeds = [
509
+ random.randint(0, 999999999)
510
+ for i in range(lcm_diffusion_setting.number_of_images)
511
+ ]
512
+
513
+ if self.use_openvino:
514
+ # no support for generators; try at least to ensure reproducible results for single images
515
+ torch.manual_seed(seeds[0])
516
+ if self._is_hetero_pipeline():
517
+ torch.manual_seed(seeds[0])
518
+ lcm_diffusion_setting.seed = seeds[0]
519
+ else:
520
+ pipeline_extra_args["generator"] = [
521
+ torch.Generator(device=self.device).manual_seed(s) for s in seeds
522
+ ]
523
+
524
+ is_openvino_pipe = lcm_diffusion_setting.use_openvino and is_openvino_device()
525
+ if is_openvino_pipe and not self._is_hetero_pipeline():
526
+ print("Using OpenVINO")
527
+ if self.is_openvino_init and self._is_sana_model():
528
+ self._compile_ov_pipeline(lcm_diffusion_setting)
529
+
530
+ if reshape and not self.is_openvino_init:
531
+ print("Reshape and compile")
532
+ self._compile_ov_pipeline(lcm_diffusion_setting)
533
+
534
+ if self.is_openvino_init:
535
+ self.is_openvino_init = False
536
+
537
+ if is_openvino_pipe and self._is_hetero_pipeline():
538
+ return self._generate_images_hetero_compute(lcm_diffusion_setting)
539
+ elif lcm_diffusion_setting.use_gguf_model:
540
+ return self._generate_images_gguf(lcm_diffusion_setting)
541
+
542
+ if lcm_diffusion_setting.clip_skip > 1:
543
+ # We follow the convention that "CLIP Skip == 2" means "skip
544
+ # the last layer", so "CLIP Skip == 1" means "no skipping"
545
+ pipeline_extra_args["clip_skip"] = lcm_diffusion_setting.clip_skip - 1
546
+
547
+ self.pipeline.safety_checker = None
548
+ if (
549
+ lcm_diffusion_setting.diffusion_task == DiffusionTask.image_to_image.value
550
+ and not is_openvino_pipe
551
+ ):
552
+ self.img_to_img_pipeline.safety_checker = None
553
+
554
+ if (
555
+ not lcm_diffusion_setting.use_lcm_lora
556
+ and not lcm_diffusion_setting.use_openvino
557
+ and lcm_diffusion_setting.guidance_scale != 1.0
558
+ ):
559
+ print("Not using LCM-LoRA so setting guidance_scale 1.0")
560
+ guidance_scale = 1.0
561
+
562
+ controlnet_args = update_controlnet_arguments(lcm_diffusion_setting)
563
+ if lcm_diffusion_setting.use_openvino:
564
+ if (
565
+ lcm_diffusion_setting.diffusion_task
566
+ == DiffusionTask.text_to_image.value
567
+ ):
568
+ if self._is_sana_model():
569
+ result_images = self.pipeline(
570
+ prompt=lcm_diffusion_setting.prompt,
571
+ num_inference_steps=lcm_diffusion_setting.inference_steps,
572
+ guidance_scale=guidance_scale,
573
+ width=lcm_diffusion_setting.image_width,
574
+ height=lcm_diffusion_setting.image_height,
575
+ num_images_per_prompt=lcm_diffusion_setting.number_of_images,
576
+ ).images
577
+ else:
578
+ result_images = self.pipeline(
579
+ prompt=lcm_diffusion_setting.prompt,
580
+ negative_prompt=lcm_diffusion_setting.negative_prompt,
581
+ num_inference_steps=lcm_diffusion_setting.inference_steps,
582
+ guidance_scale=guidance_scale,
583
+ width=lcm_diffusion_setting.image_width,
584
+ height=lcm_diffusion_setting.image_height,
585
+ num_images_per_prompt=lcm_diffusion_setting.number_of_images,
586
+ ).images
587
+ elif (
588
+ lcm_diffusion_setting.diffusion_task
589
+ == DiffusionTask.image_to_image.value
590
+ ):
591
+ result_images = self.pipeline(
592
+ image=lcm_diffusion_setting.init_image,
593
+ strength=lcm_diffusion_setting.strength,
594
+ prompt=lcm_diffusion_setting.prompt,
595
+ negative_prompt=lcm_diffusion_setting.negative_prompt,
596
+ num_inference_steps=img_to_img_inference_steps * 3,
597
+ guidance_scale=guidance_scale,
598
+ num_images_per_prompt=lcm_diffusion_setting.number_of_images,
599
+ ).images
600
+
601
+ else:
602
+ if (
603
+ lcm_diffusion_setting.diffusion_task
604
+ == DiffusionTask.text_to_image.value
605
+ ):
606
+ print(f"Using {self.pipeline.__class__.__name__}")
607
+ result_images = self.pipeline(
608
+ prompt=lcm_diffusion_setting.prompt,
609
+ negative_prompt=lcm_diffusion_setting.negative_prompt,
610
+ num_inference_steps=lcm_diffusion_setting.inference_steps,
611
+ guidance_scale=guidance_scale,
612
+ width=lcm_diffusion_setting.image_width,
613
+ height=lcm_diffusion_setting.image_height,
614
+ num_images_per_prompt=lcm_diffusion_setting.number_of_images,
615
+ timesteps=self._get_timesteps(),
616
+ **pipeline_extra_args,
617
+ **controlnet_args,
618
+ ).images
619
+
620
+ elif (
621
+ lcm_diffusion_setting.diffusion_task
622
+ == DiffusionTask.image_to_image.value
623
+ ):
624
+ print(f"Using {self.pipeline.__class__.__name__}")
625
+ result_images = self.img_to_img_pipeline(
626
+ image=lcm_diffusion_setting.init_image,
627
+ strength=lcm_diffusion_setting.strength,
628
+ prompt=lcm_diffusion_setting.prompt,
629
+ negative_prompt=lcm_diffusion_setting.negative_prompt,
630
+ num_inference_steps=img_to_img_inference_steps,
631
+ guidance_scale=guidance_scale,
632
+ width=lcm_diffusion_setting.image_width,
633
+ height=lcm_diffusion_setting.image_height,
634
+ num_images_per_prompt=lcm_diffusion_setting.number_of_images,
635
+ **pipeline_extra_args,
636
+ **controlnet_args,
637
+ ).images
638
+
639
+ if self.txt2img_pipeline: # In LCM or LCM-LoRA modes
640
+ self.pipeline = self.txt2img_pipeline
641
+ self.img_to_img_pipeline = self.img2img_pipeline
642
+
643
+ for i, seed in enumerate(seeds):
644
+ result_images[i].info["image_seed"] = seed
645
+
646
+ return result_images
647
+
648
+ def _init_gguf_diffusion(
649
+ self,
650
+ lcm_diffusion_setting: LCMDiffusionSetting,
651
+ ):
652
+ config = ModelConfig()
653
+ config.model_path = lcm_diffusion_setting.gguf_model.diffusion_path
654
+ config.diffusion_model_path = lcm_diffusion_setting.gguf_model.diffusion_path
655
+ config.clip_l_path = lcm_diffusion_setting.gguf_model.clip_path
656
+ config.t5xxl_path = lcm_diffusion_setting.gguf_model.t5xxl_path
657
+ config.vae_path = lcm_diffusion_setting.gguf_model.vae_path
658
+ config.n_threads = GGUF_THREADS
659
+ print(f"GGUF Threads : {GGUF_THREADS} ")
660
+ print("GGUF - Model config")
661
+ pprint(lcm_diffusion_setting.gguf_model.model_dump())
662
+ self.pipeline = GGUFDiffusion(
663
+ get_app_path(), # Place DLL in fastsdcpu folder
664
+ config,
665
+ True,
666
+ )
667
+
668
+ def _generate_images_gguf(
669
+ self,
670
+ lcm_diffusion_setting: LCMDiffusionSetting,
671
+ ):
672
+ if lcm_diffusion_setting.diffusion_task == DiffusionTask.text_to_image.value:
673
+ t2iconfig = Txt2ImgConfig()
674
+ t2iconfig.prompt = lcm_diffusion_setting.prompt
675
+ t2iconfig.batch_count = lcm_diffusion_setting.number_of_images
676
+ t2iconfig.cfg_scale = lcm_diffusion_setting.guidance_scale
677
+ t2iconfig.height = lcm_diffusion_setting.image_height
678
+ t2iconfig.width = lcm_diffusion_setting.image_width
679
+ t2iconfig.sample_steps = lcm_diffusion_setting.inference_steps
680
+ t2iconfig.sample_method = SampleMethod.EULER
681
+ if lcm_diffusion_setting.use_seed:
682
+ t2iconfig.seed = lcm_diffusion_setting.seed
683
+ else:
684
+ t2iconfig.seed = -1
685
+
686
+ return self.pipeline.generate_text2mg(t2iconfig)
src/backend/lora.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import glob
2
+ from os import path
3
+ from paths import get_file_name, FastStableDiffusionPaths
4
+ from pathlib import Path
5
+
6
+
7
+ class _lora_info:
8
+ """
9
+ A basic class to keep track of the currently loaded LoRAs and their weights.
10
+
11
+ The diffusers function _get_active_adapters()_ returns a list of adapter
12
+ names but not their weights so we need a way to keep track of the current
13
+ LoRA weights to set whenever a new LoRA is loaded.
14
+ """
15
+
16
+ def __init__(
17
+ self,
18
+ path: str,
19
+ weight: float,
20
+ ):
21
+ self.path = path
22
+ self.adapter_name = get_file_name(path)
23
+ self.weight = weight
24
+
25
+ def __del__(self):
26
+ self.path = None
27
+ self.adapter_name = None
28
+
29
+
30
+ _loaded_loras = []
31
+ _current_pipeline = None
32
+
33
+
34
+ def load_lora_weight(
35
+ pipeline,
36
+ lcm_diffusion_setting,
37
+ ):
38
+ """
39
+ Loads a LoRA from the LoRA path setting.
40
+
41
+ This function loads a LoRA from the LoRA path stored in the settings so
42
+ it's possible to load multiple LoRAs by calling this function more than
43
+ once with a different LoRA path setting; note that if you plan to load
44
+ multiple LoRAs and dynamically change their weights, you might want to
45
+ set the LoRA fuse option to _False_.
46
+ """
47
+ if not lcm_diffusion_setting.lora.path:
48
+ raise Exception("Empty lora model path")
49
+
50
+ if not path.exists(lcm_diffusion_setting.lora.path):
51
+ raise Exception("Lora model path is invalid")
52
+
53
+ # If the pipeline has been rebuilt since the last call, remove all
54
+ # references to previously loaded LoRAs and store the new pipeline
55
+ global _loaded_loras
56
+ global _current_pipeline
57
+ if pipeline != _current_pipeline:
58
+ reset_active_lora_weights()
59
+ _current_pipeline = pipeline
60
+
61
+ current_lora = _lora_info(
62
+ lcm_diffusion_setting.lora.path,
63
+ lcm_diffusion_setting.lora.weight,
64
+ )
65
+ _loaded_loras.append(current_lora)
66
+
67
+ if lcm_diffusion_setting.lora.enabled:
68
+ print(f"LoRA adapter name : {current_lora.adapter_name}")
69
+ pipeline.load_lora_weights(
70
+ FastStableDiffusionPaths.get_lora_models_path(),
71
+ weight_name=Path(lcm_diffusion_setting.lora.path).name,
72
+ local_files_only=True,
73
+ adapter_name=current_lora.adapter_name,
74
+ )
75
+ update_lora_weights(
76
+ pipeline,
77
+ lcm_diffusion_setting,
78
+ )
79
+
80
+ if lcm_diffusion_setting.lora.fuse:
81
+ pipeline.fuse_lora()
82
+
83
+
84
+ def get_lora_models(root_dir: str):
85
+ lora_models = glob.glob(f"{root_dir}/**/*.safetensors", recursive=True)
86
+ lora_models_map = {}
87
+ for file_path in lora_models:
88
+ lora_name = get_file_name(file_path)
89
+ if lora_name is not None:
90
+ lora_models_map[lora_name] = file_path
91
+ return lora_models_map
92
+
93
+
94
+ def get_active_lora_weights():
95
+ """
96
+ Returns a list of _(adapter_name, weight)_ tuples for the currently loaded LoRAs.
97
+ """
98
+ active_loras = []
99
+ for lora_info in _loaded_loras:
100
+ active_loras.append(
101
+ (
102
+ lora_info.adapter_name,
103
+ lora_info.weight,
104
+ )
105
+ )
106
+ return active_loras
107
+
108
+
109
+ def reset_active_lora_weights():
110
+ """
111
+ Clears the global list of active LoRA weights.
112
+
113
+ This method clears the list of active LoRA weights but it doesn't actually
114
+ remove the active LoRA weights from the current generation pipeline.
115
+ This method is only meant to be called when rebuilding the generation pipeline
116
+ as it will also clear the _current_pipeline_ variable; setting the
117
+ _current_pipeline_ variable to _None_ is safe here since the active LoRA weights
118
+ list is being reset, but it also helps to remove the pipeline reference that
119
+ might prevent the garbage collector from releasing the current pipeline memory.
120
+ """
121
+ global _loaded_loras
122
+ for lora in _loaded_loras:
123
+ del lora
124
+ del _loaded_loras
125
+ _loaded_loras = []
126
+
127
+ global _current_pipeline
128
+ _current_pipeline = None
129
+
130
+
131
+ def update_lora_weights(
132
+ pipeline,
133
+ lcm_diffusion_setting,
134
+ lora_weights=None,
135
+ ):
136
+ """
137
+ Updates the LoRA weights for the currently active LoRAs.
138
+
139
+ Args:
140
+ pipeline: The currently active pipeline.
141
+ lcm_diffusion_setting: The global settings, needed to verify if the
142
+ pipeline is running in LCM-LoRA mode.
143
+ lora_weights: An optional list of updated _(adapter_name, weight)_ tuples.
144
+ """
145
+ global _loaded_loras
146
+ global _current_pipeline
147
+ if pipeline != _current_pipeline:
148
+ print("Wrong pipeline when trying to update LoRA weights")
149
+ return
150
+ if lora_weights:
151
+ for idx, lora in enumerate(lora_weights):
152
+ if _loaded_loras[idx].adapter_name != lora[0]:
153
+ print("Wrong adapter name in LoRA enumeration!")
154
+ continue
155
+ _loaded_loras[idx].weight = lora[1]
156
+
157
+ adapter_names = []
158
+ adapter_weights = []
159
+ if lcm_diffusion_setting.use_lcm_lora:
160
+ adapter_names.append("lcm")
161
+ adapter_weights.append(1.0)
162
+ for lora in _loaded_loras:
163
+ adapter_names.append(lora.adapter_name)
164
+ adapter_weights.append(lora.weight)
165
+ pipeline.set_adapters(
166
+ adapter_names,
167
+ adapter_weights=adapter_weights,
168
+ )
169
+ adapter_weights = zip(adapter_names, adapter_weights)
170
+ print(f"Adapters: {list(adapter_weights)}")
src/backend/models/device.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class DeviceInfo(BaseModel):
5
+ device_type: str
6
+ device_name: str
7
+ os: str
8
+ platform: str
9
+ processor: str
src/backend/models/gen_images.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+ from enum import Enum
3
+ from paths import FastStableDiffusionPaths
4
+
5
+
6
+ class ImageFormat(str, Enum):
7
+ """Image format"""
8
+
9
+ JPEG = "jpeg"
10
+ PNG = "png"
11
+
12
+
13
+ class GeneratedImages(BaseModel):
14
+ path: str = FastStableDiffusionPaths.get_results_path()
15
+ format: str = ImageFormat.PNG.value.upper()
16
+ save_image: bool = True
17
+ save_image_quality: int = 90
src/backend/models/lcmdiffusion_setting.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from enum import Enum
2
+ from PIL import Image
3
+ from typing import Any, Optional, Union
4
+
5
+ from constants import LCM_DEFAULT_MODEL, LCM_DEFAULT_MODEL_OPENVINO
6
+ from paths import FastStableDiffusionPaths
7
+ from pydantic import BaseModel
8
+
9
+
10
+ class LCMLora(BaseModel):
11
+ base_model_id: str = "Lykon/dreamshaper-8"
12
+ lcm_lora_id: str = "latent-consistency/lcm-lora-sdv1-5"
13
+
14
+
15
+ class DiffusionTask(str, Enum):
16
+ """Diffusion task types"""
17
+
18
+ text_to_image = "text_to_image"
19
+ image_to_image = "image_to_image"
20
+
21
+
22
+ class Lora(BaseModel):
23
+ models_dir: str = FastStableDiffusionPaths.get_lora_models_path()
24
+ path: Optional[Any] = None
25
+ weight: Optional[float] = 0.5
26
+ fuse: bool = True
27
+ enabled: bool = False
28
+
29
+
30
+ class ControlNetSetting(BaseModel):
31
+ adapter_path: Optional[str] = None # ControlNet adapter path
32
+ conditioning_scale: float = 0.5
33
+ enabled: bool = False
34
+ _control_image: Image = None # Control image, PIL image
35
+
36
+
37
+ class GGUFModel(BaseModel):
38
+ gguf_models: str = FastStableDiffusionPaths.get_gguf_models_path()
39
+ diffusion_path: Optional[str] = None
40
+ clip_path: Optional[str] = None
41
+ t5xxl_path: Optional[str] = None
42
+ vae_path: Optional[str] = None
43
+
44
+
45
+ class LCMDiffusionSetting(BaseModel):
46
+ lcm_model_id: str = LCM_DEFAULT_MODEL
47
+ openvino_lcm_model_id: str = LCM_DEFAULT_MODEL_OPENVINO
48
+ use_offline_model: bool = False
49
+ use_lcm_lora: bool = False
50
+ lcm_lora: Optional[LCMLora] = LCMLora()
51
+ use_tiny_auto_encoder: bool = False
52
+ use_openvino: bool = False
53
+ prompt: str = ""
54
+ negative_prompt: str = ""
55
+ init_image: Any = None
56
+ strength: Optional[float] = 0.6
57
+ image_height: Optional[int] = 512
58
+ image_width: Optional[int] = 512
59
+ inference_steps: Optional[int] = 1
60
+ guidance_scale: Optional[float] = 1
61
+ clip_skip: Optional[int] = 1
62
+ token_merging: Optional[float] = 0
63
+ number_of_images: Optional[int] = 1
64
+ seed: Optional[int] = 123123
65
+ use_seed: bool = False
66
+ use_safety_checker: bool = False
67
+ diffusion_task: str = DiffusionTask.text_to_image.value
68
+ lora: Optional[Lora] = Lora()
69
+ controlnet: Optional[Union[ControlNetSetting, list[ControlNetSetting]]] = None
70
+ dirs: dict = {
71
+ "controlnet": FastStableDiffusionPaths.get_controlnet_models_path(),
72
+ "lora": FastStableDiffusionPaths.get_lora_models_path(),
73
+ }
74
+ rebuild_pipeline: bool = False
75
+ rebuild_controlnet_pipeline: bool = False
76
+ use_gguf_model: bool = False
77
+ gguf_model: Optional[GGUFModel] = GGUFModel()
src/backend/models/upscale.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from enum import Enum
2
+
3
+
4
+ class UpscaleMode(str, Enum):
5
+ """Diffusion task types"""
6
+
7
+ normal = "normal"
8
+ sd_upscale = "sd_upscale"
9
+ aura_sr = "aura_sr"