Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ce9c8bed8 | ||
|
|
25c15bffd8 | ||
|
|
8ff91690c1 | ||
|
|
97bbe0fe07 | ||
|
|
2d361c6636 | ||
|
|
9361634d83 | ||
|
|
7cf0363555 | ||
|
|
c2ee3b0ce1 | ||
|
|
ca6fc13171 | ||
|
|
534afbbbf9 | ||
|
|
cb1ea1f828 | ||
|
|
010e3be5dc | ||
|
|
d8dab1a66e | ||
|
|
97b236d04d | ||
|
|
15fe59cc19 | ||
|
|
89bec31540 | ||
|
|
7568a0ef86 | ||
|
|
6180222e15 | ||
|
|
6d45ce29e2 | ||
|
|
1da56d6d22 | ||
|
|
91a1c00442 | ||
|
|
3db942f1f6 | ||
|
|
2e56291649 | ||
|
|
b31badc6d1 | ||
|
|
ed399fcb21 | ||
|
|
d858c9394b | ||
|
|
a19a6599ec | ||
|
|
57c15f7c9f | ||
|
|
aa25ae8c87 | ||
|
|
c27055761e | ||
|
|
abf3024892 | ||
|
|
2182d4dde1 | ||
|
|
b632b927a3 | ||
|
|
4e2be12064 | ||
|
|
29b33cd327 | ||
|
|
032d0e1eae | ||
|
|
a3ddffeeee | ||
|
|
28a5ff2f9f | ||
|
|
1028e04e02 | ||
|
|
49c010db59 | ||
|
|
48ff60d3b8 | ||
|
|
9c5b993c4f | ||
|
|
a4aecc4d38 | ||
|
|
4d73230e84 | ||
|
|
a5a09f3e64 | ||
|
|
49894bd9b3 |
@@ -559,15 +559,15 @@ checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_log-sys"
|
||||
version = "0.3.1"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937"
|
||||
checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d"
|
||||
|
||||
[[package]]
|
||||
name = "android_logger"
|
||||
version = "0.14.1"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05b07e8e73d720a1f2e4b6014766e6039fd2e96a4fa44e2a78d0e1fa2ff49826"
|
||||
checksum = "f6f39be698127218cca460cb624878c9aa4e2b47dba3b277963d2bf00bad263b"
|
||||
dependencies = [
|
||||
"android_log-sys",
|
||||
"env_filter",
|
||||
@@ -661,7 +661,10 @@ dependencies = [
|
||||
"assertor",
|
||||
"base64 0.22.1",
|
||||
"bytes 1.10.0",
|
||||
"chrono",
|
||||
"const_format",
|
||||
"crockford",
|
||||
"cudarc",
|
||||
"directories 6.0.0",
|
||||
"dirs 6.0.0",
|
||||
"filesys",
|
||||
@@ -674,6 +677,7 @@ dependencies = [
|
||||
"ml_models",
|
||||
"ml_weights_registry",
|
||||
"once_cell",
|
||||
"ort",
|
||||
"primitives",
|
||||
"rand 0.8.5",
|
||||
"reqwest 0.12.12",
|
||||
@@ -1373,7 +1377,7 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1835,6 +1839,7 @@ dependencies = [
|
||||
"fancy-regex",
|
||||
"half",
|
||||
"intel-mkl-src",
|
||||
"log",
|
||||
"num-traits",
|
||||
"rand 0.8.5",
|
||||
"rayon",
|
||||
@@ -1971,9 +1976,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.39"
|
||||
version = "0.4.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
||||
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
@@ -1981,7 +1986,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.52.6",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3949,9 +3954,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.1"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64d6dafc854908ff5da46ff3f8f473c6984119a2876a383a860246dd7841a868"
|
||||
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
@@ -5458,9 +5463,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.5"
|
||||
version = "0.25.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b"
|
||||
checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
@@ -5489,6 +5494,16 @@ dependencies = [
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image_algorithms"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"errors",
|
||||
"image",
|
||||
"my-workspace-hack",
|
||||
"testing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imageproc"
|
||||
version = "0.24.0"
|
||||
@@ -6307,9 +6322,9 @@ checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.21"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
dependencies = [
|
||||
"value-bag",
|
||||
]
|
||||
@@ -6729,6 +6744,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6817,6 +6833,7 @@ dependencies = [
|
||||
"half",
|
||||
"hf-hub 0.3.2",
|
||||
"image",
|
||||
"image_algorithms",
|
||||
"libloading 0.8.6",
|
||||
"log",
|
||||
"memory_caching",
|
||||
@@ -6921,9 +6938,9 @@ dependencies = [
|
||||
"dpi",
|
||||
"gtk",
|
||||
"keyboard-types",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.2.2",
|
||||
"once_cell",
|
||||
"png",
|
||||
"serde",
|
||||
@@ -7449,6 +7466,15 @@ dependencies = [
|
||||
"objc2-encode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3531f65190d9cff863b77a99857e74c314dd16bf56c538c4b57c7cbc3f3a6e59"
|
||||
dependencies = [
|
||||
"objc2-encode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-app-kit"
|
||||
version = "0.2.2"
|
||||
@@ -7458,10 +7484,10 @@ dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"block2",
|
||||
"libc",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-core-data",
|
||||
"objc2-core-image",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-quartz-core",
|
||||
]
|
||||
|
||||
@@ -7473,9 +7499,9 @@ checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-core-location",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7485,8 +7511,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7497,8 +7523,8 @@ checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7508,8 +7534,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-metal",
|
||||
]
|
||||
|
||||
@@ -7520,9 +7546,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-contacts",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7540,7 +7566,17 @@ dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"block2",
|
||||
"libc",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-foundation"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a21c6c9014b82c39515db5b396f91645182611c97d24637cf56ac01e5f8d998"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"objc2 0.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7550,9 +7586,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7563,8 +7599,8 @@ checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7575,8 +7611,8 @@ checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-metal",
|
||||
]
|
||||
|
||||
@@ -7586,8 +7622,8 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7598,12 +7634,12 @@ checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-cloud-kit",
|
||||
"objc2-core-data",
|
||||
"objc2-core-image",
|
||||
"objc2-core-location",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-link-presentation",
|
||||
"objc2-quartz-core",
|
||||
"objc2-symbols",
|
||||
@@ -7618,8 +7654,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7630,9 +7666,9 @@ checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-core-location",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7643,9 +7679,9 @@ checksum = "68bc69301064cebefc6c4c90ce9cba69225239e4b8ff99d445a2b5563797da65"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7935,6 +7971,7 @@ dependencies = [
|
||||
"half",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"libloading 0.7.4",
|
||||
"ndarray",
|
||||
"tar",
|
||||
"thiserror 1.0.63",
|
||||
@@ -8419,15 +8456,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.10"
|
||||
version = "0.17.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64"
|
||||
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide 0.7.2",
|
||||
"miniz_oxide 0.8.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10635,8 +10672,8 @@ dependencies = [
|
||||
"foreign-types 0.5.0",
|
||||
"js-sys",
|
||||
"log",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-quartz-core",
|
||||
"raw-window-handle",
|
||||
"redox_syscall 0.5.2",
|
||||
@@ -11808,9 +11845,9 @@ dependencies = [
|
||||
"log",
|
||||
"mime",
|
||||
"muda",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.2.2",
|
||||
"percent-encoding",
|
||||
"plist",
|
||||
"raw-window-handle",
|
||||
@@ -11918,16 +11955,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-log"
|
||||
version = "2.2.1"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "367a28a5e0ca39eac98005699466e8906edc4a2a8f8e13a5f1a71dc0bea6c677"
|
||||
checksum = "2341d5b9bc5318c8e34f35a569140c78337241aa9c14091550b424c49f0314e0"
|
||||
dependencies = [
|
||||
"android_logger",
|
||||
"byte-unit",
|
||||
"fern",
|
||||
"log",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2 0.6.0",
|
||||
"objc2-foundation 0.3.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
@@ -11967,9 +12004,9 @@ dependencies = [
|
||||
"http 1.1.0",
|
||||
"jni",
|
||||
"log",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.2.2",
|
||||
"percent-encoding",
|
||||
"raw-window-handle",
|
||||
"softbuffer",
|
||||
@@ -12779,9 +12816,9 @@ dependencies = [
|
||||
"dirs 5.0.1",
|
||||
"libappindicator",
|
||||
"muda",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.2.2",
|
||||
"once_cell",
|
||||
"png",
|
||||
"serde",
|
||||
@@ -13752,9 +13789,9 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ea403deff7b51fff19e261330f71608ff2cdef5721d72b64180bb95be7c4150"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.2.2",
|
||||
"raw-window-handle",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-version",
|
||||
@@ -13853,6 +13890,12 @@ dependencies = [
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.2.0"
|
||||
@@ -14247,9 +14290,9 @@ dependencies = [
|
||||
"kuchikiki",
|
||||
"libc",
|
||||
"ndk 0.9.0",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-ui-kit",
|
||||
"objc2-web-kit",
|
||||
"once_cell",
|
||||
|
||||
@@ -58,6 +58,7 @@ members = [
|
||||
"crates/lib/files/mimetypes",
|
||||
"crates/lib/files/videos",
|
||||
"crates/lib/idempotency",
|
||||
"crates/lib/image_algorithms",
|
||||
"crates/lib/jobs_common",
|
||||
"crates/lib/logging",
|
||||
"crates/lib/markdown",
|
||||
|
||||
@@ -20,6 +20,7 @@ normal = ["my-workspace-hack"]
|
||||
|
||||
# Internal
|
||||
filesys = { path = "../../lib/files/filesys" }
|
||||
image_algorithms = { path = "../../lib/image_algorithms" }
|
||||
memory_caching = { path = "../../lib/caching/memory_caching" }
|
||||
ml_weights_registry = { path = "../ml_weights_registry" }
|
||||
primitives = { path = "../../lib/data/primitives" }
|
||||
|
||||
@@ -3,6 +3,7 @@ use image::{imageops, DynamicImage};
|
||||
use ndarray::{Array, CowArray};
|
||||
use ort::Value;
|
||||
use std::path::Path;
|
||||
use image_algorithms::remove_image_border::remove_image_border;
|
||||
|
||||
pub async fn remove_image_background<P: AsRef<Path>>(model_path: P, image: DynamicImage) -> anyhow::Result<DynamicImage> {
|
||||
// NB: Real time needed cuDNN 9, but this uses cuDNN 8:
|
||||
@@ -11,6 +12,10 @@ pub async fn remove_image_background<P: AsRef<Path>>(model_path: P, image: Dynam
|
||||
// update-alternatives: using /usr/include/x86_64-linux-gnu/cudnn_v8.h to provide /usr/include/cudnn.h (libcudnn) in manual mode
|
||||
let session = onnx_session(model_path.as_ref())?;
|
||||
let image = process_dynamic_image(&session, image)?;
|
||||
|
||||
// Crop transparent border cruft away.
|
||||
let image = remove_image_border(&image)?;
|
||||
|
||||
Ok(image)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
|
||||
use crate::ml::image::dynamic_image_to_tensor::dynamic_image_to_tensor;
|
||||
use crate::ml::image::tensor_to_image_buffer::{tensor_to_image_buffer, RgbImage};
|
||||
use crate::ml::model_cache::ModelCache;
|
||||
use crate::ml::prompt_cache::PromptCache;
|
||||
use candle_nn::VarBuilder;
|
||||
use crate::ml::model_config::ModelConfig;
|
||||
use anyhow::{anyhow, Error as E, Result};
|
||||
use candle_core::{DType, IndexOp, Tensor, D, Device, Module};
|
||||
use candle_transformers::models::stable_diffusion::vae::{AutoEncoderKL, DiagonalGaussianDistribution};
|
||||
use candle_transformers::models::stable_diffusion::lcm::LCMScheduler;
|
||||
use image::DynamicImage;
|
||||
use log::{debug, info};
|
||||
use rand::Rng;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
use candle_transformers::models::{clip, t5, flux};
|
||||
use tokenizers::Tokenizer;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FluxModel {
|
||||
Schnell,
|
||||
Dev,
|
||||
}
|
||||
|
||||
pub struct FluxPipelineArgs<'a> {
|
||||
pub image: &'a DynamicImage,
|
||||
pub prompt: String,
|
||||
pub model_cache: &'a ModelCache,
|
||||
pub prompt_cache: &'a PromptCache,
|
||||
pub app: &'a AppHandle,
|
||||
pub flux_model: FluxModel,
|
||||
pub active_blocks: Option<usize>,
|
||||
pub use_cuda_stream: Option<bool>,
|
||||
pub prefetch_next_batch: Option<bool>,
|
||||
pub use_full_gpu: Option<bool>,
|
||||
pub img2img_strength: Option<f64>,
|
||||
pub seed: Option<u64>,
|
||||
pub use_quantized_model: Option<bool>,
|
||||
pub model_config: &'a ModelConfig,
|
||||
pub flux_dev_or_schnell_path: &'a Path,
|
||||
pub flux_schnell_quantized_gguf: &'a Path,
|
||||
pub flux_autoencoder_path: &'a Path,
|
||||
pub google_t5_model_path: &'a Path,
|
||||
pub google_t5_config_path: &'a Path,
|
||||
pub lmz_t5_tokenizer_path: &'a Path,
|
||||
pub openai_clip_model_path: &'a Path,
|
||||
pub openai_clip_tokenizer_json_path: &'a Path,
|
||||
}
|
||||
|
||||
pub fn flux_pipeline(args: FluxPipelineArgs<'_>) -> Result<RgbImage> {
|
||||
let FluxPipelineArgs {
|
||||
prompt,
|
||||
model_cache,
|
||||
prompt_cache,
|
||||
app,
|
||||
image,
|
||||
flux_model,
|
||||
active_blocks,
|
||||
use_cuda_stream,
|
||||
prefetch_next_batch,
|
||||
use_full_gpu,
|
||||
img2img_strength,
|
||||
seed,
|
||||
use_quantized_model,
|
||||
model_config ,
|
||||
flux_dev_or_schnell_path,
|
||||
flux_schnell_quantized_gguf,
|
||||
flux_autoencoder_path,
|
||||
google_t5_model_path,
|
||||
google_t5_config_path,
|
||||
lmz_t5_tokenizer_path,
|
||||
openai_clip_model_path,
|
||||
openai_clip_tokenizer_json_path,
|
||||
} = args;
|
||||
|
||||
if use_quantized_model.unwrap_or(false) {
|
||||
#[cfg(feature = "cuda")]
|
||||
candle_core::quantized::cuda::set_force_dmmv(false);
|
||||
}
|
||||
|
||||
let height = 1024;
|
||||
let width = 1024;
|
||||
|
||||
let quantized = use_quantized_model.unwrap_or(false);
|
||||
let device = model_config.device.clone();
|
||||
let dtype = device.bf16_default_to_f32();
|
||||
|
||||
info!("Starting image generation with FLUX model:");
|
||||
info!(" Model: {:?}", flux_model);
|
||||
info!(" Prompt: {}", prompt);
|
||||
info!(" Dimensions: {}x{}", width, height);
|
||||
info!(" Device: {:?}", model_config.device);
|
||||
|
||||
let seed_value = seed.unwrap_or_else(|| rand::thread_rng().gen());
|
||||
device.set_seed(seed_value)?;
|
||||
|
||||
info!("Using seed: {}", seed_value);
|
||||
|
||||
// Setup FLUX model configuration
|
||||
let flux_config = match flux_model {
|
||||
FluxModel::Dev => flux::model::Config::dev(),
|
||||
FluxModel::Schnell => flux::model::Config::schnell(),
|
||||
};
|
||||
|
||||
// Get T5 embeddings
|
||||
let t5_emb = {
|
||||
info!("Generating T5 embeddings for prompt...");
|
||||
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&[google_t5_model_path], dtype , &device)? };
|
||||
let config = std::fs::read_to_string(google_t5_config_path)?;
|
||||
let config: t5::Config = serde_json::from_str(&config)?;
|
||||
|
||||
let mut model = t5::T5EncoderModel::load(vb, &config)?;
|
||||
let tokenizer = Tokenizer::from_file(lmz_t5_tokenizer_path).map_err(E::msg)?;
|
||||
|
||||
let mut tokens = tokenizer.encode(prompt.as_str(), true).map_err(E::msg)?.get_ids().to_vec();
|
||||
tokens.resize(256, 0);
|
||||
let input_token_ids = Tensor::new(&tokens[..], &device)?.unsqueeze(0)?;
|
||||
|
||||
model.forward(&input_token_ids)?
|
||||
};
|
||||
|
||||
info!("T5 embeddings generated");
|
||||
|
||||
// Get CLIP embeddings
|
||||
let clip_emb = {
|
||||
info!("Generating CLIP embeddings for prompt...");
|
||||
let vb =
|
||||
unsafe { VarBuilder::from_mmaped_safetensors(&[openai_clip_model_path], dtype, &device)? };
|
||||
// https://huggingface.co/openai/clip-vit-large-patch14/blob/main/config.json
|
||||
let config = clip::text_model::ClipTextConfig {
|
||||
vocab_size: 49408,
|
||||
projection_dim: 768,
|
||||
activation: clip::text_model::Activation::QuickGelu,
|
||||
intermediate_size: 3072,
|
||||
embed_dim: 768,
|
||||
max_position_embeddings: 77,
|
||||
pad_with: None,
|
||||
num_hidden_layers: 12,
|
||||
num_attention_heads: 12,
|
||||
};
|
||||
let model =
|
||||
clip::text_model::ClipTextTransformer::new(vb.pp("text_model"), &config)?;
|
||||
let tokenizer = Tokenizer::from_file(openai_clip_tokenizer_json_path).map_err(E::msg)?;
|
||||
let tokens = tokenizer
|
||||
.encode(prompt.as_str(), true)
|
||||
.map_err(E::msg)?
|
||||
.get_ids()
|
||||
.to_vec();
|
||||
let input_token_ids = Tensor::new(&tokens[..], &device)?.unsqueeze(0)?;
|
||||
model.forward(&input_token_ids)?
|
||||
};
|
||||
info!("CLIP embeddings generated");
|
||||
|
||||
// Process input image to tensor
|
||||
info!("Converting input image to tensor...");
|
||||
let img_tensor = dynamic_image_to_tensor(image, &device, dtype)?;
|
||||
info!("Image tensor shape: {:?}", img_tensor.shape());
|
||||
|
||||
// Create initial noise - this is important to match the expected format
|
||||
info!("Generating initial noise for FLUX...");
|
||||
let initial_tensor = flux::sampling::get_noise(1, height, width, &device)?.to_dtype(dtype)?;
|
||||
info!("Initial tensor shape: {:?}", initial_tensor.shape());
|
||||
|
||||
// Blend initial noise with input image if img2img_strength is specified
|
||||
let initial_tensor = if let Some(strength) = img2img_strength {
|
||||
if strength > 0.0 && strength < 1.0 {
|
||||
info!("Using img2img with strength: {}", strength);
|
||||
// Reshape the input tensor to match the noise tensor dimensions
|
||||
let adjusted_img = if img_tensor.shape() != initial_tensor.shape() {
|
||||
info!("Reshaping input image to match noise dimensions...");
|
||||
// You may need more complex reshaping logic here depending on your use case
|
||||
let (_, c, h, w) = initial_tensor.dims4()?;
|
||||
img_tensor.upsample_nearest2d(h, w)?
|
||||
} else {
|
||||
img_tensor
|
||||
};
|
||||
|
||||
// Blend the noise and the image according to strength
|
||||
let strength_tensor = Tensor::new(strength as f32, &device)?;
|
||||
let inverse_strength = Tensor::new(1.0 - strength as f32, &device)?;
|
||||
let weighted_noise = (&initial_tensor * &strength_tensor)?;
|
||||
let weighted_img = (&adjusted_img * &inverse_strength)?;
|
||||
(&weighted_noise + &weighted_img)?
|
||||
} else {
|
||||
initial_tensor
|
||||
}
|
||||
} else {
|
||||
initial_tensor
|
||||
};
|
||||
info!("Final initial tensor shape: {:?}", initial_tensor.shape());
|
||||
|
||||
// Create FLUX state
|
||||
info!("Creating FLUX state...");
|
||||
let state = if quantized {
|
||||
flux::sampling::State::new(
|
||||
&t5_emb.to_dtype(DType::F32)?,
|
||||
&clip_emb.to_dtype(DType::F32)?,
|
||||
&initial_tensor.to_dtype(DType::F32)?,
|
||||
)?
|
||||
} else {
|
||||
flux::sampling::State::new(&t5_emb, &clip_emb, &initial_tensor)?
|
||||
};
|
||||
info!("{state:?}");
|
||||
|
||||
// Set appropriate timesteps based on model
|
||||
let timesteps = match flux_model {
|
||||
FluxModel::Dev => flux::sampling::get_schedule(28, Some((state.img.dim(1)?, 0.5, 1.15))),
|
||||
FluxModel::Schnell => flux::sampling::get_schedule(4, None),
|
||||
};
|
||||
|
||||
info!("Starting FLUX denoising with timesteps: {:?}", timesteps);
|
||||
|
||||
// Actual model loading and inference
|
||||
let img_latent = if quantized {
|
||||
// Quantized model path
|
||||
info!("Loading quantized FLUX model...");
|
||||
let model_file = match flux_model {
|
||||
FluxModel::Schnell => flux_schnell_quantized_gguf,
|
||||
FluxModel::Dev => return Err(anyhow!("Quantized model not available for FLUX.1-dev")),
|
||||
};
|
||||
|
||||
info!("Loading quantized model from {}", model_file.to_str().unwrap());
|
||||
|
||||
let vb = candle_transformers::quantized_var_builder::VarBuilder::from_gguf(model_file, &device)?;
|
||||
let mut model = flux::quantized_model::Flux::new(&flux_config, vb)?;
|
||||
info!("Quantized model loaded on {:?}", device);
|
||||
|
||||
// Run denoising
|
||||
info!("Running denoising...");
|
||||
let img_latent = flux::sampling::denoise(&mut model, &state.img, &state.img_ids, &state.txt, &state.txt_ids, &state.vec, ×teps, 4.)?.to_dtype(dtype)?;
|
||||
info!("Denoising completed");
|
||||
img_latent
|
||||
} else {
|
||||
// Full precision model
|
||||
info!("Loading FLUX model...");
|
||||
|
||||
let mut model = if use_full_gpu.unwrap_or(false) {
|
||||
// Load entire model on GPU
|
||||
info!("Loading entire model directly to GPU...");
|
||||
let gpu_vb = unsafe { VarBuilder::from_mmaped_safetensors(&[flux_dev_or_schnell_path], dtype, &device)? };
|
||||
let mut model = flux::model::Flux::new(&flux_config, gpu_vb)?;
|
||||
model.set_use_device_management(false);
|
||||
model
|
||||
} else {
|
||||
// Use memory-efficient approach with blocks on CPU
|
||||
info!("Using memory-efficient approach with blocks on CPU...");
|
||||
let cpu_device = candle_core::Device::Cpu;
|
||||
let cpu_vb = unsafe { VarBuilder::from_mmaped_safetensors(&[flux_dev_or_schnell_path], dtype, &cpu_device)? };
|
||||
let mut model = flux::model::Flux::new_with_gpu_core(&flux_config, cpu_vb, &device)?;
|
||||
|
||||
// Set active blocks
|
||||
let active_blocks_count = active_blocks.unwrap_or(1);
|
||||
model.set_max_active_blocks(active_blocks_count);
|
||||
info!("Using {} active blocks on GPU", active_blocks_count);
|
||||
|
||||
// Enable prefetching if requested
|
||||
if prefetch_next_batch.unwrap_or(false) {
|
||||
model.set_prefetch_next_batch(true);
|
||||
info!("Prefetching enabled - will overlap transfers with computation");
|
||||
}
|
||||
|
||||
model
|
||||
};
|
||||
|
||||
// Run denoising
|
||||
flux::sampling::denoise(&mut model, &state.img, &state.img_ids, &state.txt, &state.txt_ids, &state.vec, ×teps, 4.)?
|
||||
};
|
||||
|
||||
info!("FLUX denoising completed, unpacking latent image...");
|
||||
let img_unpacked = flux::sampling::unpack(&img_latent, height, width)?;
|
||||
|
||||
// Load autoencoder for final decoding
|
||||
info!("Loading autoencoder for final decoding...");
|
||||
|
||||
let ae_config = match flux_model {
|
||||
FluxModel::Dev => flux::autoencoder::Config::dev(),
|
||||
FluxModel::Schnell => flux::autoencoder::Config::schnell(),
|
||||
};
|
||||
|
||||
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&[flux_autoencoder_path], dtype, &device)? };
|
||||
let ae_model = flux::autoencoder::AutoEncoder::new(&ae_config, vb)?;
|
||||
|
||||
info!("Decoding final image...");
|
||||
let img = ae_model.decode(&img_unpacked)?;
|
||||
|
||||
// Convert tensor to image
|
||||
info!("Converting tensor to image...");
|
||||
let img = ((img.clamp(-1f32, 1f32)? + 1.0)? * 127.5)?.to_dtype(candle_core::DType::U8)?;
|
||||
|
||||
// Convert to RGB image
|
||||
let image = tensor_to_image_buffer(&img.i(0)?)?;
|
||||
|
||||
info!("FLUX pipeline completed successfully");
|
||||
Ok(image)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod flux_pipeline;
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod background_removal;
|
||||
pub mod flux;
|
||||
pub mod image;
|
||||
pub mod model_cache;
|
||||
pub mod model_config;
|
||||
|
||||
@@ -20,12 +20,14 @@ use crate::ml::stable_diffusion::get_vae_scale::get_vae_scale;
|
||||
use crate::ml::stable_diffusion::infer_clip_text_embeddings::{infer_clip_text_embeddings, ClipArgs};
|
||||
use crate::ml::stable_diffusion::remap_lcm_strength_range::remap_lcm_strength_range;
|
||||
use image::DynamicImage;
|
||||
use log::info;
|
||||
use log::{debug, error, info};
|
||||
use ml_weights_registry::weights_registry::weights::{LYKON_DEAMSHAPER_7_TEXT_ENCODER_FP16, LYKON_DEAMSHAPER_7_VAE, SIMIANLUO_LCM_DREAMSHAPER_V7_UNET};
|
||||
use rand::Rng;
|
||||
|
||||
const DEFAULT_STRENGTH : f64 = 75.0;
|
||||
|
||||
const DEFAULT_SEED : u64 = 42;
|
||||
|
||||
pub struct Args<'a, P1: AsRef<Path>, P2: AsRef<Path>, P3: AsRef<Path>, P4: AsRef<Path>> {
|
||||
pub image: &'a DynamicImage,
|
||||
pub prompt: String,
|
||||
@@ -85,10 +87,9 @@ pub fn lcm_pipeline<P1, P2, P3, P4>(args: Args<'_, P1, P2, P3, P4>) -> Result<Rg
|
||||
// Use LCM Scheduler instead of Euler Ancestral for better speed and quality
|
||||
let mut scheduler = LCMScheduler::new(scheduler_steps, img2img_strength, LCMSchedulerConfig::default())?;
|
||||
|
||||
let seed = maybe_seed.unwrap_or_else(|| rand::thread_rng().gen());
|
||||
//let seed = maybe_seed.unwrap_or_else(|| rand::thread_rng().gen());
|
||||
let seed = maybe_seed.unwrap_or(DEFAULT_SEED);
|
||||
|
||||
let seed = 42;
|
||||
|
||||
device.set_seed(seed)?;
|
||||
|
||||
let guidance_scale = match cfg_scale {
|
||||
@@ -129,13 +130,13 @@ pub fn lcm_pipeline<P1, P2, P3, P4>(args: Args<'_, P1, P2, P3, P4>) -> Result<Rg
|
||||
tensor
|
||||
};
|
||||
|
||||
info!("Text embeddings shape: {:?}", text_embeddings.shape());
|
||||
debug!("Text embeddings shape: {:?}", text_embeddings.shape());
|
||||
|
||||
info!("Loading input image into tensor...");
|
||||
debug!("Loading input image into tensor...");
|
||||
|
||||
let input_image = dynamic_image_to_tensor(image, &device, dtype)?;
|
||||
|
||||
info!("Reference image shape: {:?}", input_image.shape());
|
||||
debug!("Reference image shape: {:?}", input_image.shape());
|
||||
|
||||
let vae_scale = get_vae_scale(sd_version);
|
||||
|
||||
@@ -161,7 +162,7 @@ pub fn lcm_pipeline<P1, P2, P3, P4>(args: Args<'_, P1, P2, P3, P4>) -> Result<Rg
|
||||
let unet = match maybe_unet {
|
||||
Some(unet) => unet,
|
||||
None => {
|
||||
info!("No unet found in cache; loading...");
|
||||
info!("Building UNET model from file {:?}...", unet_path.as_ref());
|
||||
|
||||
let in_channels = match sd_version {
|
||||
StableDiffusionVersion::XlInpaint | StableDiffusionVersion::V2Inpaint | StableDiffusionVersion::V1_5Inpaint => 9,
|
||||
@@ -217,16 +218,17 @@ pub fn lcm_pipeline<P1, P2, P3, P4>(args: Args<'_, P1, P2, P3, P4>) -> Result<Rg
|
||||
let guidance_scale_embedding = scheduler.get_guidance_scale_embedding(guidance_scale, embedding_dim, &device, dtype)?;
|
||||
|
||||
// Generate latents from input image using the LCM approach
|
||||
info!("Generating latents from input image...");
|
||||
debug!("Generating latents from input image...");
|
||||
let init_latents = (init_latent_dist.sample()? * vae_scale)?;
|
||||
info!("Initial latents shape: {:?}", init_latents.shape());
|
||||
|
||||
debug!("Initial latents shape: {:?}", init_latents.shape());
|
||||
|
||||
// Calculate timesteps for LCM with proper img2img handling
|
||||
let timesteps = LCMScheduler::get_timesteps_for_steps(scheduler_steps, img2img_strength);
|
||||
let t_start = timesteps[0]; // First denoising step
|
||||
|
||||
// Add noise at the appropriate timestep for img2img
|
||||
info!("Adding noise to latents for t_start={}", t_start);
|
||||
debug!("Adding noise to latents for t_start={}", t_start);
|
||||
let noise = init_latents.randn_like(0f64, 1f64)?;
|
||||
let latents = scheduler.add_noise(&init_latents, noise, t_start)?;
|
||||
|
||||
@@ -244,7 +246,7 @@ pub fn lcm_pipeline<P1, P2, P3, P4>(args: Args<'_, P1, P2, P3, P4>) -> Result<Rg
|
||||
let mut noise_pred = match unet.forward_with_guidance(&latent_model_input, timestep as f64, &text_embeddings, Some(&guidance_scale_embedding)) {
|
||||
Ok(pred) => pred,
|
||||
Err(e) => {
|
||||
info!("UNet inference failed with error: {}", e);
|
||||
error!("UNet inference failed with error: {}", e);
|
||||
return Err(anyhow::anyhow!("UNet inference failed: {}", e));
|
||||
},
|
||||
};
|
||||
@@ -259,21 +261,19 @@ pub fn lcm_pipeline<P1, P2, P3, P4>(args: Args<'_, P1, P2, P3, P4>) -> Result<Rg
|
||||
latents = scheduler.step(&noise_pred, timestep, &latents, timestep_index, scheduler_steps)?;
|
||||
}
|
||||
|
||||
info!("Diffusion process completed, decoding image...");
|
||||
debug!("Diffusion process completed, decoding image...");
|
||||
let image = vae.decode(&(latents / vae_scale)?)?;
|
||||
|
||||
info!("VAE decode completed");
|
||||
|
||||
info!("Scaling image back");
|
||||
debug!("Scaling image back");
|
||||
let image = ((image / 2.)? + 0.5)?;
|
||||
|
||||
info!("Normalized image values");
|
||||
debug!("Normalized image values");
|
||||
let image = (image.clamp(0f32, 1.)? * 255.)?;
|
||||
|
||||
info!("Convert to int8");
|
||||
debug!("Convert to int8");
|
||||
let image = image.to_dtype(DType::U8)?;
|
||||
|
||||
info!("Converted to 8-bit format");
|
||||
debug!("Converted to 8-bit format");
|
||||
|
||||
let image = tensor_to_image_buffer(&image.i(0)?)?;
|
||||
|
||||
|
||||
@@ -59,14 +59,6 @@ pub fn stable_diffusion_pipeline<P1, P2, P3, P4>(args: Args<'_, P1, P2, P3, P4>)
|
||||
clip_weights_path,
|
||||
} = args;
|
||||
|
||||
println!("Starting image generation with the following configuration:");
|
||||
println!(" Model: {:?}", sd_version);
|
||||
println!(" Prompt: {}", prompt);
|
||||
println!(" Steps: {}", scheduler_steps);
|
||||
println!(" Device: {:?}", device);
|
||||
|
||||
println!("Model dimensions: {}x{}", sd_config.width, sd_config.height);
|
||||
|
||||
// TODO(bt,2025-02-18): The scheduler is `EulerAncestralDiscreteScheduler`, but we may want to port an LCM scheduler.
|
||||
// This is a target for performance improvement
|
||||
// See: https://github.com/huggingface/candle/issues/1331
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
use crate::weights_registry::weight_descriptor::{WeightDescriptor, WeightFunction};
|
||||
use crate::weights_registry::weight_descriptor_builder::weight;
|
||||
|
||||
|
||||
/// https://huggingface.co/black-forest-labs/FLUX.1-dev
|
||||
pub const FLUX_DEV : WeightDescriptor = weight!(
|
||||
"Flux-1 Dev",
|
||||
"flux1-dev.safetensors",
|
||||
WeightFunction::ImageGeneration
|
||||
);
|
||||
|
||||
// TODO(bt): I think the autoencoder for Dev and Schnell are the same.
|
||||
// They have the same checksum unless I mixed up the files when downloading.
|
||||
|
||||
/// https://huggingface.co/black-forest-labs/FLUX.1-dev
|
||||
pub const FLUX_DEV_AUTOENCODER : WeightDescriptor = weight!(
|
||||
"Flux-1 Dev (Autoencoder)",
|
||||
"flux1-dev.ae.safetensors",
|
||||
WeightFunction::ImageGeneration
|
||||
);
|
||||
|
||||
/// https://huggingface.co/black-forest-labs/FLUX.1-schnell
|
||||
pub const FLUX_SCHNELL : WeightDescriptor = weight!(
|
||||
"Flux-1 Schnell",
|
||||
"flux1-schnell.safetensors",
|
||||
WeightFunction::ImageGeneration
|
||||
);
|
||||
|
||||
/// https://huggingface.co/black-forest-labs/FLUX.1-schnell
|
||||
pub const FLUX_SCHNELL_AUTOENCODER : WeightDescriptor = weight!(
|
||||
"Flux-1 Schnell (Autoencoder)",
|
||||
"flux1-schnell.ae.safetensors",
|
||||
WeightFunction::ImageGeneration
|
||||
);
|
||||
|
||||
/// https://huggingface.co/lmz/candle-flux
|
||||
pub const LMZ_CANDLE_FLUX_SCHNELL_QUANTIZED_GGUF : WeightDescriptor = weight!(
|
||||
"LMZ Flux Schnell Quantized",
|
||||
"lmz_candle_flux_flux1-schnell.gguf",
|
||||
WeightFunction::ImageGeneration
|
||||
);
|
||||
|
||||
/// https://huggingface.co/google/t5-v1_1-xxl/tree/main
|
||||
/// NB: I couldn't find the safetensors file here
|
||||
/// NB: This should live under "refs/pr/2"
|
||||
pub const GOOGLE_T5_V1_1_XXL_MODEL : WeightDescriptor = weight!(
|
||||
"Google T5 v1.1 (XXL)",
|
||||
"google_t5_v1_1_xxl_model.safetensors",
|
||||
WeightFunction::TextEncoder
|
||||
);
|
||||
|
||||
/// https://huggingface.co/google/t5-v1_1-xxl/tree/main
|
||||
/// NB: I couldn't find the safetensors file here
|
||||
/// NB: This should live under "refs/pr/2"
|
||||
pub const GOOGLE_T5_V1_1_XXL_CONFIG : WeightDescriptor = weight!(
|
||||
"Google T5 v1.1 (XXL) Config",
|
||||
"google_t5_v1_1_xxl_config.json",
|
||||
WeightFunction::TextEncoder
|
||||
);
|
||||
|
||||
/// https://huggingface.co/lmz/mt5-tokenizers
|
||||
pub const LMZ_T5_TOKENIZER_JSON : WeightDescriptor = weight!(
|
||||
"LMZ T5 Tokenizer",
|
||||
"lmz_mt5_tokenizers_t5-v1_1-xxl.tokenizer.json",
|
||||
WeightFunction::TextTokenizer
|
||||
);
|
||||
|
||||
/// https://huggingface.co/openai/clip-vit-large-patch14/tree/main
|
||||
pub const OPENAI_CLIP_VIT_P14_MODEL : WeightDescriptor = weight!(
|
||||
"OpenAI CLIP VIT (patch 14) model",
|
||||
"openai_clip-vit-large-patch14.model.safetensors",
|
||||
WeightFunction::TextTokenizer
|
||||
);
|
||||
|
||||
/// https://huggingface.co/openai/clip-vit-large-patch14/tree/main
|
||||
pub const OPENAI_CLIP_VIT_P14_TOKENIZER_JSON: WeightDescriptor = weight!(
|
||||
"OpenAI CLIP VIT (patch 14) tokenizer",
|
||||
"openai_clip-vit-large-patch14.tokenizer.json",
|
||||
WeightFunction::TextTokenizer
|
||||
);
|
||||
@@ -1,3 +1,4 @@
|
||||
mod weight_descriptor_builder;
|
||||
pub mod flux_weights;
|
||||
pub mod weight_descriptor;
|
||||
pub mod weights;
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum WeightFunction {
|
||||
ImageGeneration,
|
||||
ImageSegmentation,
|
||||
TextEncoder,
|
||||
TextTokenizer,
|
||||
@@ -29,6 +30,7 @@ pub struct WeightDescriptor {
|
||||
impl WeightFunction {
|
||||
pub fn as_descriptive_str(&self) -> &'static str {
|
||||
match self {
|
||||
WeightFunction::ImageGeneration => "image generation",
|
||||
WeightFunction::ImageSegmentation => "image segmentation",
|
||||
WeightFunction::TextEncoder => "text encoder",
|
||||
WeightFunction::TextTokenizer => "text tokenizer",
|
||||
|
||||
@@ -21,6 +21,7 @@ tauri-build = { version = "2.0.4", features = [] }
|
||||
[dependencies]
|
||||
|
||||
# Internal
|
||||
crockford = { path = "../../lib/data/crockford" }
|
||||
filesys = { path = "../../lib/files/filesys" }
|
||||
memory_caching = { path = "../../lib/caching/memory_caching" }
|
||||
ml_weights_registry = { path = "../ml_weights_registry" }
|
||||
@@ -29,12 +30,16 @@ primitives = { path = "../../lib/data/primitives" }
|
||||
# Internal ML
|
||||
# TODO: Only on Windows and Linux
|
||||
#ml_models = { path = "../ml_models", optional = true }
|
||||
# Bypass cudarc issues on Windows https://github.com/coreylowman/cudarc/issues/332
|
||||
cudarc = { version = "0.13.5", features = ["std", "cublas", "cublaslt", "curand", "driver", "nvrtc", "f16", "cuda-version-from-build-system", "dynamic-linking"], default-features=false }
|
||||
|
||||
# External
|
||||
#openssl = { version = "0.10", features = ["vendored"] }
|
||||
# NB(bt,2025-03-10): default-features = false on Windows while I debug openssl / rustls
|
||||
anyhow = "1.0"
|
||||
base64 = "0.22.1"
|
||||
bytes = "1.10"
|
||||
chrono = "0.4.40"
|
||||
const_format = "0.2.34"
|
||||
directories = "6.0.0"
|
||||
dirs = "6.0.0"
|
||||
@@ -43,8 +48,8 @@ half = { version = "2.3.1", features = ["num-traits", "use-intrinsics", "rand_di
|
||||
image = "0.25.5"
|
||||
log = "0.4"
|
||||
once_cell = "1.20.3"
|
||||
ort = { version = "1.16.2", features = ["cuda", "load-dynamic"] }
|
||||
rand = "0.8.5"
|
||||
# NB(bt,2025-03-10): default-features = false on Windows while I debug openssl / rustls
|
||||
reqwest = { version = "0.12.12", features = ["stream"], default-features = false }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
use log::info;
|
||||
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn inpaint_image(
|
||||
image: &str,
|
||||
mask: &str,
|
||||
prompt: String,
|
||||
) -> Result<String, String> {
|
||||
info!("inpaint_endpoint endpoint called.");
|
||||
|
||||
Ok(mask.to_string())
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod download_models;
|
||||
pub mod flip_image;
|
||||
pub mod inpaint_image_endpoint;
|
||||
pub mod realtime_image_endpoint;
|
||||
pub mod remove_background_endpoint;
|
||||
pub mod save_image_endpoint;
|
||||
pub mod text_to_image_endpoint;
|
||||
|
||||
@@ -31,6 +31,7 @@ pub async fn infer_image(
|
||||
image: &str,
|
||||
prompt: Option<String>,
|
||||
strength: Option<f64>,
|
||||
seed: Option<u64>,
|
||||
model_config: State<'_, AppConfig>,
|
||||
model_cache: State<'_, ModelCache>,
|
||||
prompt_cache: State<'_, PromptCache>,
|
||||
@@ -49,12 +50,15 @@ pub async fn infer_image(
|
||||
// TODO(bt,2025-02-17): Running out of vram with full image buffer size
|
||||
let mut image = image.resize(512, 512, FilterType::CatmullRom);
|
||||
|
||||
let seed = seed.or(model_config.seed);
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
let result = infer_image_impl(
|
||||
&prompt,
|
||||
image,
|
||||
strength,
|
||||
seed,
|
||||
&model_config,
|
||||
&model_cache,
|
||||
prompt_cache,
|
||||
@@ -83,6 +87,7 @@ async fn infer_image_impl(
|
||||
prompt: &str,
|
||||
image: DynamicImage,
|
||||
strength: Option<f64>,
|
||||
seed: Option<u64>,
|
||||
config: &AppConfig,
|
||||
model_cache: &ModelCache,
|
||||
prompt_cache: State<'_, PromptCache>,
|
||||
@@ -106,7 +111,7 @@ async fn infer_image_impl(
|
||||
cfg_scale: config.cfg_scale,
|
||||
use_flash_attn: true,
|
||||
model_config: &config.model_config,
|
||||
maybe_seed: None,
|
||||
maybe_seed: seed,
|
||||
scheduler_steps: config.scheduler_steps,
|
||||
vae_path: &vae_path,
|
||||
unet_path: &unet_path,
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
use crate::state::app_config::AppConfig;
|
||||
use crate::state::app_dir::AppDataRoot;
|
||||
use crate::stubs::model_cache::ModelCache;
|
||||
use crate::stubs::prompt_cache::PromptCache;
|
||||
use crate::utils::image::decode_base64_image::decode_base64_image;
|
||||
use chrono::Local;
|
||||
use crockford::crockford_entropy_lower;
|
||||
use image::{ImageFormat, RgbImage};
|
||||
use log::info;
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
const FILENAME_LENGTH : usize = 10;
|
||||
|
||||
/// This handler takes an image (as a base64 encoded string) and saves it.
|
||||
#[tauri::command]
|
||||
pub async fn save_image(
|
||||
image: &str,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
) -> Result<String, String> {
|
||||
info!("save_image endpoint called.");
|
||||
|
||||
let image = decode_base64_image(image)
|
||||
.map_err(|err| format!("Couldn't hydrate image from base64: {}", err))?;
|
||||
|
||||
let assets_dir = app_data_root.assets_dir();
|
||||
|
||||
let date_save_dir = assets_dir.make_or_get_current_date_dir()
|
||||
.map_err(|err| format!("Couldn't create assets save directory: {}", err))?;
|
||||
|
||||
let entropy = crockford_entropy_lower(FILENAME_LENGTH); // TODO: These are ugly. A monotonic counter would be better.
|
||||
let filename = format!("{}.png", entropy);
|
||||
let filename = date_save_dir.join(filename);
|
||||
|
||||
image.save_with_format(filename, ImageFormat::Png)
|
||||
.map_err(|err| format!("Couldn't save image: {}", err))?;
|
||||
|
||||
Ok("".to_string())
|
||||
}
|
||||
@@ -1,23 +1,21 @@
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
use ml_models::ml::stable_diffusion::lcm_pipeline::{lcm_pipeline, Args};
|
||||
use ml_models::ml::flux::flux_pipeline::{flux_pipeline, FluxModel, FluxPipelineArgs};
|
||||
|
||||
use crate::state::app_config::AppConfig;
|
||||
use crate::state::app_dir::AppDataRoot;
|
||||
use crate::state::app_dir::{AppDataRoot, AppWeightsDir};
|
||||
use crate::stubs::model_cache::ModelCache;
|
||||
use crate::stubs::prompt_cache::PromptCache;
|
||||
use crate::utils::image::encode_dynamic_image_base64_png::encode_dynamic_image_base64_png;
|
||||
use image::imageops::FilterType;
|
||||
use image::{DynamicImage, ImageReader, RgbImage};
|
||||
use log::{error, info};
|
||||
use ml_weights_registry::weights_registry::weights::{CLIP_JSON, LYKON_DEAMSHAPER_7_VAE, SDXL_TURBO_CLIP_TEXT_ENCODER, SIMIANLUO_LCM_DREAMSHAPER_V7_UNET};
|
||||
|
||||
use ml_weights_registry::weights_registry::flux_weights::{FLUX_DEV, FLUX_SCHNELL, FLUX_SCHNELL_AUTOENCODER, GOOGLE_T5_V1_1_XXL_CONFIG, GOOGLE_T5_V1_1_XXL_MODEL, LMZ_CANDLE_FLUX_SCHNELL_QUANTIZED_GGUF, LMZ_T5_TOKENIZER_JSON, OPENAI_CLIP_VIT_P14_MODEL, OPENAI_CLIP_VIT_P14_TOKENIZER_JSON};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::io::Cursor;
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
const RANDOM_SEED: u32 = 42;
|
||||
|
||||
const STRENGTH: f64 = 1.0;
|
||||
const STRENGTH: f64 = 100.0;
|
||||
const PNG_BYTES : &[u8] = include_bytes!("../../binary_includes/1024.png");
|
||||
|
||||
// NB: We're just hacking a temporary "text to image" test endpoint leveraging the existing image to image modality.
|
||||
@@ -35,8 +33,8 @@ pub async fn text_to_image(
|
||||
model_config: State<'_, AppConfig>,
|
||||
model_cache: State<'_, ModelCache>,
|
||||
prompt_cache: State<'_, PromptCache>,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
app: AppHandle,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
) -> Result<String, String> {
|
||||
info!("text_to_image endpoint called.");
|
||||
|
||||
@@ -51,8 +49,8 @@ pub async fn text_to_image(
|
||||
&model_config,
|
||||
&model_cache,
|
||||
prompt_cache,
|
||||
app_data_root.weights_dir(),
|
||||
app,
|
||||
&app_data_root
|
||||
).await;
|
||||
|
||||
image = match result {
|
||||
@@ -80,35 +78,36 @@ async fn text_to_image_impl(
|
||||
config: &AppConfig,
|
||||
model_cache: &ModelCache,
|
||||
prompt_cache: State<'_, PromptCache>,
|
||||
weights_dir: &AppWeightsDir,
|
||||
app: AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
) -> Result<RgbImage, String> {
|
||||
|
||||
let weights_dir = app_data_root.weights_dir();
|
||||
let vae_path = weights_dir.weight_path(&LYKON_DEAMSHAPER_7_VAE);
|
||||
let unet_path = weights_dir.weight_path(&SIMIANLUO_LCM_DREAMSHAPER_V7_UNET);
|
||||
let clip_json_path = weights_dir.weight_path(&CLIP_JSON);
|
||||
let clip_weights_path= weights_dir.weight_path(&SDXL_TURBO_CLIP_TEXT_ENCODER);
|
||||
|
||||
let args = Args {
|
||||
let args = FluxPipelineArgs {
|
||||
image: &image,
|
||||
prompt: prompt.to_string(),
|
||||
uncond_prompt: "".to_string(),
|
||||
model_cache,
|
||||
prompt_cache: &prompt_cache,
|
||||
app: &app,
|
||||
flux_model: FluxModel::Schnell,
|
||||
active_blocks: None,
|
||||
use_cuda_stream: Some(true),
|
||||
prefetch_next_batch: Some(false),
|
||||
use_full_gpu: Some(true),
|
||||
img2img_strength: strength,
|
||||
cfg_scale: config.cfg_scale,
|
||||
use_flash_attn: true,
|
||||
seed: None,
|
||||
use_quantized_model: Some(true),
|
||||
model_config: &config.model_config,
|
||||
maybe_seed: None,
|
||||
scheduler_steps: config.scheduler_steps,
|
||||
vae_path: &vae_path,
|
||||
unet_path: &unet_path,
|
||||
clip_json_path: &clip_json_path,
|
||||
clip_weights_path: &clip_weights_path,
|
||||
flux_dev_or_schnell_path: &weights_dir.weight_path(&FLUX_SCHNELL),
|
||||
flux_schnell_quantized_gguf: &weights_dir.weight_path(&LMZ_CANDLE_FLUX_SCHNELL_QUANTIZED_GGUF),
|
||||
flux_autoencoder_path: &weights_dir.weight_path(&FLUX_SCHNELL_AUTOENCODER),
|
||||
google_t5_model_path: &weights_dir.weight_path(&GOOGLE_T5_V1_1_XXL_MODEL),
|
||||
google_t5_config_path: &weights_dir.weight_path(&GOOGLE_T5_V1_1_XXL_CONFIG),
|
||||
lmz_t5_tokenizer_path: &weights_dir.weight_path(&LMZ_T5_TOKENIZER_JSON),
|
||||
openai_clip_model_path: &weights_dir.weight_path(&OPENAI_CLIP_VIT_P14_MODEL),
|
||||
openai_clip_tokenizer_json_path: &weights_dir.weight_path(&OPENAI_CLIP_VIT_P14_TOKENIZER_JSON),
|
||||
};
|
||||
|
||||
let image = lcm_pipeline(args)
|
||||
let image = flux_pipeline(args)
|
||||
.map_err(|err| format!("failure to encode image: {:?}", err))?;
|
||||
|
||||
Ok(image)
|
||||
|
||||
@@ -8,6 +8,9 @@ pub enum NotificationModelType {
|
||||
TextTokenizer,
|
||||
Unet,
|
||||
Vae,
|
||||
Json,
|
||||
ClipEncoder,
|
||||
FluxModel,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
@@ -26,5 +29,17 @@ pub enum NotificationEvent<'a> {
|
||||
ModelDownloadComplete {
|
||||
model_name: &'a str,
|
||||
model_type: NotificationModelType,
|
||||
}
|
||||
},
|
||||
ModelLoading {
|
||||
model_name: String,
|
||||
model_type: NotificationModelType,
|
||||
},
|
||||
ModelInference {
|
||||
model_name: String,
|
||||
model_type: NotificationModelType,
|
||||
},
|
||||
ModelInferenceComplete {
|
||||
model_name: String,
|
||||
model_type: NotificationModelType,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -8,8 +8,10 @@ pub mod utils;
|
||||
|
||||
use crate::endpoints::download_models::download_models;
|
||||
use crate::endpoints::flip_image::flip_image;
|
||||
use crate::endpoints::inpaint_image_endpoint::inpaint_image;
|
||||
use crate::endpoints::realtime_image_endpoint::infer_image;
|
||||
use crate::endpoints::remove_background_endpoint::remove_background;
|
||||
use crate::endpoints::save_image_endpoint::save_image;
|
||||
use crate::endpoints::text_to_image_endpoint::text_to_image;
|
||||
use crate::state::app_config::AppConfig;
|
||||
use crate::stubs::model_cache::ModelCache;
|
||||
@@ -28,7 +30,6 @@ pub fn run() {
|
||||
let config = AppConfig::init()
|
||||
.expect("config should load");
|
||||
|
||||
|
||||
let prompt_cache = PromptCache::with_capacity(8)
|
||||
.expect("prompt cache should load");
|
||||
|
||||
@@ -44,7 +45,10 @@ pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_log::Builder::new()
|
||||
.level(log::LevelFilter::Info)
|
||||
.targets(vec![Target::new(TargetKind::Stdout)])
|
||||
.targets(vec![
|
||||
Target::new(TargetKind::Stdout),
|
||||
Target::new(TargetKind::LogDir { file_name: Some(app_data_root.log_file_name_str().to_string()) }),
|
||||
])
|
||||
.build())
|
||||
.setup(|app| {
|
||||
// TODO(bt): This is broken on windows
|
||||
@@ -71,7 +75,10 @@ pub fn run() {
|
||||
download_models,
|
||||
flip_image,
|
||||
infer_image,
|
||||
inpaint_image,
|
||||
remove_background,
|
||||
save_image,
|
||||
text_to_image,
|
||||
text_to_image,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
|
||||
@@ -4,6 +4,7 @@ use anyhow::anyhow;
|
||||
use directories::UserDirs;
|
||||
use ml_weights_registry::weights_registry::weight_descriptor::WeightDescriptor;
|
||||
use std::path::{Path, PathBuf};
|
||||
use chrono::{DateTime, Local};
|
||||
use tempdir::TempDir;
|
||||
use tempfile::{Builder, NamedTempFile};
|
||||
|
||||
@@ -13,6 +14,9 @@ const WEIGHTS_SUBDIRECTORY : &str = "weights";
|
||||
|
||||
const TEMPORARY_SUBDIRECTORY : &str = "temp";
|
||||
|
||||
/// Note: Tauri appends ".log" to the end of the filename.
|
||||
const LOG_FILE_NAME : &str = "application_debug";
|
||||
|
||||
/// The path to the application data directory, which includes "asset" and "weights" data.
|
||||
#[derive(Clone)]
|
||||
pub struct AppDataRoot {
|
||||
@@ -20,6 +24,8 @@ pub struct AppDataRoot {
|
||||
assets_dir: AppAssetsDir,
|
||||
weights_dir: AppWeightsDir,
|
||||
temp_dir: TemporaryDir,
|
||||
log_file_name: PathBuf,
|
||||
log_file_name_string: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -70,12 +76,19 @@ impl AppDataRoot {
|
||||
let assets_dir = AppAssetsDir::create_existing(dir.join(ASSETS_SUBDIRECTORY))?;
|
||||
let weights_dir = AppWeightsDir::create_existing(dir.join(WEIGHTS_SUBDIRECTORY))?;
|
||||
let temp_dir = TemporaryDir::create_existing(dir.join(TEMPORARY_SUBDIRECTORY))?;
|
||||
let log_file_name = dir.join(LOG_FILE_NAME);
|
||||
let log_file_name_string = log_file_name
|
||||
.to_str()
|
||||
.ok_or(anyhow!("couldn't convert log path to str"))?
|
||||
.to_string();
|
||||
|
||||
Ok(Self {
|
||||
path: dir,
|
||||
assets_dir,
|
||||
weights_dir,
|
||||
temp_dir,
|
||||
log_file_name,
|
||||
log_file_name_string,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -94,6 +107,14 @@ impl AppDataRoot {
|
||||
pub fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn log_file_name(&self) -> &Path {
|
||||
&self.log_file_name
|
||||
}
|
||||
|
||||
pub fn log_file_name_str(&self) -> &str {
|
||||
&self.log_file_name_string
|
||||
}
|
||||
}
|
||||
|
||||
impl AppAssetsDir {
|
||||
@@ -117,6 +138,18 @@ impl AppAssetsDir {
|
||||
pub fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
|
||||
/// Return the current date asset directory
|
||||
/// If it doesn't exist, create it.
|
||||
pub fn make_or_get_current_date_dir(&self) -> anyhow::Result<PathBuf> {
|
||||
let format: DateTime<Local> = Local::now();
|
||||
let date_directory_name = format.format("%Y-%d-%m").to_string();
|
||||
let full_path = self.path.join(date_directory_name);
|
||||
if !full_path.exists() {
|
||||
std::fs::create_dir(&full_path)?;
|
||||
}
|
||||
Ok(full_path)
|
||||
}
|
||||
}
|
||||
|
||||
impl AppWeightsDir {
|
||||
|
||||
@@ -13,6 +13,7 @@ use std::sync::{Arc, Mutex};
|
||||
use tauri::{AppHandle, Emitter};
|
||||
use tempfile::NamedTempFile;
|
||||
use tokio::task::JoinHandle;
|
||||
use ml_weights_registry::weights_registry::flux_weights::{FLUX_SCHNELL, FLUX_SCHNELL_AUTOENCODER, GOOGLE_T5_V1_1_XXL_CONFIG, GOOGLE_T5_V1_1_XXL_MODEL, LMZ_CANDLE_FLUX_SCHNELL_QUANTIZED_GGUF, LMZ_T5_TOKENIZER_JSON, OPENAI_CLIP_VIT_P14_MODEL, OPENAI_CLIP_VIT_P14_TOKENIZER_JSON};
|
||||
|
||||
const NOTIFICATION_CHANNEL_NAME : &str = "notification";
|
||||
const MAX_FILES : usize = 8;
|
||||
@@ -50,6 +51,16 @@ pub async fn downloader_thread(app_data_root: AppDataRoot, app: AppHandle) -> !
|
||||
//download_queue.push_back(SDXL_TURBO_VAE); // TODO(bt): REMOVE
|
||||
//download_queue.push_back(SDXL_TURBO_UNET); // TODO(bt): REMOVE
|
||||
|
||||
// Flux:
|
||||
download_queue.push_back(FLUX_SCHNELL);
|
||||
download_queue.push_back(FLUX_SCHNELL_AUTOENCODER);
|
||||
download_queue.push_back(LMZ_CANDLE_FLUX_SCHNELL_QUANTIZED_GGUF);
|
||||
download_queue.push_back(GOOGLE_T5_V1_1_XXL_MODEL);
|
||||
download_queue.push_back(GOOGLE_T5_V1_1_XXL_CONFIG);
|
||||
download_queue.push_back(LMZ_T5_TOKENIZER_JSON);
|
||||
download_queue.push_back(OPENAI_CLIP_VIT_P14_MODEL);
|
||||
download_queue.push_back(OPENAI_CLIP_VIT_P14_TOKENIZER_JSON);
|
||||
|
||||
let mut handles = FuturesUnordered::new();
|
||||
|
||||
while let Some(model) = download_queue.pop_front() {
|
||||
@@ -82,6 +93,7 @@ pub async fn downloader_thread(app_data_root: AppDataRoot, app: AppHandle) -> !
|
||||
WeightFunction::Unet => NotificationModelType::Unet,
|
||||
WeightFunction::Vae => NotificationModelType::Vae,
|
||||
WeightFunction::ImageSegmentation => NotificationModelType::ImageSegmentation,
|
||||
WeightFunction::ImageGeneration => NotificationModelType::ImageSegmentation, // TODO FIX
|
||||
};
|
||||
|
||||
let result = app.emit(NOTIFICATION_CHANNEL_NAME, NotificationEvent::ModelDownloadStarted {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
use actix_cors::Cors;
|
||||
use crate::util::netlify_branch_domain_matches::netlify_branch_domain_matches;
|
||||
|
||||
pub fn add_artcraft_2d(cors: Cors, _is_production: bool) -> Cors {
|
||||
cors
|
||||
// Hypothetical domains
|
||||
.allowed_origin("https://2d.storyteller.ai")
|
||||
.allowed_origin("https://2d.getartcraft.com")
|
||||
// Netlify project
|
||||
.allowed_origin_fn(|origin, _req_head| {
|
||||
netlify_branch_domain_matches(origin, "storyteller-2d.netlify.app")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
use actix_cors::Cors;
|
||||
use crate::util::netlify_branch_domain_matches::netlify_branch_domain_matches;
|
||||
|
||||
pub fn add_artcraft_3d(cors: Cors, _is_production: bool) -> Cors {
|
||||
cors
|
||||
// Hypothetical domains
|
||||
.allowed_origin("https://3d.storyteller.ai")
|
||||
.allowed_origin("https://3d.getartcraft.com")
|
||||
// Netlify project
|
||||
.allowed_origin_fn(|origin, _req_head| {
|
||||
netlify_branch_domain_matches(origin, "storyteller-3d.netlify.app")
|
||||
})
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
pub (crate) mod artcraft_2d;
|
||||
pub (crate) mod artcraft_3d;
|
||||
pub (crate) mod development_only;
|
||||
pub (crate) mod fakeyou;
|
||||
pub (crate) mod legacy;
|
||||
|
||||
@@ -2,7 +2,8 @@ use actix_cors::Cors;
|
||||
use log::info;
|
||||
|
||||
use reusable_types::server_environment::ServerEnvironment;
|
||||
|
||||
use crate::configs::artcraft_2d::add_artcraft_2d;
|
||||
use crate::configs::artcraft_3d::add_artcraft_3d;
|
||||
use crate::configs::development_only::add_development_only;
|
||||
use crate::configs::fakeyou::{add_fakeyou, add_fakeyou_dev_proxy};
|
||||
use crate::configs::legacy::{add_legacy_storyteller_stream, add_legacy_trumped, add_legacy_vocodes, add_power_stream};
|
||||
@@ -40,6 +41,10 @@ fn do_build_cors_config(is_production: bool) -> Cors {
|
||||
cors = add_storyteller_board(cors, is_production);
|
||||
cors = add_storyteller_render(cors, is_production);
|
||||
|
||||
// Artcraft
|
||||
cors = add_artcraft_2d(cors, is_production);
|
||||
cors = add_artcraft_3d(cors, is_production);
|
||||
|
||||
// Legacy
|
||||
cors = add_legacy_trumped(cors, is_production);
|
||||
cors = add_power_stream(cors, is_production);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "image_algorithms"
|
||||
edition = "2018"
|
||||
version = "0.0.1"
|
||||
authors = [
|
||||
"Brandon Thomas <bt@brand.io>",
|
||||
"Brandon Thomas <echelon@gmail.com>",
|
||||
]
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "image_algorithms"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
normal = ["my-workspace-hack"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
# Workspace hack (faster compile times)
|
||||
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
|
||||
|
||||
# Internal
|
||||
errors = { path = "../errors" }
|
||||
|
||||
# External
|
||||
image = "0.25.6"
|
||||
|
||||
[dev-dependencies]
|
||||
testing = { path = "../testing" }
|
||||
@@ -0,0 +1 @@
|
||||
pub mod remove_image_border;
|
||||
@@ -0,0 +1,78 @@
|
||||
use image::{DynamicImage, Pixel};
|
||||
use errors::AnyhowResult;
|
||||
|
||||
pub fn remove_image_border(image: &DynamicImage) -> AnyhowResult<DynamicImage> {
|
||||
let mut image = image.to_rgba8();
|
||||
|
||||
let mut top = 0u32;
|
||||
let mut bottom = image.height();
|
||||
let mut left = 0u32;
|
||||
let mut right = image.width();
|
||||
|
||||
// TODO: Iterate with binary search - it'll probably be much faster.
|
||||
|
||||
for (i, mut row) in image.rows().enumerate() {
|
||||
let count_non_transparent = row.filter(|pixel| pixel.0[3] != 0).count();
|
||||
if count_non_transparent < 5 {
|
||||
top = i as u32;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (i, mut row) in image.rows().enumerate().rev() {
|
||||
//let all_transparent = row.all(|pixel| pixel.0[3] == 0);
|
||||
let count_non_transparent = row.filter(|pixel| pixel.0[3] != 0).count();
|
||||
//if all_transparent {
|
||||
if count_non_transparent < 5 {
|
||||
bottom = i as u32;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (x, y, pixel) in image.enumerate_pixels() {
|
||||
|
||||
}
|
||||
|
||||
for (y, mut row) in image.enumerate_rows() {
|
||||
for (x, y, pixel) in row {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let image = DynamicImage::ImageRgba8(image);
|
||||
|
||||
let width = image.width();
|
||||
let height = bottom - top;
|
||||
|
||||
println!("top: {}, bottom: {}", top, bottom);
|
||||
println!("orig width: {}, height: {}", image.width(), image.height());
|
||||
println!("new width: {}, height: {}", width, height);
|
||||
|
||||
let image = image.crop_imm(0, top, width, height);
|
||||
|
||||
Ok(image)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use image::{ImageFormat, ImageReader};
|
||||
use errors::AnyhowResult;
|
||||
use testing::test_file_path::test_file_path;
|
||||
use crate::remove_image_border::remove_image_border;
|
||||
|
||||
#[ignore] // NB: Not going to check these files into the repo. Just for manual testing.
|
||||
#[test]
|
||||
fn test_crop_image() -> AnyhowResult<()> {
|
||||
let filename = test_file_path("test_data/image/mario.png")?;
|
||||
let image = ImageReader::open(filename)?.decode()?;
|
||||
|
||||
let image = remove_image_border(&image)?;
|
||||
|
||||
image.save_with_format("testing_output.png", ImageFormat::Png)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -15,252 +15,281 @@ use tokenizers::Tokenizer;
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// The prompt to be used for image generation.
|
||||
#[arg(long, default_value = "A rusty robot walking on a beach")]
|
||||
prompt: String,
|
||||
/// The prompt to be used for image generation.
|
||||
#[arg(long, default_value = "A rusty robot walking on a beach")]
|
||||
prompt: String,
|
||||
|
||||
/// Run on CPU rather than on GPU.
|
||||
#[arg(long)]
|
||||
cpu: bool,
|
||||
/// Run on CPU rather than on GPU.
|
||||
#[arg(long)]
|
||||
cpu: bool,
|
||||
|
||||
/// Use the quantized model.
|
||||
#[arg(long)]
|
||||
quantized: bool,
|
||||
/// Use the quantized model.
|
||||
#[arg(long)]
|
||||
quantized: bool,
|
||||
|
||||
/// Enable tracing (generates a trace-timestamp.json file).
|
||||
#[arg(long)]
|
||||
tracing: bool,
|
||||
/// Enable tracing (generates a trace-timestamp.json file).
|
||||
#[arg(long)]
|
||||
tracing: bool,
|
||||
|
||||
/// The height in pixels of the generated image.
|
||||
#[arg(long)]
|
||||
height: Option<usize>,
|
||||
/// The height in pixels of the generated image.
|
||||
#[arg(long)]
|
||||
height: Option<usize>,
|
||||
|
||||
/// The width in pixels of the generated image.
|
||||
#[arg(long)]
|
||||
width: Option<usize>,
|
||||
/// The width in pixels of the generated image.
|
||||
#[arg(long)]
|
||||
width: Option<usize>,
|
||||
|
||||
#[arg(long)]
|
||||
decode_only: Option<String>,
|
||||
#[arg(long)]
|
||||
decode_only: Option<String>,
|
||||
|
||||
#[arg(long, value_enum, default_value = "schnell")]
|
||||
model: Model,
|
||||
#[arg(long, value_enum, default_value = "schnell")]
|
||||
model: Model,
|
||||
|
||||
/// Use the slower kernels.
|
||||
#[arg(long)]
|
||||
use_dmmv: bool,
|
||||
/// Use the slower kernels.
|
||||
#[arg(long)]
|
||||
use_dmmv: bool,
|
||||
|
||||
/// The seed to use when generating random samples.
|
||||
#[arg(long)]
|
||||
seed: Option<u64>,
|
||||
/// The seed to use when generating random samples.
|
||||
#[arg(long)]
|
||||
seed: Option<u64>,
|
||||
|
||||
#[arg(long)]
|
||||
cache_path: Option<String>,
|
||||
|
||||
/// Number of blocks to keep active on GPU (default: 1).
|
||||
/// Only relevant when NOT using --use_full_gpu.
|
||||
/// Higher values use more memory but may improve performance.
|
||||
#[arg(long, default_value = "1")]
|
||||
active_blocks: usize,
|
||||
|
||||
/// Use CUDA stream for faster device transfers (default: true).
|
||||
/// Only relevant when running on GPU.
|
||||
#[arg(long, default_value = "true")]
|
||||
use_cuda_stream: bool,
|
||||
|
||||
/// Enable prefetching of the next batch of blocks while processing the current batch.
|
||||
/// Only relevant when NOT using --use_full_gpu.
|
||||
/// This can improve performance by overlapping memory transfers and computation.
|
||||
#[arg(long, default_value = "false")]
|
||||
prefetch_next_batch: bool,
|
||||
|
||||
/// Load entire model on GPU instead of using memory-efficient active blocks approach.
|
||||
/// This uses more GPU memory but avoids CPU-GPU transfers completely.
|
||||
/// When this flag is enabled, the --active_blocks and --prefetch_next_batch flags have no effect.
|
||||
#[arg(long, default_value = "false")]
|
||||
use_full_gpu: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, clap::ValueEnum, PartialEq, Eq)]
|
||||
enum Model {
|
||||
Schnell,
|
||||
Dev,
|
||||
Schnell,
|
||||
Dev,
|
||||
}
|
||||
|
||||
fn run(args: Args) -> Result<()> {
|
||||
use tracing_chrome::ChromeLayerBuilder;
|
||||
use tracing_subscriber::prelude::*;
|
||||
use tracing_chrome::ChromeLayerBuilder;
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
let Args {
|
||||
prompt,
|
||||
cpu,
|
||||
height,
|
||||
width,
|
||||
tracing,
|
||||
decode_only,
|
||||
model,
|
||||
quantized,
|
||||
..
|
||||
} = args;
|
||||
let width = width.unwrap_or(1360);
|
||||
let height = height.unwrap_or(768);
|
||||
let Args { prompt, cpu, height, width, tracing, decode_only, model, quantized, active_blocks, use_cuda_stream, prefetch_next_batch, use_full_gpu, .. } = args;
|
||||
let width = width.unwrap_or(1360);
|
||||
let height = height.unwrap_or(768);
|
||||
|
||||
let _guard = if tracing {
|
||||
let (chrome_layer, guard) = ChromeLayerBuilder::new().build();
|
||||
tracing_subscriber::registry().with(chrome_layer).init();
|
||||
Some(guard)
|
||||
} else {
|
||||
None
|
||||
let _guard = if tracing {
|
||||
let (chrome_layer, guard) = ChromeLayerBuilder::new().build();
|
||||
tracing_subscriber::registry().with(chrome_layer).init();
|
||||
Some(guard)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let api = match args.cache_path.as_ref() {
|
||||
Some(path) => hf_hub::api::sync::ApiBuilder::from_cache(hf_hub::Cache::new(path.to_string().into())).build().map_err(anyhow::Error::msg)?,
|
||||
None => {
|
||||
let cache = hf_hub::Cache::from_env();
|
||||
hf_hub::api::sync::ApiBuilder::from_cache(cache).build().map_err(anyhow::Error::msg)?
|
||||
},
|
||||
};
|
||||
let bf_repo = {
|
||||
let name = match model {
|
||||
Model::Dev => "black-forest-labs/FLUX.1-dev",
|
||||
Model::Schnell => "black-forest-labs/FLUX.1-schnell",
|
||||
};
|
||||
api.repo(hf_hub::Repo::model(name.to_string()))
|
||||
};
|
||||
|
||||
let api = hf_hub::api::sync::Api::new()?;
|
||||
let bf_repo = {
|
||||
let name = match model {
|
||||
Model::Dev => "black-forest-labs/FLUX.1-dev",
|
||||
Model::Schnell => "black-forest-labs/FLUX.1-schnell",
|
||||
};
|
||||
api.repo(hf_hub::Repo::model(name.to_string()))
|
||||
};
|
||||
let device = candle_examples::device(cpu)?;
|
||||
if let Some(seed) = args.seed {
|
||||
device.set_seed(seed)?;
|
||||
// Use CUDA with stream for faster device transfers when not in CPU mode
|
||||
let device = if cpu {
|
||||
candle::Device::Cpu
|
||||
} else {
|
||||
#[cfg(feature = "cuda")]
|
||||
{
|
||||
if use_cuda_stream {
|
||||
println!("Using CUDA with dedicated stream for faster device transfers");
|
||||
// Use device ordinal 0 (first GPU)
|
||||
candle::Device::new_cuda_with_stream(0)?
|
||||
} else {
|
||||
println!("Using standard CUDA device (no dedicated stream)");
|
||||
candle::Device::new_cuda(0)?
|
||||
}
|
||||
}
|
||||
let dtype = device.bf16_default_to_f32();
|
||||
let img = match decode_only {
|
||||
None => {
|
||||
let t5_emb = {
|
||||
let repo = api.repo(hf_hub::Repo::with_revision(
|
||||
"google/t5-v1_1-xxl".to_string(),
|
||||
hf_hub::RepoType::Model,
|
||||
"refs/pr/2".to_string(),
|
||||
));
|
||||
let model_file = repo.get("model.safetensors")?;
|
||||
let vb =
|
||||
unsafe { VarBuilder::from_mmaped_safetensors(&[model_file], dtype, &device)? };
|
||||
let config_filename = repo.get("config.json")?;
|
||||
let config = std::fs::read_to_string(config_filename)?;
|
||||
let config: t5::Config = serde_json::from_str(&config)?;
|
||||
let mut model = t5::T5EncoderModel::load(vb, &config)?;
|
||||
let tokenizer_filename = api
|
||||
.model("lmz/mt5-tokenizers".to_string())
|
||||
.get("t5-v1_1-xxl.tokenizer.json")?;
|
||||
let tokenizer = Tokenizer::from_file(tokenizer_filename).map_err(E::msg)?;
|
||||
let mut tokens = tokenizer
|
||||
.encode(prompt.as_str(), true)
|
||||
.map_err(E::msg)?
|
||||
.get_ids()
|
||||
.to_vec();
|
||||
tokens.resize(256, 0);
|
||||
let input_token_ids = Tensor::new(&tokens[..], &device)?.unsqueeze(0)?;
|
||||
println!("{input_token_ids}");
|
||||
model.forward(&input_token_ids)?
|
||||
};
|
||||
println!("T5\n{t5_emb}");
|
||||
let clip_emb = {
|
||||
let repo = api.repo(hf_hub::Repo::model(
|
||||
"openai/clip-vit-large-patch14".to_string(),
|
||||
));
|
||||
let model_file = repo.get("model.safetensors")?;
|
||||
let vb =
|
||||
unsafe { VarBuilder::from_mmaped_safetensors(&[model_file], dtype, &device)? };
|
||||
// https://huggingface.co/openai/clip-vit-large-patch14/blob/main/config.json
|
||||
let config = clip::text_model::ClipTextConfig {
|
||||
vocab_size: 49408,
|
||||
projection_dim: 768,
|
||||
activation: clip::text_model::Activation::QuickGelu,
|
||||
intermediate_size: 3072,
|
||||
embed_dim: 768,
|
||||
max_position_embeddings: 77,
|
||||
pad_with: None,
|
||||
num_hidden_layers: 12,
|
||||
num_attention_heads: 12,
|
||||
};
|
||||
let model =
|
||||
clip::text_model::ClipTextTransformer::new(vb.pp("text_model"), &config)?;
|
||||
let tokenizer_filename = repo.get("tokenizer.json")?;
|
||||
let tokenizer = Tokenizer::from_file(tokenizer_filename).map_err(E::msg)?;
|
||||
let tokens = tokenizer
|
||||
.encode(prompt.as_str(), true)
|
||||
.map_err(E::msg)?
|
||||
.get_ids()
|
||||
.to_vec();
|
||||
let input_token_ids = Tensor::new(&tokens[..], &device)?.unsqueeze(0)?;
|
||||
println!("{input_token_ids}");
|
||||
model.forward(&input_token_ids)?
|
||||
};
|
||||
println!("CLIP\n{clip_emb}");
|
||||
let img = {
|
||||
let cfg = match model {
|
||||
Model::Dev => flux::model::Config::dev(),
|
||||
Model::Schnell => flux::model::Config::schnell(),
|
||||
};
|
||||
let img = flux::sampling::get_noise(1, height, width, &device)?.to_dtype(dtype)?;
|
||||
let state = if quantized {
|
||||
flux::sampling::State::new(
|
||||
&t5_emb.to_dtype(candle::DType::F32)?,
|
||||
&clip_emb.to_dtype(candle::DType::F32)?,
|
||||
&img.to_dtype(candle::DType::F32)?,
|
||||
)?
|
||||
} else {
|
||||
flux::sampling::State::new(&t5_emb, &clip_emb, &img)?
|
||||
};
|
||||
let timesteps = match model {
|
||||
Model::Dev => {
|
||||
flux::sampling::get_schedule(50, Some((state.img.dim(1)?, 0.5, 1.15)))
|
||||
}
|
||||
Model::Schnell => flux::sampling::get_schedule(4, None),
|
||||
};
|
||||
println!("{state:?}");
|
||||
println!("{timesteps:?}");
|
||||
if quantized {
|
||||
let model_file = match model {
|
||||
Model::Schnell => api
|
||||
.repo(hf_hub::Repo::model("lmz/candle-flux".to_string()))
|
||||
.get("flux1-schnell.gguf")?,
|
||||
Model::Dev => todo!(),
|
||||
};
|
||||
let vb = candle_transformers::quantized_var_builder::VarBuilder::from_gguf(
|
||||
model_file, &device,
|
||||
)?;
|
||||
#[cfg(not(feature = "cuda"))]
|
||||
{
|
||||
println!("CUDA not available, using CPU");
|
||||
candle::Device::Cpu
|
||||
}
|
||||
};
|
||||
|
||||
let model = flux::quantized_model::Flux::new(&cfg, vb)?;
|
||||
flux::sampling::denoise(
|
||||
&model,
|
||||
&state.img,
|
||||
&state.img_ids,
|
||||
&state.txt,
|
||||
&state.txt_ids,
|
||||
&state.vec,
|
||||
×teps,
|
||||
4.,
|
||||
)?
|
||||
.to_dtype(dtype)?
|
||||
} else {
|
||||
let model_file = match model {
|
||||
Model::Schnell => bf_repo.get("flux1-schnell.safetensors")?,
|
||||
Model::Dev => bf_repo.get("flux1-dev.safetensors")?,
|
||||
};
|
||||
let vb = unsafe {
|
||||
VarBuilder::from_mmaped_safetensors(&[model_file], dtype, &device)?
|
||||
};
|
||||
let model = flux::model::Flux::new(&cfg, vb)?;
|
||||
flux::sampling::denoise(
|
||||
&model,
|
||||
&state.img,
|
||||
&state.img_ids,
|
||||
&state.txt,
|
||||
&state.txt_ids,
|
||||
&state.vec,
|
||||
×teps,
|
||||
4.,
|
||||
)?
|
||||
}
|
||||
};
|
||||
flux::sampling::unpack(&img, height, width)?
|
||||
}
|
||||
Some(file) => {
|
||||
let mut st = candle::safetensors::load(file, &device)?;
|
||||
st.remove("img").unwrap().to_dtype(dtype)?
|
||||
}
|
||||
};
|
||||
println!("latent img\n{img}");
|
||||
|
||||
let img = {
|
||||
let model_file = bf_repo.get("ae.safetensors")?;
|
||||
if let Some(seed) = args.seed {
|
||||
device.set_seed(seed)?;
|
||||
}
|
||||
let start_time = std::time::Instant::now();
|
||||
let dtype = device.bf16_default_to_f32();
|
||||
let img = match decode_only {
|
||||
None => {
|
||||
let t5_emb = {
|
||||
let repo = api.repo(hf_hub::Repo::with_revision("google/t5-v1_1-xxl".to_string(), hf_hub::RepoType::Model, "refs/pr/2".to_string()));
|
||||
let model_file = repo.get("model.safetensors")?;
|
||||
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&[model_file], dtype, &device)? };
|
||||
let config_filename = repo.get("config.json")?;
|
||||
let config = std::fs::read_to_string(config_filename)?;
|
||||
let config: t5::Config = serde_json::from_str(&config)?;
|
||||
let mut model = t5::T5EncoderModel::load(vb, &config)?;
|
||||
let tokenizer_filename = api.model("lmz/mt5-tokenizers".to_string()).get("t5-v1_1-xxl.tokenizer.json")?;
|
||||
let tokenizer = Tokenizer::from_file(tokenizer_filename).map_err(E::msg)?;
|
||||
let mut tokens = tokenizer.encode(prompt.as_str(), true).map_err(E::msg)?.get_ids().to_vec();
|
||||
tokens.resize(256, 0);
|
||||
let input_token_ids = Tensor::new(&tokens[..], &device)?.unsqueeze(0)?;
|
||||
println!("{input_token_ids}");
|
||||
model.forward(&input_token_ids)?
|
||||
};
|
||||
println!("T5\n{t5_emb}");
|
||||
let clip_emb = {
|
||||
let repo = api.repo(hf_hub::Repo::model("openai/clip-vit-large-patch14".to_string()));
|
||||
let model_file = repo.get("model.safetensors")?;
|
||||
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&[model_file], dtype, &device)? };
|
||||
// https://huggingface.co/openai/clip-vit-large-patch14/blob/main/config.json
|
||||
let config = clip::text_model::ClipTextConfig { vocab_size: 49408, projection_dim: 768, activation: clip::text_model::Activation::QuickGelu, intermediate_size: 3072, embed_dim: 768, max_position_embeddings: 77, pad_with: None, num_hidden_layers: 12, num_attention_heads: 12 };
|
||||
let model = clip::text_model::ClipTextTransformer::new(vb.pp("text_model"), &config)?;
|
||||
let tokenizer_filename = repo.get("tokenizer.json")?;
|
||||
let tokenizer = Tokenizer::from_file(tokenizer_filename).map_err(E::msg)?;
|
||||
let tokens = tokenizer.encode(prompt.as_str(), true).map_err(E::msg)?.get_ids().to_vec();
|
||||
let input_token_ids = Tensor::new(&tokens[..], &device)?.unsqueeze(0)?;
|
||||
println!("{input_token_ids}");
|
||||
model.forward(&input_token_ids)?
|
||||
};
|
||||
println!("CLIP\n{clip_emb}");
|
||||
let img = {
|
||||
let cfg = match model {
|
||||
Model::Dev => flux::autoencoder::Config::dev(),
|
||||
Model::Schnell => flux::autoencoder::Config::schnell(),
|
||||
Model::Dev => flux::model::Config::dev(),
|
||||
Model::Schnell => flux::model::Config::schnell(),
|
||||
};
|
||||
let model = flux::autoencoder::AutoEncoder::new(&cfg, vb)?;
|
||||
model.decode(&img)?
|
||||
let img = flux::sampling::get_noise(1, height, width, &device)?.to_dtype(dtype)?;
|
||||
let state = if quantized { flux::sampling::State::new(&t5_emb.to_dtype(candle::DType::F32)?, &clip_emb.to_dtype(candle::DType::F32)?, &img.to_dtype(candle::DType::F32)?)? } else { flux::sampling::State::new(&t5_emb, &clip_emb, &img)? };
|
||||
let timesteps = match model {
|
||||
Model::Dev => flux::sampling::get_schedule(28, Some((state.img.dim(1)?, 0.5, 1.15))),
|
||||
Model::Schnell => flux::sampling::get_schedule(4, None),
|
||||
};
|
||||
println!("{state:?}");
|
||||
println!("{timesteps:?}");
|
||||
if quantized {
|
||||
// Note about quantized model memory management
|
||||
let model_file = match model {
|
||||
Model::Schnell => api.repo(hf_hub::Repo::model("lmz/candle-flux".to_string())).get("flux1-schnell.gguf")?,
|
||||
Model::Dev => todo!(),
|
||||
};
|
||||
let vb = candle_transformers::quantized_var_builder::VarBuilder::from_gguf(model_file, &device)?;
|
||||
|
||||
let mut model = flux::quantized_model::Flux::new(&cfg, vb)?;
|
||||
// Quantized models are always fully loaded to the selected device
|
||||
if !cpu {
|
||||
println!("Quantized model fully loaded to GPU with optimized memory layout");
|
||||
}
|
||||
|
||||
flux::sampling::denoise(&mut model, &state.img, &state.img_ids, &state.txt, &state.txt_ids, &state.vec, ×teps, 4.)?.to_dtype(dtype)?
|
||||
} else {
|
||||
let model_file = match model {
|
||||
Model::Schnell => bf_repo.get("flux1-schnell.safetensors")?,
|
||||
Model::Dev => bf_repo.get("flux1-dev.safetensors")?,
|
||||
};
|
||||
|
||||
let mut model = if use_full_gpu {
|
||||
// When use_full_gpu is true, load everything directly to GPU
|
||||
println!("Loading entire model directly to GPU...");
|
||||
let gpu_vb = unsafe { VarBuilder::from_mmaped_safetensors(&[model_file], dtype, &device)? };
|
||||
let mut model = flux::model::Flux::new(&cfg, gpu_vb)?;
|
||||
model.set_use_device_management(false);
|
||||
println!("Model loaded entirely on GPU");
|
||||
model
|
||||
} else {
|
||||
// Use memory-efficient approach with blocks on CPU
|
||||
// Create a CPU device for initially loading all model weights
|
||||
let cpu_device = candle::Device::Cpu;
|
||||
|
||||
// Load the entire model to CPU first (uses less memory)
|
||||
println!("Loading model to CPU to minimize GPU memory usage...");
|
||||
let cpu_vb = unsafe { VarBuilder::from_mmaped_safetensors(&[model_file], dtype, &cpu_device)? };
|
||||
|
||||
// Create the model with CPU weights, then selectively move core components to GPU
|
||||
let mut model = flux::model::Flux::new_with_gpu_core(&cfg, cpu_vb, &device)?;
|
||||
println!("Model loaded (core components on GPU, blocks on CPU for reduced memory usage)");
|
||||
|
||||
// Set the number of active blocks to keep on GPU
|
||||
model.set_max_active_blocks(active_blocks);
|
||||
println!("Using {} active blocks on GPU", active_blocks);
|
||||
|
||||
// Enable prefetching if requested
|
||||
if prefetch_next_batch {
|
||||
model.set_prefetch_next_batch(true);
|
||||
println!("Prefetching enabled - will overlap transfers with computation");
|
||||
}
|
||||
|
||||
model
|
||||
};
|
||||
|
||||
// Print final memory management approach being used
|
||||
if use_full_gpu {
|
||||
println!("Using full GPU memory - entire model on GPU");
|
||||
} else {
|
||||
println!("Using device management - blocks will be transferred between CPU and GPU as needed");
|
||||
}
|
||||
|
||||
flux::sampling::denoise(&mut model, &state.img, &state.img_ids, &state.txt, &state.txt_ids, &state.vec, ×teps, 4.)?
|
||||
}
|
||||
};
|
||||
flux::sampling::unpack(&img, height, width)?
|
||||
},
|
||||
Some(file) => {
|
||||
let mut st = candle::safetensors::load(file, &device)?;
|
||||
st.remove("img").unwrap().to_dtype(dtype)?
|
||||
},
|
||||
};
|
||||
println!("latent img\n{img}");
|
||||
|
||||
let end_time = std::time::Instant::now();
|
||||
println!("Time taken to decode: {:?}", end_time.duration_since(start_time));
|
||||
let img = {
|
||||
let model_file = bf_repo.get("ae.safetensors")?;
|
||||
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&[model_file], dtype, &device)? };
|
||||
let cfg = match model {
|
||||
Model::Dev => flux::autoencoder::Config::dev(),
|
||||
Model::Schnell => flux::autoencoder::Config::schnell(),
|
||||
};
|
||||
println!("img\n{img}");
|
||||
let img = ((img.clamp(-1f32, 1f32)? + 1.0)? * 127.5)?.to_dtype(candle::DType::U8)?;
|
||||
let filename = match args.seed {
|
||||
None => "out.jpg".to_string(),
|
||||
Some(s) => format!("out-{s}.jpg"),
|
||||
};
|
||||
candle_examples::save_image(&img.i(0)?, filename)?;
|
||||
Ok(())
|
||||
let model = flux::autoencoder::AutoEncoder::new(&cfg, vb)?;
|
||||
model.decode(&img)?
|
||||
};
|
||||
println!("img\n{img}");
|
||||
let img = ((img.clamp(-1f32, 1f32)? + 1.0)? * 127.5)?.to_dtype(candle::DType::U8)?;
|
||||
let filename = match args.seed {
|
||||
None => "out.jpg".to_string(),
|
||||
Some(s) => format!("out-{s}.jpg"),
|
||||
};
|
||||
candle_examples::save_image(&img.i(0)?, filename)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
#[cfg(feature = "cuda")]
|
||||
candle::quantized::cuda::set_force_dmmv(args.use_dmmv);
|
||||
run(args)
|
||||
let args = Args::parse();
|
||||
#[cfg(feature = "cuda")]
|
||||
candle::quantized::cuda::set_force_dmmv(args.use_dmmv);
|
||||
run(args)
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
//! ```
|
||||
//!
|
||||
//! [`Layer Normalization`]: https://arxiv.org/abs/1607.06450
|
||||
use candle::{DType, Module, Result, Tensor, D};
|
||||
use candle::{DType, Module, Result, Tensor, D, Device};
|
||||
use crate::offload::DeviceTransferable;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct LayerNormConfig {
|
||||
@@ -201,3 +202,20 @@ pub fn rms_norm(size: usize, eps: f64, vb: crate::VarBuilder) -> Result<RmsNorm>
|
||||
};
|
||||
Ok(RmsNorm(layer_norm(size, config, vb)?))
|
||||
}
|
||||
|
||||
impl DeviceTransferable for LayerNorm {
|
||||
fn to_device(&self, device: &Device) -> Result<Self> {
|
||||
Ok(Self {
|
||||
weight: self.weight.to_device(device)?,
|
||||
bias: self.bias.as_ref().map(|b| b.to_device(device)).transpose()?,
|
||||
remove_mean: self.remove_mean,
|
||||
eps: self.eps,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceTransferable for RmsNorm {
|
||||
fn to_device(&self, device: &Device) -> Result<Self> {
|
||||
Ok(Self(self.0.to_device(device)?))
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ pub mod kv_cache;
|
||||
pub mod layer_norm;
|
||||
pub mod linear;
|
||||
pub mod loss;
|
||||
pub mod offload;
|
||||
pub mod ops;
|
||||
pub mod optim;
|
||||
pub mod rnn;
|
||||
@@ -58,3 +59,4 @@ pub use var_builder::VarBuilder;
|
||||
pub use var_map::VarMap;
|
||||
|
||||
pub use candle::{Module, ModuleT};
|
||||
pub use offload::DeviceTransferable;
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
//! assert_eq!(ys.to_vec2::<f32>()?, &[[210.0, 430.0, 650.0]]);
|
||||
//! # Ok(()) }
|
||||
//! ```
|
||||
use candle::{Result, Tensor};
|
||||
use candle::{Result, Tensor, Device};
|
||||
use crate::offload::DeviceTransferable;
|
||||
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Linear {
|
||||
@@ -88,3 +90,12 @@ pub fn linear_b(
|
||||
linear_no_bias(in_dim, out_dim, vb)
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceTransferable for Linear {
|
||||
fn to_device(&self, device: &Device) -> Result<Self> {
|
||||
Ok(Self {
|
||||
weight: self.weight.to_device(device)?,
|
||||
bias: self.bias.as_ref().map(|b| b.to_device(device)).transpose()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
use candle::{Device, Result};
|
||||
|
||||
pub trait DeviceTransferable {
|
||||
/// Move the module to the specified device
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `device` - The target device to move the module to
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A new instance of the module with all tensors moved to the target device,
|
||||
/// or an error if the module cannot be moved.
|
||||
fn to_device(&self, _device: &Device) -> Result<Self> where Self: Sized {
|
||||
// Default implementation that rejects moving - components can override this
|
||||
candle::bail!("to_device not implemented for this type")
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,9 @@ candle = { path = "../candle-core", package = "candle-core" }
|
||||
candle-flash-attn = { path = "../candle-flash-attn", optional = true }
|
||||
candle-nn = { path = "../candle-nn", package = "candle-nn" }
|
||||
fancy-regex = "0.13.0"
|
||||
half = { version = "2.3.1", features = ["num-traits", "use-intrinsics", "rand_distr"] }
|
||||
intel-mkl-src = { version = "0.8.1", features = ["mkl-static-lp64-iomp"], optional = true }
|
||||
log = "0.4.27"
|
||||
num-traits = "0.2.15"
|
||||
rand = "0.8.5"
|
||||
rayon = "1.7.0"
|
||||
@@ -17,7 +19,6 @@ serde = { version = "1.0.171", features = ["derive"] }
|
||||
serde_json = "1.0.99"
|
||||
serde_plain = "1.0.2"
|
||||
tracing = "0.1.37"
|
||||
half = { version = "2.3.1", features = ["num-traits", "use-intrinsics", "rand_distr"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -24,9 +24,39 @@
|
||||
use candle::{Result, Tensor};
|
||||
|
||||
pub trait WithForward {
|
||||
/// The forward pass for the Flux model.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `img` - The input image tensor
|
||||
/// * `img_ids` - The image position ids
|
||||
/// * `txt` - The text tensor
|
||||
/// * `txt_ids` - The text position ids
|
||||
/// * `timesteps` - The timesteps tensor
|
||||
/// * `y` - The conditioning vector
|
||||
/// * `guidance` - An optional guidance vector
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The result of the forward pass
|
||||
///
|
||||
/// # Note on Memory Optimization
|
||||
///
|
||||
/// To optimize memory usage, we could implement a mechanism to move model blocks
|
||||
/// to CPU when not in use. This would require:
|
||||
///
|
||||
/// 1. Adding `to_device` methods to all model blocks (DoubleStreamBlock, SingleStreamBlock, etc.)
|
||||
/// 2. Implementing these methods by moving all tensors inside each block to the target device
|
||||
/// 3. Modifying the forward method to:
|
||||
/// - Move each block to GPU when needed
|
||||
/// - Process the block
|
||||
/// - Move the block back to CPU when done
|
||||
///
|
||||
/// This pattern could significantly reduce GPU memory usage for large models by
|
||||
/// keeping only the currently active block in GPU memory.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn forward(
|
||||
&self,
|
||||
&mut self,
|
||||
img: &Tensor,
|
||||
img_ids: &Tensor,
|
||||
txt: &Tensor,
|
||||
|
||||
@@ -419,7 +419,7 @@ impl Flux {
|
||||
impl super::WithForward for Flux {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn forward(
|
||||
&self,
|
||||
&mut self,
|
||||
img: &Tensor,
|
||||
img_ids: &Tensor,
|
||||
txt: &Tensor,
|
||||
|
||||
@@ -93,7 +93,7 @@ pub fn unpack(xs: &Tensor, height: usize, width: usize) -> Result<Tensor> {
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn denoise<M: super::WithForward>(
|
||||
model: &M,
|
||||
model: &mut M,
|
||||
img: &Tensor,
|
||||
img_ids: &Tensor,
|
||||
txt: &Tensor,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use super::schedulers::{betas_for_alpha_bar, BetaSchedule, PredictionType};
|
||||
use candle::{DType, Device, IndexOp, Result, Tensor};
|
||||
use log::debug;
|
||||
|
||||
const FINAL_ALPHA_CUMPROD: f64 = 0.9991499781608582;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum LCMVarianceType {
|
||||
FixedSmall,
|
||||
@@ -87,7 +90,7 @@ impl LCMScheduler {
|
||||
let timesteps = LCMScheduler::get_timesteps_for_steps(inference_steps, strength);
|
||||
let step_ratio = config.train_timesteps / inference_steps;
|
||||
|
||||
println!("Using LCM timesteps: {:?}", timesteps);
|
||||
debug!("Using LCM timesteps: {:?}", timesteps);
|
||||
|
||||
Ok(Self {
|
||||
alphas_cumprod,
|
||||
@@ -143,11 +146,11 @@ impl LCMScheduler {
|
||||
let scaled_timestep_squared = scaled_timestep.powi(2);
|
||||
let denominator = scaled_timestep_squared + sigma_squared;
|
||||
|
||||
println!(
|
||||
debug!(
|
||||
"Boundary condition for timestep {}, scaled timestep {}",
|
||||
timestep, scaled_timestep
|
||||
);
|
||||
println!(
|
||||
debug!(
|
||||
" Intermediate values: sigma_data^2: {:.20}, scaled_timestep^2: {:.20}, denominator: {:.20}",
|
||||
sigma_squared, scaled_timestep_squared, denominator
|
||||
);
|
||||
@@ -155,7 +158,7 @@ impl LCMScheduler {
|
||||
let c_skip = sigma_squared / denominator;
|
||||
let c_out = scaled_timestep / (scaled_timestep_squared + sigma_squared).sqrt();
|
||||
|
||||
println!(
|
||||
debug!(
|
||||
" Final computed values: c_skip: {:.20}, c_out: {:.20}",
|
||||
c_skip, c_out
|
||||
);
|
||||
@@ -172,14 +175,14 @@ impl LCMScheduler {
|
||||
) -> Result<Tensor> {
|
||||
// Scale by exactly 1000.0 to match diffusers
|
||||
let w = guidance_scale * 1000.0;
|
||||
println!(
|
||||
debug!(
|
||||
"Creating guidance embedding with dim={}, scaled value={}",
|
||||
embedding_dim, w
|
||||
);
|
||||
|
||||
// Calculate the half dimension
|
||||
let half_dim = embedding_dim / 2;
|
||||
println!("Half dimension: {}", half_dim);
|
||||
debug!("Half dimension: {}", half_dim);
|
||||
|
||||
let log_10000 = (10000.0f32).ln();
|
||||
let freq_factor = log_10000 / ((half_dim - 1) as f32); // pre-compute division
|
||||
@@ -191,7 +194,7 @@ impl LCMScheduler {
|
||||
freqs.push(freq);
|
||||
}
|
||||
|
||||
println!(
|
||||
debug!(
|
||||
"First few frequencies: {:?}",
|
||||
&freqs.iter().take(5).collect::<Vec<_>>()
|
||||
);
|
||||
@@ -204,39 +207,39 @@ impl LCMScheduler {
|
||||
emb_data.push(w_val * freq);
|
||||
}
|
||||
|
||||
println!(
|
||||
debug!(
|
||||
"First few scaled frequencies: {:?}",
|
||||
&emb_data.iter().take(5).collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
// Create the embedding tensor
|
||||
let emb = Tensor::new(emb_data.as_slice(), device)?.to_dtype(dtype)?;
|
||||
println!("Embedding tensor shape: {:?}", emb.shape());
|
||||
debug!("Embedding tensor shape: {:?}", emb.shape());
|
||||
|
||||
// Reshape for correct sin/cos application
|
||||
let emb = emb.reshape((1, half_dim))?;
|
||||
println!("Reshaped embedding tensor: {:?}", emb.shape());
|
||||
debug!("Reshaped embedding tensor: {:?}", emb.shape());
|
||||
|
||||
// Compute sin and cos components
|
||||
let sin_emb = emb.sin()?;
|
||||
println!("Sin embedding shape: {:?}", sin_emb.shape());
|
||||
debug!("Sin embedding shape: {:?}", sin_emb.shape());
|
||||
let cos_emb = emb.cos()?;
|
||||
println!("Cos embedding shape: {:?}", cos_emb.shape());
|
||||
debug!("Cos embedding shape: {:?}", cos_emb.shape());
|
||||
|
||||
// Concatenate sin and cos (along dimension 1)
|
||||
let result = Tensor::cat(&[sin_emb, cos_emb], 1)?;
|
||||
println!("After concatenation shape: {:?}", result.shape());
|
||||
debug!("After concatenation shape: {:?}", result.shape());
|
||||
|
||||
// If odd dimension, pad with zero
|
||||
let result = if embedding_dim % 2 == 1 {
|
||||
println!("Adding zero padding for odd dimension");
|
||||
debug!("Adding zero padding for odd dimension");
|
||||
let zero = Tensor::zeros((1, 1), dtype, device)?;
|
||||
Tensor::cat(&[result, zero], 1)?
|
||||
} else {
|
||||
result
|
||||
};
|
||||
|
||||
println!("Final guidance embedding shape: {:?}", result.shape());
|
||||
debug!("Final guidance embedding shape: {:?}", result.shape());
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
@@ -288,12 +291,12 @@ impl LCMScheduler {
|
||||
};
|
||||
|
||||
let timesteps = linspace.iter().map(|&f| f as usize).rev().collect();
|
||||
println!("timesteps: {:?}", timesteps);
|
||||
debug!("timesteps: {:?}", timesteps);
|
||||
timesteps
|
||||
}
|
||||
};
|
||||
|
||||
println!("all_timesteps: {:?}", all_timesteps);
|
||||
debug!("all_timesteps: {:?}", all_timesteps);
|
||||
|
||||
// Compute `t_start` based on `strength`
|
||||
let num_train_timesteps = 1000;
|
||||
@@ -303,7 +306,7 @@ impl LCMScheduler {
|
||||
.position(|&t| t <= init_timestep)
|
||||
.unwrap_or(0);
|
||||
|
||||
println!(
|
||||
debug!(
|
||||
"get_timesteps: t_start: {} init_timestep: {} strength: {}",
|
||||
t_start, init_timestep, strength
|
||||
);
|
||||
@@ -327,7 +330,7 @@ impl LCMScheduler {
|
||||
0
|
||||
};
|
||||
|
||||
println!(
|
||||
debug!(
|
||||
"LCM step: timestep={}, prev_timestep={}, step_index={}",
|
||||
timestep, prev_timestep, step_index
|
||||
);
|
||||
@@ -357,15 +360,15 @@ impl LCMScheduler {
|
||||
let denoised = ((c_out * pred_original_sample )? + (c_skip * sample)?)?;
|
||||
|
||||
let pred_prev_sample = if step_index != self.timesteps.len() - 1 {
|
||||
println!("Adding noise at intermediate step {}", timestep);
|
||||
debug!("Adding noise at intermediate step {}", timestep);
|
||||
let noise = Tensor::randn_like(&denoised, 0.0, 1.0)?;
|
||||
((alpha_prod_t_prev.sqrt() * denoised)? + (beta_prod_t_prev.sqrt() * noise)?)?
|
||||
} else {
|
||||
println!("No noise for final timestep {}", timestep);
|
||||
debug!("No noise for final timestep {}", timestep);
|
||||
denoised
|
||||
};
|
||||
|
||||
println!(
|
||||
debug!(
|
||||
"Denoised sample shape: {:?}, first few values: {}",
|
||||
pred_prev_sample.shape(),
|
||||
pred_prev_sample
|
||||
@@ -388,7 +391,7 @@ impl LCMScheduler {
|
||||
// self.timesteps[step_index + 1]
|
||||
// };
|
||||
|
||||
// println!(
|
||||
// debug!(
|
||||
// "LCM step: timestep={}, prev_timestep={}",
|
||||
// timestep, prev_timestep
|
||||
// );
|
||||
@@ -401,7 +404,7 @@ impl LCMScheduler {
|
||||
// self.alphas_cumprod[prev_timestep]
|
||||
// };
|
||||
|
||||
// println!(
|
||||
// debug!(
|
||||
// "LCM scheduler step: timestep={}, step_index={}, sigma={}",
|
||||
// timestep,
|
||||
// step_index,
|
||||
@@ -433,13 +436,13 @@ impl LCMScheduler {
|
||||
// pred_original_sample = pred_original_sample.clamp(-1f32, 1f32)?;
|
||||
// }
|
||||
|
||||
// println!("boundary: Pred original sample first few values: {}",
|
||||
// debug!("boundary: Pred original sample first few values: {}",
|
||||
// pred_original_sample.i(0)?.i(0)?.narrow(0, 0, 5)?.narrow(1, 0, 5)?);
|
||||
|
||||
// // Denoise model output using boundary conditions
|
||||
// let denoised = ((pred_original_sample * c_out)? + (sample * c_skip)?)?;
|
||||
|
||||
// println!("boundary: Denoised first few values: {}",
|
||||
// debug!("boundary: Denoised first few values: {}",
|
||||
// denoised.i(0)?.i(0)?.narrow(0, 0, 5)?.narrow(1, 0, 5)?);
|
||||
|
||||
// // LCM doesn't add noise during inference
|
||||
@@ -452,11 +455,11 @@ impl LCMScheduler {
|
||||
|
||||
// // let pred_prev_sample = pred_prev_sample.clamp(-1f32, 1f32)?;
|
||||
|
||||
// println!(
|
||||
// debug!(
|
||||
// "LCM scheduler pred_original shape: {:?}",
|
||||
// pred_prev_sample.shape()
|
||||
// );
|
||||
// println!(
|
||||
// debug!(
|
||||
// "LCM scheduler pred_original first few values: {}",
|
||||
// pred_prev_sample
|
||||
// .i(0)?
|
||||
@@ -469,7 +472,7 @@ impl LCMScheduler {
|
||||
// }
|
||||
|
||||
pub fn add_noise(&self, original: &Tensor, noise: Tensor, timestep: usize) -> Result<Tensor> {
|
||||
println!("Adding noise at timestep {}", timestep);
|
||||
debug!("Adding noise at timestep {}", timestep);
|
||||
let timestep = if timestep >= self.alphas_cumprod.len() {
|
||||
self.alphas_cumprod.len() - 1 // Ensure valid range
|
||||
} else {
|
||||
@@ -491,7 +494,7 @@ impl LCMScheduler {
|
||||
// let alpha_cumprod = self.alphas_cumprod[timestep];
|
||||
|
||||
// // Print the alpha value to debug
|
||||
// println!(
|
||||
// debug!(
|
||||
// "Adding noise with timestep={}, alpha={}, sqrt(alpha)={}",
|
||||
// timestep,
|
||||
// alpha_cumprod,
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
backup
|
||||
.venv
|
||||
# Editor directories and files
|
||||
~
|
||||
.vscode
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
**/*.onnx
|
||||
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Not Found</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1> This Page Was Not Found </h1>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,39 @@
|
||||
# Memeboard
|
||||
|
||||
## Installation & Dev
|
||||
|
||||
```sh
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
yarn # this installs the node packages
|
||||
yarn dev # this runs the vite dev server
|
||||
```
|
||||
|
||||
You may need to turn off browser security to get around CORS restrictions:
|
||||
|
||||
### Windows 11
|
||||
|
||||
from your powershell
|
||||
|
||||
```sh
|
||||
# Chrome
|
||||
Start "C:\Program Files\Google\Chrome\Application\chrome.exe" -ArgumentList '--user-data-dir="C://chrome-dev-disabled-security" --disable-web-security --disable-site-isolation-trials'
|
||||
```
|
||||
|
||||
### on MAC
|
||||
|
||||
form your terminal
|
||||
|
||||
```sh
|
||||
# Chrome
|
||||
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --disable-web-security --user-data-dir=~/chrome
|
||||
```
|
||||
|
||||
### Inspecting the Shared Workers
|
||||
|
||||
on chrome, type `chrome://inspect/#workers` into the url field
|
||||
@@ -0,0 +1,3 @@
|
||||
/*
|
||||
Access-Control-Allow-Origin: *
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Board Netlify Redirects File
|
||||
|
||||
# Netlify docs:
|
||||
# The redirects engine will process the first matching rule it
|
||||
# finds, reading from top to bottom. Rules in the _redirects file
|
||||
# are always processed first, followed by rules in the Netlify
|
||||
# configuration file.
|
||||
|
||||
/landing /index.html 200!
|
||||
/login /index.html 200!
|
||||
/m_* /index.html 200!
|
||||
@@ -0,0 +1,44 @@
|
||||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
import reactHooks from "eslint-plugin-react-hooks";
|
||||
import reactRefresh from "eslint-plugin-react-refresh";
|
||||
import tseslint from "typescript-eslint";
|
||||
import react from "eslint-plugin-react";
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ["dist"] },
|
||||
{
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
...tseslint.configs.stylisticTypeChecked,
|
||||
],
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
"react-hooks": reactHooks,
|
||||
"react-refresh": reactRefresh,
|
||||
react,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
"react-refresh/only-export-components": [
|
||||
"warn",
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
...react.configs.recommended.rules,
|
||||
...react.configs["jsx-runtime"].rules,
|
||||
// TODO WIL: make no-reaching rule for imports
|
||||
// "import/no-internal-modules": [
|
||||
// "error",
|
||||
// {
|
||||
// allow: [],
|
||||
// forbid: ["**/uiAccess/*"],
|
||||
// },
|
||||
// ],
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,24 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/x-ico" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Syne:wght@600&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>Board | Storyteller.AI</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/app/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,34 @@
|
||||
[build]
|
||||
command = "./script/netlify_build.sh"
|
||||
publish = "dist/"
|
||||
|
||||
#[build.environment]
|
||||
#REACT_APP_PUBLIC_POSTHOG_KEY = "phc_jBFgac0mVALAFk3negnSfYCcHgvkT00yBLQDmCDYNBb"
|
||||
#REACT_APP_PUBLIC_POSTHOG_UI = "https://us.i.posthog.com"
|
||||
|
||||
#[dev]
|
||||
#command = "npm run dev"
|
||||
#framework = "vite"
|
||||
|
||||
# Set immutable caching for static files, because they have fingerprinted filenames
|
||||
|
||||
[[headers]]
|
||||
for = "/build/*"
|
||||
|
||||
[headers.values]
|
||||
|
||||
"Cache-Control" = "public, max-age=31560000, immutable"
|
||||
|
||||
# [[redirects]]
|
||||
# from = "/ingest/static/*"
|
||||
# to = "https://us-assets.i.posthog.com/static/:splat"
|
||||
# host = "us-assets.i.posthog.com"
|
||||
# status = 200
|
||||
# force = true
|
||||
#
|
||||
# [[redirects]]
|
||||
# from = "/ingest/*"
|
||||
# to = "https://us.i.posthog.com/:splat"
|
||||
# host = "us.i.posthog.com"
|
||||
# status = 200
|
||||
# force = true
|
||||
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "storyteller-board",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "echo 'realtime' && vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"build2": "vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@devnomic/marquee": "^1.0.3",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.6.0",
|
||||
"@headlessui/react": "^2.1.3",
|
||||
"@preact/signals-react": "^2.2.0",
|
||||
"@tauri-apps/api": "^2.2.0",
|
||||
"@techstark/opencv-js": "^4.10.0-release.1",
|
||||
"@zip.js/zip.js": "^2.7.52",
|
||||
"konva": "^9.3.14",
|
||||
"onnxruntime-web": "^1.19.2",
|
||||
"posthog-js": "^1.174.3",
|
||||
"react": "^18.3.1",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-drag-drop-files": "^2.3.10",
|
||||
"react-easy-crop": "^5.1.0",
|
||||
"react-router-dom": "^6.26.1",
|
||||
"sonner": "^2.0.1",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"uuid": "^10.0.0",
|
||||
"yarn": "^1.22.22"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
||||
"@fortawesome/pro-solid-svg-icons": "^6.6.0",
|
||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||
"@preact/signals-react-transform": "^0.4.0",
|
||||
"@tauri-apps/cli": "^2.4.0",
|
||||
"@types/deep-equal": "^1.0.4",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"deep-equal": "^2.2.3",
|
||||
"eslint": "^9.9.0",
|
||||
"eslint-plugin-react": "^7.35.0",
|
||||
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.9",
|
||||
"globals": "^15.9.0",
|
||||
"i": "^0.3.7",
|
||||
"npm": "^10.8.3",
|
||||
"postcss": "^8.4.41",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.6",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"typescript": "^5.5.3",
|
||||
"typescript-eslint": "^8.0.1",
|
||||
"vite": "^5.4.1",
|
||||
"vite-plugin-static-copy": "^1.0.6",
|
||||
"vite-tsconfig-paths": "^5.0.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
import asyncio
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import pathlib
|
||||
import time
|
||||
from io import BytesIO
|
||||
from typing import Dict, Any, Optional, Tuple, Callable, List
|
||||
from dataclasses import dataclass
|
||||
from dataclasses_json import dataclass_json
|
||||
from enum import Enum, auto
|
||||
import base64
|
||||
from PIL import Image
|
||||
import io
|
||||
import torch
|
||||
from diffusers import AutoPipelineForImage2Image, LCMScheduler
|
||||
from diffusers.utils import make_image_grid, load_image
|
||||
from sd_embed.embedding_funcs import get_weighted_text_embeddings_sdxl
|
||||
import torch
|
||||
import websockets
|
||||
from PIL import Image
|
||||
from diffusers import (
|
||||
StableDiffusionXLPipeline,
|
||||
StableDiffusionXLImg2ImgPipeline,
|
||||
AutoPipelineForImage2Image,
|
||||
LCMScheduler,
|
||||
UNet2DConditionModel,
|
||||
)
|
||||
import torch
|
||||
import torchvision.transforms as transforms
|
||||
import gc
|
||||
|
||||
transform = transforms.Compose([
|
||||
transforms.ToTensor(), # This converts PIL Image to tensor and scales to [0, 1]
|
||||
])
|
||||
|
||||
@dataclass_json
|
||||
@dataclass
|
||||
class ProgressMessage:
|
||||
message: str
|
||||
progress: float
|
||||
type: str = "progress"
|
||||
error: Optional[str] = None
|
||||
|
||||
@dataclass_json
|
||||
@dataclass
|
||||
class LoadModelRequest:
|
||||
model_path: str
|
||||
lora_path: Optional[str] = None
|
||||
command: str = "load_model"
|
||||
|
||||
@dataclass_json
|
||||
@dataclass
|
||||
class GenerateRequest:
|
||||
image: str
|
||||
prompt: str
|
||||
command: str = "generate"
|
||||
strength: float = 0.6
|
||||
guidance_scale: float = 2.0
|
||||
num_inference_steps: int = 4
|
||||
height: int = 1024
|
||||
width: int = 1024
|
||||
lora_strength: float = 1.0
|
||||
|
||||
@dataclass_json
|
||||
@dataclass
|
||||
class GenerateResponse:
|
||||
image: str
|
||||
type: str = "result"
|
||||
|
||||
def save_image(image: Image.Image, base_path: str = "outputs") -> str:
|
||||
"""Save a PIL Image to disk with a timestamp-based filename."""
|
||||
try:
|
||||
# Create outputs directory if it doesn't exist
|
||||
output_dir = pathlib.Path(base_path)
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Generate filename with timestamp
|
||||
timestamp = time.strftime("%Y%m%d-%H%M%S")
|
||||
filename = f"generated-{timestamp}.png"
|
||||
filepath = output_dir / filename
|
||||
|
||||
# Save the image
|
||||
print(f"Saving image to: {filepath}")
|
||||
image.save(str(filepath), format="PNG")
|
||||
print(f"Successfully saved image to: {filepath}")
|
||||
return str(filepath)
|
||||
except Exception as e:
|
||||
print(f"Error saving image: {e}")
|
||||
return ""
|
||||
|
||||
|
||||
async def send_progress(websocket, message: str, progress: float = 0.0, error: str = None):
|
||||
"""Send progress updates to the client."""
|
||||
progress_msg = ProgressMessage(
|
||||
message=message,
|
||||
progress=progress,
|
||||
error=error
|
||||
)
|
||||
await websocket.send(progress_msg.to_json())
|
||||
|
||||
async def load_model(model_path: str, lora_path: str = None) -> StableDiffusionXLImg2ImgPipeline:
|
||||
"""Load the model with progress reporting."""
|
||||
try:
|
||||
# Load base SDXL pipeline
|
||||
pipe = StableDiffusionXLPipeline.from_single_file(
|
||||
model_path,
|
||||
torch_dtype=torch.float16,
|
||||
use_safetensors=True
|
||||
).to("cuda")
|
||||
|
||||
# Convert to img2img pipeline
|
||||
pipe = StableDiffusionXLImg2ImgPipeline(**pipe.components)
|
||||
|
||||
# Set scheduler
|
||||
pipe.scheduler = LCMScheduler.from_config(pipe.scheduler.config)
|
||||
|
||||
# Load LCM-LoRA weights if provided
|
||||
if lora_path:
|
||||
pipe.load_lora_weights(
|
||||
"F:/ComfyUI_windows_portable_nvidia/ComfyUI_windows_portable/ComfyUI/models/loras/LCM_LoRA_Weights_SDXL.safetensors",
|
||||
adapter_name="lcm"
|
||||
)
|
||||
pipe.load_lora_weights(lora_path, adapter_name="lora")
|
||||
pipe.set_adapters(["lcm", "lora"], adapter_weights=[1.0, 1.0])
|
||||
#pipe.set_adapters(["lcm"], adapter_weights=[1.0])
|
||||
#pipe.unet = torch.compile(pipe.unet, mode="reduce-overhead", fullgraph=True)
|
||||
return pipe
|
||||
except Exception as e:
|
||||
raise Exception(f"Error loading model: {str(e)}")
|
||||
|
||||
#https://huggingface.co/blog/simple_sdxl_optimizations
|
||||
async def generate_image(
|
||||
pipe: StableDiffusionXLImg2ImgPipeline,
|
||||
request: GenerateRequest
|
||||
) -> str:
|
||||
"""Generate image and return base64 string."""
|
||||
try:
|
||||
# Clear CUDA cache before processing
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
image_stream = io.BytesIO(base64.b64decode(request.image))
|
||||
|
||||
init_image = Image.open(image_stream).convert('RGB')
|
||||
init_image = init_image.resize((request.width, request.height)) # width, height
|
||||
init_image = transform(init_image).to('cuda')
|
||||
init_image = init_image.unsqueeze(0)
|
||||
|
||||
(
|
||||
prompt_embeds
|
||||
, prompt_neg_embeds
|
||||
, pooled_prompt_embeds
|
||||
, negative_pooled_prompt_embeds
|
||||
) = get_weighted_text_embeddings_sdxl(
|
||||
pipe,
|
||||
prompt = request.prompt,
|
||||
neg_prompt = "bad quality, poorly Rendered face, poorly drawn face, poorly facial details, poorly drawn hands, poorly rendered hands, low resolution, Images cut out at the top, left, right, bottom, bad composition, mutated body parts, blurry image, disfigured, oversaturated, bad anatomy, deformed body features"
|
||||
)
|
||||
|
||||
# Generate image
|
||||
with torch.inference_mode():
|
||||
output_image = pipe(
|
||||
prompt_embeds = prompt_embeds,
|
||||
negative_prompt_embeds = prompt_neg_embeds,
|
||||
pooled_prompt_embeds = pooled_prompt_embeds,
|
||||
negative_pooled_prompt_embeds = negative_pooled_prompt_embeds,
|
||||
image=init_image,
|
||||
num_inference_steps=request.num_inference_steps,
|
||||
guidance_scale=request.guidance_scale,
|
||||
strength=request.strength,
|
||||
generator=torch.manual_seed(0),
|
||||
height=request.height, width=request.width,
|
||||
).images[0]
|
||||
|
||||
# Clean up CUDA tensors
|
||||
del init_image, prompt_embeds, prompt_neg_embeds,pooled_prompt_embeds, negative_pooled_prompt_embeds
|
||||
torch.cuda.empty_cache()
|
||||
gc.collect()
|
||||
|
||||
# Convert to base64
|
||||
buffered = BytesIO()
|
||||
output_image.save(buffered, format="PNG")
|
||||
buffered.seek(0)
|
||||
img_str = base64.b64encode(buffered.getvalue()).decode()
|
||||
|
||||
return f"data:image/png;base64,{img_str}"
|
||||
except Exception as e:
|
||||
raise Exception(f"Error generating image: {str(e)} at line {e.__traceback__.tb_lineno}")
|
||||
|
||||
async def handle_client(websocket):
|
||||
"""Handle WebSocket client connection."""
|
||||
pipe = None
|
||||
try:
|
||||
async for message in websocket:
|
||||
try:
|
||||
# Clear memory before processing new request
|
||||
torch.cuda.empty_cache()
|
||||
gc.collect()
|
||||
|
||||
data = json.loads(message)
|
||||
command = data.get("command")
|
||||
|
||||
if command == "load_model":
|
||||
await send_progress(websocket, "Loading model...", 0.0)
|
||||
request = LoadModelRequest.from_json(message)
|
||||
|
||||
if pipe is not None:
|
||||
# Clean up existing model
|
||||
del pipe
|
||||
torch.cuda.empty_cache()
|
||||
gc.collect()
|
||||
|
||||
pipe = await load_model(request.model_path, request.lora_path)
|
||||
await send_progress(websocket, "Model loaded successfully", 1.0)
|
||||
|
||||
elif command == "generate":
|
||||
if not pipe:
|
||||
raise Exception("Model not loaded. Please load model first.")
|
||||
|
||||
await send_progress(websocket, "Generating image...", 0.0)
|
||||
request = GenerateRequest.from_json(message)
|
||||
|
||||
# Use a timeout for generation to prevent hanging
|
||||
try:
|
||||
result = await asyncio.wait_for(
|
||||
generate_image(pipe=pipe, request=request),
|
||||
timeout=10.0 # 30 second timeout
|
||||
)
|
||||
response = GenerateResponse(image=result)
|
||||
await websocket.send(response.to_json())
|
||||
except asyncio.TimeoutError:
|
||||
# Force cleanup if generation times out
|
||||
torch.cuda.empty_cache()
|
||||
gc.collect()
|
||||
raise Exception("Generation timed out")
|
||||
|
||||
else:
|
||||
raise Exception(f"Unknown command: {command}")
|
||||
|
||||
except Exception as e:
|
||||
await send_progress(websocket, "Error processing request", 0.0, str(e))
|
||||
# Clean up after error
|
||||
torch.cuda.empty_cache()
|
||||
gc.collect()
|
||||
|
||||
except websockets.exceptions.ConnectionClosed:
|
||||
print("Client connection closed")
|
||||
except Exception as e:
|
||||
await send_progress(websocket, "Fatal error", 0.0, str(e))
|
||||
finally:
|
||||
# Clean up resources when client disconnects
|
||||
if pipe is not None:
|
||||
del pipe
|
||||
torch.cuda.empty_cache()
|
||||
gc.collect()
|
||||
|
||||
async def server():
|
||||
"""Start WebSocket server."""
|
||||
async with websockets.serve(
|
||||
handle_client,
|
||||
"localhost",
|
||||
8765,
|
||||
max_size=None,
|
||||
ping_interval=None, # Disable ping/pong to prevent timeouts
|
||||
max_queue=10 # Limit pending connections
|
||||
):
|
||||
print("Server started on ws://localhost:8765")
|
||||
await asyncio.Future() # run forever
|
||||
|
||||
if __name__ == "__main__":
|
||||
# test() # Comment out test function
|
||||
asyncio.run(server())
|
||||
@@ -0,0 +1,101 @@
|
||||
import asyncio
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import pathlib
|
||||
import time
|
||||
from io import BytesIO
|
||||
from typing import Dict, Any, Optional, Tuple, Callable, List
|
||||
|
||||
import torch
|
||||
from diffusers import AutoPipelineForImage2Image, LCMScheduler
|
||||
from diffusers.utils import make_image_grid, load_image
|
||||
|
||||
import torch
|
||||
import websockets
|
||||
from PIL import Image
|
||||
from diffusers import (
|
||||
StableDiffusionXLPipeline,
|
||||
StableDiffusionXLImg2ImgPipeline,
|
||||
AutoPipelineForImage2Image,
|
||||
LCMScheduler,
|
||||
UNet2DConditionModel,
|
||||
)
|
||||
|
||||
def save_image(image: Image.Image, base_path: str = "outputs") -> str:
|
||||
"""Save a PIL Image to disk with a timestamp-based filename."""
|
||||
try:
|
||||
# Create outputs directory if it doesn't exist
|
||||
output_dir = pathlib.Path(base_path)
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Generate filename with timestamp
|
||||
timestamp = time.strftime("%Y%m%d-%H%M%S")
|
||||
filename = f"generated-{timestamp}.png"
|
||||
filepath = output_dir / filename
|
||||
|
||||
# Save the image
|
||||
print(f"Saving image to: {filepath}")
|
||||
image.save(str(filepath), format="PNG")
|
||||
print(f"Successfully saved image to: {filepath}")
|
||||
return str(filepath)
|
||||
except Exception as e:
|
||||
print(f"Error saving image: {e}")
|
||||
return ""
|
||||
|
||||
def test():
|
||||
# Load base SDXL pipeline from .safetensors file
|
||||
pipe = StableDiffusionXLPipeline.from_single_file(
|
||||
f"C:/Users/Tensor/Downloads/animagineXL40_v4Opt.safetensors",
|
||||
torch_dtype=torch.float16,
|
||||
use_safetensors=True,
|
||||
).to("cuda")
|
||||
|
||||
# Convert to img2img pipeline
|
||||
pipe = StableDiffusionXLImg2ImgPipeline(**pipe.components)
|
||||
|
||||
# Set scheduler
|
||||
pipe.scheduler = LCMScheduler.from_config(pipe.scheduler.config)
|
||||
|
||||
# Load LCM-LoRA weights
|
||||
pipe.load_lora_weights(
|
||||
"F:/ComfyUI_windows_portable_nvidia/ComfyUI_windows_portable/ComfyUI/models/loras/LCM_LoRA_Weights_SDXL.safetensors",
|
||||
adapter_name="lcm"
|
||||
)
|
||||
pipe.set_adapters(["lcm"], adapter_weights=[1.0])
|
||||
|
||||
# prepare image
|
||||
url = "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/7462a0ac-637d-467b-baa9-34c5f54696b4/original=true,quality=90/60277788.jpeg"
|
||||
init_image = load_image(url)
|
||||
prompt = "safe_pos, 1girl, solo, solo focus, 8k, masterpiece, hires, absurdres, splash art, gradient, looking at viewer, medium breasts, vibrant yellow hair, purple hair tips, (shoulder freckles:1.2), nude, athletic, confident, hand on waist, extremely long hair, dancing, delicate gold jewelry, thin gold bangles, belly button chain, action shot,, head tilt, hair censor, spotlight, tottotonero"
|
||||
|
||||
# pass prompt and image to pipeline
|
||||
generator = torch.manual_seed(0)
|
||||
output_image = pipe(
|
||||
prompt,
|
||||
image=init_image,
|
||||
num_inference_steps=4,
|
||||
guidance_scale=2,
|
||||
strength=0.6,
|
||||
generator=generator
|
||||
).images[0]
|
||||
|
||||
# Save both input and output images
|
||||
save_image(init_image, "outputs/input")
|
||||
output_path = save_image(output_image, "outputs/output")
|
||||
print(f"Output image saved to: {output_path}")
|
||||
|
||||
# Create and save the grid
|
||||
grid = make_image_grid([init_image, output_image], rows=1, cols=2)
|
||||
grid_path = save_image(grid, "outputs/grid")
|
||||
print(f"Grid image saved to: {grid_path}")
|
||||
|
||||
async def start_server():
|
||||
async with websockets.serve(
|
||||
"localhost",
|
||||
8765 # Increase to 10MB
|
||||
):
|
||||
await asyncio.Future() # run forever
|
||||
|
||||
if __name__ == "__main__":
|
||||
test()
|
||||
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 204 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 701 B After Width: | Height: | Size: 701 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 351 KiB |
|
After Width: | Height: | Size: 244 KiB |
|
After Width: | Height: | Size: 267 KiB |
|
After Width: | Height: | Size: 271 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 276 KiB |
|
After Width: | Height: | Size: 320 KiB |
|
After Width: | Height: | Size: 564 KiB |
|
After Width: | Height: | Size: 366 KiB |
|
After Width: | Height: | Size: 452 KiB |
|
After Width: | Height: | Size: 337 KiB |
|
After Width: | Height: | Size: 378 KiB |
|
After Width: | Height: | Size: 302 KiB |
|
After Width: | Height: | Size: 446 KiB |
|
After Width: | Height: | Size: 238 KiB |
|
After Width: | Height: | Size: 344 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 299 KiB |
|
After Width: | Height: | Size: 396 KiB |
|
After Width: | Height: | Size: 318 KiB |
|
After Width: | Height: | Size: 521 KiB |
|
After Width: | Height: | Size: 318 KiB |
|
After Width: | Height: | Size: 396 KiB |
|
After Width: | Height: | Size: 316 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 378 KiB |
|
After Width: | Height: | Size: 341 KiB |
|
After Width: | Height: | Size: 212 KiB |
|
After Width: | Height: | Size: 462 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 615 B After Width: | Height: | Size: 615 B |