mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-03 00:05:41 -05:00
Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
756c27bced | ||
|
|
2e56291649 | ||
|
|
b31badc6d1 | ||
|
|
ed399fcb21 | ||
|
|
d858c9394b | ||
|
|
a19a6599ec | ||
|
|
57c15f7c9f | ||
|
|
aa25ae8c87 | ||
|
|
c27055761e | ||
|
|
abf3024892 | ||
|
|
2182d4dde1 | ||
|
|
b632b927a3 | ||
|
|
4e2be12064 | ||
|
|
29b33cd327 | ||
|
|
032d0e1eae | ||
|
|
a3ddffeeee | ||
|
|
28a5ff2f9f | ||
|
|
1028e04e02 | ||
|
|
49c010db59 | ||
|
|
48ff60d3b8 | ||
|
|
9c5b993c4f | ||
|
|
a4aecc4d38 | ||
|
|
4d73230e84 | ||
|
|
a5a09f3e64 | ||
|
|
49894bd9b3 |
Generated
+107
-64
@@ -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,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(())
|
||||
}
|
||||
}
|
||||
+248
-219
@@ -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)
|
||||
}
|
||||
|
||||
+19
-1
@@ -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)?))
|
||||
}
|
||||
}
|
||||
+2
@@ -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;
|
||||
|
||||
+12
-1
@@ -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()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
+18
@@ -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")
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -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,
|
||||
|
||||
+712
-485
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -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,
|
||||
|
||||
+33
-30
@@ -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,
|
||||
|
||||
+634
-368
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user