From 0b0076cdc9eb32ce7fa77e1ccc776a1c0c5139ee Mon Sep 17 00:00:00 2001 From: jhqxxx Date: Mon, 22 Sep 2025 16:38:12 +0800 Subject: [PATCH] add qwen2.5vl model --- Cargo.lock | 3907 ++++++++++++++++++++++++++++ Cargo.toml | 18 + assets/img/ocr_test.png | Bin 0 -> 63470 bytes assets/img/ocr_test1.png | Bin 0 -> 117719 bytes src/chat_template/chat_template.rs | 104 + src/chat_template/mod.rs | 1 + src/lib.rs | 28 + src/models/mod.rs | 11 + src/models/qwen2_5vl/config.rs | 97 + src/models/qwen2_5vl/generate.rs | 137 + src/models/qwen2_5vl/mod.rs | 4 + src/models/qwen2_5vl/model.rs | 1229 +++++++++ src/models/qwen2_5vl/processor.rs | 462 ++++ src/position_embed/mod.rs | 1 + src/position_embed/rope.rs | 182 ++ src/tokenizer/mod.rs | 1 + src/tokenizer/tokenizer.rs | 44 + src/utils/audio_utils.rs | 0 src/utils/img_utils.rs | 62 + src/utils/mod.rs | 4 + src/utils/tensor_utils.rs | 202 ++ src/utils/utils.rs | 87 + src/utils/video_utils.rs | 13 + tests/config_tests.rs | 12 + tests/test_qwen2_5vl.rs | 56 + 25 files changed, 6662 insertions(+) create mode 100644 assets/img/ocr_test.png create mode 100644 assets/img/ocr_test1.png create mode 100644 src/chat_template/chat_template.rs create mode 100644 src/chat_template/mod.rs create mode 100644 src/models/mod.rs create mode 100644 src/models/qwen2_5vl/config.rs create mode 100644 src/models/qwen2_5vl/generate.rs create mode 100644 src/models/qwen2_5vl/mod.rs create mode 100644 src/models/qwen2_5vl/model.rs create mode 100644 src/models/qwen2_5vl/processor.rs create mode 100644 src/position_embed/mod.rs create mode 100644 src/position_embed/rope.rs create mode 100644 src/tokenizer/mod.rs create mode 100644 src/tokenizer/tokenizer.rs create mode 100644 src/utils/audio_utils.rs create mode 100644 src/utils/img_utils.rs create mode 100644 src/utils/mod.rs create mode 100644 src/utils/tensor_utils.rs create mode 100644 src/utils/utils.rs create mode 100644 src/utils/video_utils.rs create mode 100644 tests/config_tests.rs create mode 100644 tests/test_qwen2_5vl.rs diff --git a/Cargo.lock b/Cargo.lock index e2fd29b..0ce3965 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,3913 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aha" version = "0.0.0" +dependencies = [ + "anyhow", + "base64 0.22.1", + "candle-core", + "candle-flash-attn", + "candle-nn", + "candle-transformers", + "ffmpeg-next", + "image", + "minijinja", + "num", + "openai_dive", + "reqwest", + "serde", + "serde_json", + "tokenizers", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.3", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +dependencies = [ + "backtrace", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "av1-grain" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3efb2ca85bc610acfa917b5aaa36f3fcbebed5b3182d7f877b02531c4b80c8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.9.4", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bindgen_cuda" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f8489af5b7d17a81bffe37e0f4d6e1e4de87c87329d05447f22c35d95a1227d" +dependencies = [ + "glob", + "num_cpus", + "rayon", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "bitstream-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" + +[[package]] +name = "built" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytemuck" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +dependencies = [ + "serde", +] + +[[package]] +name = "candle-core" +version = "0.9.1" +source = "git+https://github.com/huggingface/candle.git#1a699fb2018d316dd5f6c1c179588e4cfcd7888e" +dependencies = [ + "byteorder", + "candle-kernels", + "cudarc", + "float8 0.4.2", + "gemm 0.17.1", + "half", + "memmap2", + "num-traits", + "num_cpus", + "rand 0.9.2", + "rand_distr", + "rayon", + "safetensors", + "thiserror 1.0.69", + "ug", + "ug-cuda", + "yoke 0.7.5", + "zip", +] + +[[package]] +name = "candle-flash-attn" +version = "0.9.1" +source = "git+https://github.com/huggingface/candle.git#1a699fb2018d316dd5f6c1c179588e4cfcd7888e" +dependencies = [ + "anyhow", + "bindgen_cuda", + "candle-core", + "half", +] + +[[package]] +name = "candle-kernels" +version = "0.9.1" +source = "git+https://github.com/huggingface/candle.git#1a699fb2018d316dd5f6c1c179588e4cfcd7888e" +dependencies = [ + "bindgen_cuda", +] + +[[package]] +name = "candle-nn" +version = "0.9.1" +source = "git+https://github.com/huggingface/candle.git#1a699fb2018d316dd5f6c1c179588e4cfcd7888e" +dependencies = [ + "candle-core", + "half", + "libc", + "num-traits", + "rayon", + "safetensors", + "serde", + "thiserror 1.0.69", +] + +[[package]] +name = "candle-transformers" +version = "0.9.1" +source = "git+https://github.com/huggingface/candle.git#1a699fb2018d316dd5f6c1c179588e4cfcd7888e" +dependencies = [ + "byteorder", + "candle-core", + "candle-nn", + "fancy-regex", + "num-traits", + "rand 0.9.2", + "rayon", + "serde", + "serde_json", + "serde_plain", + "tracing", +] + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cc" +version = "1.2.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "compact_str" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "serde", + "static_assertions", +] + +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.59.0", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "cudarc" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ba848ae5c6f3cb36e71eab5f268763e3fabcabe3f7bc683e16f7fa3d46281e" +dependencies = [ + "float8 0.3.0", + "half", + "libloading", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "dary_heap" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06d2e3287df1c007e74221c49ca10a95d557349e54b3a75dc2fb14712c751f04" +dependencies = [ + "serde", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dyn-stack" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e53799688f5632f364f8fb387488dd05db9fe45db7011be066fc20e7027f8b" +dependencies = [ + "bytemuck", + "reborrow", +] + +[[package]] +name = "dyn-stack" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490bd48eb68fffcfed519b4edbfd82c69cbe741d175b84f0e0cbe8c57cbe0bdd" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.0", +] + +[[package]] +name = "esaxx-rs" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6" +dependencies = [ + "cc", +] + +[[package]] +name = "exr" +version = "1.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fancy-regex" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" +dependencies = [ + "bit-set", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "ffmpeg-next" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d658424d233cbd993a972dd73a66ca733acd12a494c68995c9ac32ae1fe65b40" +dependencies = [ + "bitflags 2.9.4", + "ffmpeg-sys-next", + "libc", +] + +[[package]] +name = "ffmpeg-sys-next" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bca20aa4ee774fe384c2490096c122b0b23cf524a9910add0686691003d797b" +dependencies = [ + "bindgen", + "cc", + "libc", + "num_cpus", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "float8" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f498aec3b227cd892ce18967f4033d9d397d28a80a7ab67e9f6b0176a79654e" +dependencies = [ + "half", +] + +[[package]] +name = "float8" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4203231de188ebbdfb85c11f3c20ca2b063945710de04e7b59268731e728b462" +dependencies = [ + "cudarc", + "half", + "num-traits", + "rand 0.9.2", + "rand_distr", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gemm" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab24cc62135b40090e31a76a9b2766a501979f3070fa27f689c27ec04377d32" +dependencies = [ + "dyn-stack 0.10.0", + "gemm-c32 0.17.1", + "gemm-c64 0.17.1", + "gemm-common 0.17.1", + "gemm-f16 0.17.1", + "gemm-f32 0.17.1", + "gemm-f64 0.17.1", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab96b703d31950f1aeddded248bc95543c9efc7ac9c4a21fda8703a83ee35451" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-c32 0.18.2", + "gemm-c64 0.18.2", + "gemm-common 0.18.2", + "gemm-f16 0.18.2", + "gemm-f32 0.18.2", + "gemm-f64 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.6.0", + "seq-macro", +] + +[[package]] +name = "gemm-c32" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9c030d0b983d1e34a546b86e08f600c11696fde16199f971cd46c12e67512c0" +dependencies = [ + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-c32" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6db9fd9f40421d00eea9dd0770045a5603b8d684654816637732463f4073847" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-common 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.6.0", + "seq-macro", +] + +[[package]] +name = "gemm-c64" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbb5f2e79fefb9693d18e1066a557b4546cd334b226beadc68b11a8f9431852a" +dependencies = [ + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-c64" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfcad8a3d35a43758330b635d02edad980c1e143dc2f21e6fd25f9e4eada8edf" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-common 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.6.0", + "seq-macro", +] + +[[package]] +name = "gemm-common" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2e7ea062c987abcd8db95db917b4ffb4ecdfd0668471d8dc54734fdff2354e8" +dependencies = [ + "bytemuck", + "dyn-stack 0.10.0", + "half", + "num-complex", + "num-traits", + "once_cell", + "paste", + "pulp 0.18.22", + "raw-cpuid 10.7.0", + "rayon", + "seq-macro", + "sysctl 0.5.5", +] + +[[package]] +name = "gemm-common" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a352d4a69cbe938b9e2a9cb7a3a63b7e72f9349174a2752a558a8a563510d0f3" +dependencies = [ + "bytemuck", + "dyn-stack 0.13.0", + "half", + "libm", + "num-complex", + "num-traits", + "once_cell", + "paste", + "pulp 0.21.5", + "raw-cpuid 11.6.0", + "rayon", + "seq-macro", + "sysctl 0.6.0", +] + +[[package]] +name = "gemm-f16" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca4c06b9b11952071d317604acb332e924e817bd891bec8dfb494168c7cedd4" +dependencies = [ + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "gemm-f32 0.17.1", + "half", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "rayon", + "seq-macro", +] + +[[package]] +name = "gemm-f16" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff95ae3259432f3c3410eaa919033cd03791d81cebd18018393dc147952e109" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-common 0.18.2", + "gemm-f32 0.18.2", + "half", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.6.0", + "rayon", + "seq-macro", +] + +[[package]] +name = "gemm-f32" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9a69f51aaefbd9cf12d18faf273d3e982d9d711f60775645ed5c8047b4ae113" +dependencies = [ + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-f32" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc8d3d4385393304f407392f754cd2dc4b315d05063f62cf09f47b58de276864" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-common 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.6.0", + "seq-macro", +] + +[[package]] +name = "gemm-f64" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa397a48544fadf0b81ec8741e5c0fba0043008113f71f2034def1935645d2b0" +dependencies = [ + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-f64" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b2a4f76ce4b8b16eadc11ccf2e083252d8237c1b589558a49b0183545015bd" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-common 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.6.0", + "seq-macro", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.7+wasi-0.2.4", +] + +[[package]] +name = "gif" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "bytemuck", + "cfg-if", + "crunchy", + "num-traits", + "rand 0.9.2", + "rand_distr", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke 0.8.0", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke 0.8.0", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" + +[[package]] +name = "indexmap" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "indicatif" +version = "0.17.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width", + "web-time", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "libc", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + +[[package]] +name = "libc" +version = "0.2.175" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.3", +] + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "macro_rules_attribute" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65049d7923698040cd0b1ddcced9b0eb14dd22c5f86ae59c3740eab64a676520" +dependencies = [ + "macro_rules_attribute-proc_macro", + "paste", +] + +[[package]] +name = "macro_rules_attribute-proc_macro" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30" + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "memmap2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" +dependencies = [ + "libc", + "stable_deref_trait", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minijinja" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9f264d75233323f4b7d2f03aefe8a990690cdebfbfe26ea86bcbaec5e9ac990" +dependencies = [ + "serde", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "monostate" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f766eeb5719df144c29802e56f3f7e0b3f29bd3ec8ab6c819aa1eaddec3f80c" +dependencies = [ + "monostate-impl", + "serde", + "serde_core", +] + +[[package]] +name = "monostate-impl" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d4b3dcd6ce9277a52fb05f00bf3e45d475af8cce7376de2f8d6bd065fa4adb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "moxcms" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd32fa8935aeadb8a8a6b6b351e40225570a37c43de67690383d87ef170cd08" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "bytemuck", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "onig" +version = "6.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" +dependencies = [ + "bitflags 2.9.4", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "openai_dive" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bac82ff995cdbe4e120fa87fe4d76bd452866dd7b0c9bcbdb562ba4be0f1786d" +dependencies = [ + "bytes", + "derive_builder", + "reqwest", + "serde", + "serde_html_form", + "serde_json", + "tokio", + "tokio-util", +] + +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags 2.9.4", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "potential_utf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit 0.23.6", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "pulp" +version = "0.18.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0a01a0dc67cf4558d279f0c25b0962bd08fc6dec0137699eae304103e882fe6" +dependencies = [ + "bytemuck", + "libm", + "num-complex", + "reborrow", +] + +[[package]] +name = "pulp" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b86df24f0a7ddd5e4b95c94fc9ed8a98f1ca94d3b01bdce2824097e7835907" +dependencies = [ + "bytemuck", + "cfg-if", + "libm", + "num-complex", + "reborrow", + "version_check", +] + +[[package]] +name = "pxfm" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83f9b339b02259ada5c0f4a389b7fb472f933aa17ce176fd2ad98f28bb401fde" +dependencies = [ + "num-traits", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] +name = "rand_distr" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" +dependencies = [ + "num-traits", + "rand 0.9.2", +] + +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.12.1", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand 0.8.5", + "rand_chacha 0.3.1", + "simd_helpers", + "system-deps", + "thiserror 1.0.69", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.9.4", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-cond" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964d0cf57a3e7a06e8183d14a8b527195c706b7983549cd5462d5aa3747438f" +dependencies = [ + "either", + "itertools 0.14.0", + "rayon", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "reborrow" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" + +[[package]] +name = "regex" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + +[[package]] +name = "reqwest" +version = "0.12.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "rgb" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.9.4", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.0", +] + +[[package]] +name = "rustls" +version = "0.23.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "safetensors" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44560c11236a6130a46ce36c836a62936dc81ebf8c36a37947423571be0e55b6" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.0", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.4", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" + +[[package]] +name = "serde" +version = "1.0.226" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.226" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.226" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_html_form" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f" +dependencies = [ + "form_urlencoded", + "indexmap", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_plain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "spm_precompiled" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5851699c4033c63636f7ea4cf7b7c1f1bf06d0cc03cfb42e711de5a5c46cf326" +dependencies = [ + "base64 0.13.1", + "nom", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sysctl" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7dddc5f0fee506baf8b9fdb989e242f17e4b11c61dfbb0635b705217199eea" +dependencies = [ + "bitflags 2.9.4", + "byteorder", + "enum-as-inner", + "libc", + "thiserror 1.0.69", + "walkdir", +] + +[[package]] +name = "sysctl" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc" +dependencies = [ + "bitflags 2.9.4", + "byteorder", + "enum-as-inner", + "libc", + "thiserror 1.0.69", + "walkdir", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.4", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tempfile" +version = "3.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.61.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl 2.0.16", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokenizers" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6475a27088c98ea96d00b39a9ddfb63780d1ad4cceb6f48374349a96ab2b7842" +dependencies = [ + "ahash", + "aho-corasick", + "compact_str", + "dary_heap", + "derive_builder", + "esaxx-rs", + "getrandom 0.3.3", + "indicatif", + "itertools 0.14.0", + "log", + "macro_rules_attribute", + "monostate", + "onig", + "paste", + "rand 0.9.2", + "rayon", + "rayon-cond", + "regex", + "regex-syntax", + "serde", + "serde_json", + "spm_precompiled", + "thiserror 2.0.16", + "unicode-normalization-alignments", + "unicode-segmentation", + "unicode_categories", +] + +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "pin-project-lite", + "slab", + "socket2", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f63835928ca123f1bef57abbcd23bb2ba0ac9ae1235f1e65bda0d06e7786bd" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" +dependencies = [ + "indexmap", + "toml_datetime 0.7.2", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +dependencies = [ + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.4", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "ug" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76b761acf8af3494640d826a8609e2265e19778fb43306c7f15379c78c9b05b0" +dependencies = [ + "gemm 0.18.2", + "half", + "libloading", + "memmap2", + "num", + "num-traits", + "num_cpus", + "rayon", + "safetensors", + "serde", + "thiserror 1.0.69", + "tracing", + "yoke 0.7.5", +] + +[[package]] +name = "ug-cuda" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f0a1fa748f26166778c33b8498255ebb7c6bffb472bcc0a72839e07ebb1d9b5" +dependencies = [ + "cudarc", + "half", + "serde", + "thiserror 1.0.69", + "ug", +] + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "unicode-normalization-alignments" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f613e4fa046e69818dd287fdc4bc78175ff20331479dab6e1b0f98d57062de" +dependencies = [ + "smallvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b221ff421256839509adbb55998214a70d829d3a28c69b4a6672e9d2a42f67" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbe734895e869dc429d78c4b433f8d17d95f8d05317440b4fad5ab2d33e596dc" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.0", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +dependencies = [ + "windows-link 0.2.0", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link 0.1.3", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive 0.7.5", + "zerofrom", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive 0.8.0", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke 0.8.0", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke 0.8.0", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cc23c04387f4da0374be4533ad1208cbb091d5c11d070dfef13676ad6497164" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "indexmap", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index 4f7c6c7..bc67317 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,21 @@ license = "Apache-2.0" description = "aha" [dependencies] +candle-core = { git = "https://github.com/huggingface/candle.git", version = "0.9.1"} +candle-nn = { git = "https://github.com/huggingface/candle.git", version = "0.9.1"} +candle-transformers = { git = "https://github.com/huggingface/candle.git", version = "0.9.1"} +candle-flash-attn = { git = "https://github.com/huggingface/candle.git", optional = true } +serde = "1.0.226" +serde_json = "1.0.145" +anyhow = "1.0.100" +ffmpeg-next = "8.0.0" +image = "0.25.8" +reqwest = { version = "0.12.23", features = ["blocking"] } +base64 = "0.22.1" +num = "0.4.3" +minijinja = "2.12.0" +tokenizers = "0.22.1" +openai_dive = "1.3.0" +[features] +flash-attn=["candle-flash-attn"] +cuda=["candle-nn/cuda", "candle-core/cuda", "candle-transformers/cuda"] diff --git a/assets/img/ocr_test.png b/assets/img/ocr_test.png new file mode 100644 index 0000000000000000000000000000000000000000..b69f68e3310bdb44373e1b68dc9dbafb07eed734 GIT binary patch literal 63470 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 SK|d1)0LV&yk*E?g3jTk4^G;|0 literal 0 HcmV?d00001 diff --git a/assets/img/ocr_test1.png b/assets/img/ocr_test1.png new file mode 100644 index 0000000000000000000000000000000000000000..39ab8dd7b1ba012adcf8664c68d689f3fb3dbc07 GIT binary patch literal 117719 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 vAJ)yoGy~Z&nEi-E!8Jp4}1=k@Ls# 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%0Aws3Hh{MUy-c3v-C&;(u+0^cll% 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 LS*lXPIQYK-e{Ybs literal 0 HcmV?d00001 diff --git a/src/chat_template/chat_template.rs b/src/chat_template/chat_template.rs new file mode 100644 index 0000000..b290195 --- /dev/null +++ b/src/chat_template/chat_template.rs @@ -0,0 +1,104 @@ +use crate::utils::utils::string_to_static_str; +use anyhow::{Result, anyhow}; +use minijinja::{Environment, Value as MiniJinjaValue, context}; +use openai_dive::v1::resources::chat::ChatCompletionParameters; + +pub fn get_template(path: String) -> Result { + let tokenizer_config_file = path.clone() + "/tokenizer_config.json"; + assert!( + std::path::Path::new(&tokenizer_config_file).exists(), + "tokenizer_config.json not exists in model path" + ); + let tokenizer_config: serde_json::Value = + serde_json::from_slice(&std::fs::read(tokenizer_config_file)?) + .map_err(|e| anyhow!(format!("load tokenizer_config file error:{}", e)))?; + let chat_template = tokenizer_config["chat_template"] + .as_str() + .ok_or(anyhow!(format!("chat_template to str error")))?; + // 修复模板中的问题行 + let fixed_template = chat_template + .replace( + "message.content.startswith('')", + "message.content is startingwith('')", // 使用minijinja中的 is startingwith 替换 + ) + .replace( + "message.content.endswith('')", + "message.content is endingwith('')", // 使用minijinja中的 is endingwith 替换 + ) + .replace( + "content.split('')[0].rstrip('\\n').split('')[-1].lstrip('\\n')", + "((content | split(''))[0] | rstrip('\\n') | split(''))[-1] | lstrip('\\n')", // 使用自定义的split, rstrip, lstrip过滤器替换 + ) + .replace( + "content.split('')[-1].lstrip('\\n')", + "(content | split(''))[-1] | lstrip('\\n')", // 使用自定义的过滤器替换 + ) + .replace( + "reasoning_content.strip('\\n')", + "reasoning_content | strip('\\n')", // 使用自定义的过滤器替换 + ) + .replace( + "content.lstrip('\\n')", + "content | lstrip('\\n')", // 使用自定义的过滤器替换 + ); + Ok(fixed_template) +} + +pub struct ChatTemplate<'a> { + env: Environment<'a>, +} + +impl<'a> ChatTemplate<'a> { + pub fn init(path: &str) -> Result { + let path = path.to_string(); + assert!( + std::path::Path::new(&path).exists(), + "model path file not exists" + ); + let template = get_template(path)?; + let template = string_to_static_str(template); + // 加载jinjaenv处理chat_template + let mut env = Environment::new(); + // 添加自定义过滤器 + env.add_filter("tojson", |v: MiniJinjaValue| { + serde_json::to_string(&v).unwrap() + }); + + env.add_filter("split", |s: String, delimiter: String| { + s.split(&delimiter) + .map(|s| s.to_string()) + .collect::>() + }); + + // 添加 lstrip 过滤器 + env.add_filter("lstrip", |s: String, chars: Option| match chars { + Some(chars_str) => s.trim_start_matches(chars_str.as_str()).to_string(), + None => s.trim_start().to_string(), + }); + + // 添加 rstrip 过滤器 + env.add_filter("rstrip", |s: String, chars: Option| match chars { + Some(chars_str) => s.trim_end_matches(chars_str.as_str()).to_string(), + None => s.trim_end().to_string(), + }); + // let template = get_template(path.to_string())?; + let _ = env.add_template("chat", template); + + Ok(Self { env }) + } + + pub fn apply_chat_template(&self, messages: &ChatCompletionParameters) -> Result { + let context = context! { + messages => &messages.messages, + add_generation_prompt => true, + }; + let template = self + .env + .get_template("chat") + .map_err(|e| anyhow!(format!("render template error {}", e)))?; + let message_str = template + .render(context) + .map_err(|e| anyhow!(format!("render template error {}", e)))?; + Ok(message_str) + } +} diff --git a/src/chat_template/mod.rs b/src/chat_template/mod.rs new file mode 100644 index 0000000..6854abd --- /dev/null +++ b/src/chat_template/mod.rs @@ -0,0 +1 @@ +pub mod chat_template; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index e69de29..897c0a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -0,0 +1,28 @@ +use crate::models::{GenerateModel, qwen2_5vl::generate::Qwen2_5VLGenerateModel}; +use anyhow::{Ok, Result}; +use candle_core::{DType, Device}; +pub mod chat_template; +pub mod models; +pub mod position_embed; +pub mod tokenizer; +pub mod utils; + +pub enum ModelType { + Qwen2_5VL, +} + +impl ModelType { + pub fn init( + model_type: ModelType, + model_path: &str, + device: Option<&Device>, + dtype: Option, + ) -> Result> { + match model_type { + ModelType::Qwen2_5VL => { + let model = Qwen2_5VLGenerateModel::init(model_path, device, dtype)?; + Ok(Box::new(model)) + } + } + } +} diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..10140c7 --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1,11 @@ +pub mod qwen2_5vl; +use anyhow::Result; +use candle_core::{DType, Device}; +use openai_dive::v1::resources::chat::ChatCompletionParameters; + +pub trait GenerateModel { + fn init(path: &str, device: Option<&Device>, dtype: Option) -> Result + where + Self: Sized; + fn generate(&mut self, mes: ChatCompletionParameters) -> Result; +} diff --git a/src/models/qwen2_5vl/config.rs b/src/models/qwen2_5vl/config.rs new file mode 100644 index 0000000..a3f3a9d --- /dev/null +++ b/src/models/qwen2_5vl/config.rs @@ -0,0 +1,97 @@ +use candle_nn::Activation; + +#[derive(Debug, Clone, PartialEq, serde::Deserialize)] +pub struct VisionConfig { + pub depth: usize, + pub hidden_act: Activation, + pub hidden_size: usize, + pub intermediate_size: usize, + pub num_heads: usize, + pub in_chans: usize, + pub out_hidden_size: usize, + pub patch_size: usize, + pub spatial_merge_size: usize, + pub spatial_patch_size: usize, + pub window_size: usize, + pub fullatt_block_indexes: Vec, + pub tokens_per_second: usize, + pub temporal_patch_size: usize, +} +#[derive(Debug, Clone, PartialEq, serde::Deserialize)] +pub struct RopeScaling { + pub r#type: String, + pub mrope_section: Vec, +} + +#[derive(Debug, Clone, PartialEq, serde::Deserialize)] +pub struct Config { + pub attention_dropout: f32, + pub bos_token_id: usize, + pub eos_token_id: usize, + pub vision_start_token_id: usize, + pub vision_end_token_id: usize, + pub vision_token_id: usize, + pub image_token_id: usize, + pub video_token_id: usize, + pub hidden_act: Activation, + pub hidden_size: usize, + pub initializer_range: f32, + pub intermediate_size: usize, + pub max_position_embeddings: usize, + pub max_window_layers: usize, + pub num_attention_heads: usize, + pub num_hidden_layers: usize, + pub num_key_value_heads: usize, + pub rms_norm_eps: f64, + pub rope_theta: f32, + pub sliding_window: usize, + pub tie_word_embeddings: bool, + pub torch_dtype: String, + pub use_sliding_window: bool, + pub vision_config: VisionConfig, + pub rope_scaling: RopeScaling, + pub vocab_size: usize, +} + +pub struct VisionSetting { + pub image_factor: u32, + pub min_pixels: u32, + pub max_pixels: u32, + pub max_ratio: u32, + pub temporal_patch_size: usize, + pub patch_size: usize, + pub merge_size: usize, + pub video_min_pixels: u32, + pub video_max_pixels: u32, + pub video_total_pixels: u32, + pub frame_factor: u32, + pub fps: f32, + pub fps_min_frames: u32, + pub fps_max_frames: u32, + pub image_mean: Vec, + pub image_std: Vec, +} + +impl VisionSetting { + pub fn default() -> Self { + Self { + image_factor: 28, + min_pixels: 4 * 28 * 28, + max_pixels: 16384 * 28 * 28, + // max_pixels: 1000 * 28 * 28, + max_ratio: 200, + temporal_patch_size: 2, + patch_size: 14, + merge_size: 2, + video_min_pixels: 128 * 28 * 28, + video_max_pixels: 768 * 28 * 28, + video_total_pixels: 24576 * 28 * 28, + frame_factor: 2, + fps: 2.0, + fps_min_frames: 4, + fps_max_frames: 768, + image_mean: vec![0.48145466_f32, 0.4578275, 0.40821073], + image_std: vec![0.26862954, 0.26130258, 0.27577711], + } + } +} diff --git a/src/models/qwen2_5vl/generate.rs b/src/models/qwen2_5vl/generate.rs new file mode 100644 index 0000000..6006834 --- /dev/null +++ b/src/models/qwen2_5vl/generate.rs @@ -0,0 +1,137 @@ +use crate::models::qwen2_5vl::config::Config; +use crate::utils::utils::{find_safetensors_files, get_device, get_dtype}; +use crate::{ + chat_template::chat_template::ChatTemplate, + models::{ + GenerateModel, + qwen2_5vl::{model::Qwen2_5VLModel, processor::Qwen2_5VLProcessor}, + }, + tokenizer::tokenizer::TokenizerModel, +}; +use anyhow::{Result, anyhow}; +use candle_core::{D, DType, Device, IndexOp, Tensor}; +use candle_nn::VarBuilder; +use candle_transformers::generation::LogitsProcessor; +use openai_dive::v1::resources::chat::ChatCompletionParameters; + +pub struct Qwen2_5VLGenerateModel<'a> { + chat_template: ChatTemplate<'a>, + tokenizer: TokenizerModel, + pre_processor: Qwen2_5VLProcessor, + qwen2_5_vl: Qwen2_5VLModel, + device: Device, + dtype: DType, +} + +impl<'a> GenerateModel for Qwen2_5VLGenerateModel<'a> { + fn init(path: &str, device: Option<&Device>, dtype: Option) -> Result { + let chat_template = ChatTemplate::init(path)?; + let tokenizer = TokenizerModel::init(path)?; + let config_path = path.to_string() + "/config.json"; + let cfg: Config = serde_json::from_slice(&std::fs::read(config_path)?)?; + let device = &get_device(device); + let cfg_dtype = cfg.torch_dtype.as_str(); + let dtype = get_dtype(dtype, cfg_dtype); + let pre_processor = Qwen2_5VLProcessor::new(device, dtype)?; + + let model_list = find_safetensors_files(&path)?; + let vb = unsafe { VarBuilder::from_mmaped_safetensors(&model_list, dtype, device)? }; + let qwen2_5_vl = Qwen2_5VLModel::new(cfg, vb)?; + Ok(Qwen2_5VLGenerateModel { + chat_template, + tokenizer, + pre_processor, + qwen2_5_vl, + device: device.clone(), + dtype: dtype, + }) + } + fn generate(&mut self, mes: ChatCompletionParameters) -> Result { + let temperature = match mes.temperature { + Some(temp) => Some(temp as f64), + None => None, + }; + let top_p = match mes.top_p { + Some(tp) => Some(tp as f64), + None => None, + }; + let mut logit_processor = LogitsProcessor::new(34562, temperature, top_p); + let mes_render = self.chat_template.apply_chat_template(&mes)?; + let input = self.pre_processor.process_info(&mes, &mes_render)?; + let mut input_ids = self + .tokenizer + .text_encode(input.replace_text.clone(), &self.device)?; + let mut seq_len = input_ids.dim(1)?; + let mut seqlen_offset = 0; + let end_of_text_id = self.qwen2_5_vl.cfg.bos_token_id as u32; + let im_end_id = self.qwen2_5_vl.cfg.eos_token_id as u32; + let mut pixel_values = if input.pixel_values.is_some() { + Some(&input.pixel_values.unwrap().clone()) + } else { + None + }; + let image_grid_thw = if input.image_grid_thw.is_some() { + Some(&input.image_grid_thw.unwrap().clone()) + } else { + None + }; + let mut pixel_values_video = if input.pixel_values_video.is_some() { + Some(&input.pixel_values_video.unwrap().clone()) + } else { + None + }; + let video_grid_thw = if input.video_grid_thw.is_some() { + Some(&input.video_grid_thw.unwrap().clone()) + } else { + None + }; + let second_per_grid_ts = if input.second_per_grid_ts.is_some() { + Some(input.second_per_grid_ts.unwrap().clone()) + } else { + None + }; + + let mut mask = Tensor::ones_like(&input_ids)?; + let mut cache_position = Tensor::ones_like(&input_ids.i(0)?)? + .to_dtype(candle_core::DType::F64)? + .cumsum(D::Minus1)? + .to_dtype(candle_core::DType::U32)? + .broadcast_sub(&Tensor::new(vec![1_u32], input_ids.device())?)?; + + let mut generate = Vec::new(); + let sample_len = match mes.max_tokens { + Some(max) => max, + None => 512, + }; + for _ in 0..sample_len { + let logits = self.qwen2_5_vl.forward( + &input_ids, + pixel_values, + image_grid_thw, + pixel_values_video, + video_grid_thw, + &mask, + Some(&cache_position), + seqlen_offset, + second_per_grid_ts.clone(), + )?; + let logits = logits.squeeze(0)?.squeeze(0)?.to_dtype(DType::F32)?; + let next_token = logit_processor.sample(&logits)?; + generate.push(next_token); + if next_token == end_of_text_id || next_token == im_end_id { + break; + } + seqlen_offset += seq_len; + seq_len = 1; + input_ids = Tensor::from_vec(vec![next_token], (1, 1), &self.device)?; + let appendd_mask = Tensor::ones((1, 1), mask.dtype(), &self.device)?; + mask = Tensor::cat(&[mask, appendd_mask], 1)?; + cache_position = Tensor::from_vec(vec![seqlen_offset as u32], 1, &self.device)?; + pixel_values = None; + pixel_values_video = None; + } + let res = self.tokenizer.token_decode(generate)?; + self.qwen2_5_vl.clear_kv_cache(); + Ok(res) + } +} diff --git a/src/models/qwen2_5vl/mod.rs b/src/models/qwen2_5vl/mod.rs new file mode 100644 index 0000000..8d2989c --- /dev/null +++ b/src/models/qwen2_5vl/mod.rs @@ -0,0 +1,4 @@ +pub mod config; +pub mod model; +pub mod processor; +pub mod generate; \ No newline at end of file diff --git a/src/models/qwen2_5vl/model.rs b/src/models/qwen2_5vl/model.rs new file mode 100644 index 0000000..0a32f00 --- /dev/null +++ b/src/models/qwen2_5vl/model.rs @@ -0,0 +1,1229 @@ +use crate::{ + models::qwen2_5vl::config::{Config, RopeScaling}, + position_embed::rope::{ + Qwen2_5VLTextRotaryEmbedding, Qwen2_5VisionRotaryEmbedding, apply_rotary_pos_emb, + apply_rotary_pos_emb_vision, + }, + utils::tensor_utils::{ + get_equal_mask, get_vision_next_indices, masked_scatter_dim0, nonzero_index, + safe_arg_sort_last_dim, zero_index, + }, +}; +use anyhow::{Result, anyhow}; +use candle_core::{D, DType, Device, IndexOp, Tensor}; +use candle_nn::{ + Activation, Init, Linear, Module, RmsNorm, VarBuilder, linear, linear_no_bias, rms_norm, +}; + +pub struct Qwen2_5VisionPatchEmbed { + conv3d_weight: Tensor, +} + +impl Qwen2_5VisionPatchEmbed { + pub fn new(cfg: &Config, vb: VarBuilder) -> Result { + let patch_size = cfg.vision_config.patch_size; + let temporal_patch_size = cfg.vision_config.temporal_patch_size; + let in_channels = cfg.vision_config.in_chans; + let embed_dim = cfg.vision_config.hidden_size; + // conv3d weight key: visual.patch_embed.proj.weight, value: Tensor[dims 1280, 3, 2, 14, 14; bf16, cuda:0] + // (1280, 3, 2, 14, 14) -> (1280, 1176) -> (1176, 1280) + let conv3d_weight = vb + .get_with_hints( + ( + embed_dim, + in_channels, + temporal_patch_size, + patch_size, + patch_size, + ), + "proj.weight", + Init::Const(1.), + )? + .flatten(1, 4)? + .t()?; + Ok(Self { conv3d_weight }) + } + + pub fn forward(&self, hidden_states: &Tensor) -> Result { + // hidden_states shape: (grid_t*grid_h*grid_w, c*temporal_patch_size*patch_size*patch_size) + // ((), 1176) matmul (1176, 1280) -> ((), 1280) + let hidden_states = hidden_states.matmul(&self.conv3d_weight)?; + Ok(hidden_states) + } +} + +pub struct Qwen2_5VLPatchMerger { + hidden_size: usize, + ln_q: RmsNorm, + mlp_0: Linear, + mlp_2: Linear, +} + +impl Qwen2_5VLPatchMerger { + pub fn new(cfg: &Config, vb: VarBuilder) -> Result { + let hidden_size = + cfg.vision_config.hidden_size * (cfg.vision_config.spatial_merge_size.pow(2)); + let ln_q = rms_norm( + cfg.vision_config.hidden_size, + cfg.rms_norm_eps, + vb.pp("ln_q"), + )?; + let mlp_0 = linear(hidden_size, hidden_size, vb.pp("mlp.0"))?; + let mlp_2 = linear( + hidden_size, + cfg.vision_config.out_hidden_size, + vb.pp("mlp.2"), + )?; + Ok(Self { + hidden_size, + ln_q, + mlp_0, + mlp_2, + }) + } +} +impl Module for Qwen2_5VLPatchMerger { + fn forward(&self, xs: &Tensor) -> candle_core::Result { + let xs = xs.apply(&self.ln_q)?.reshape(((), self.hidden_size))?; + let xs = xs.apply(&self.mlp_0)?.gelu()?.apply(&self.mlp_2)?; + Ok(xs) + } +} + +#[derive(Debug, Clone)] +struct Qwen2_5VLVisionMLP { + gate_proj: Linear, + up_proj: Linear, + down_proj: Linear, + act_fn: Activation, +} + +impl Qwen2_5VLVisionMLP { + fn new(cfg: &Config, vb: VarBuilder) -> Result { + let hidden_sz = cfg.vision_config.hidden_size; + let intermediate_sz = cfg.vision_config.intermediate_size; + let gate_proj = linear(hidden_sz, intermediate_sz, vb.pp("gate_proj"))?; + let up_proj = linear(hidden_sz, intermediate_sz, vb.pp("up_proj"))?; + let down_proj = linear(intermediate_sz, hidden_sz, vb.pp("down_proj"))?; + Ok(Self { + gate_proj, + up_proj, + down_proj, + act_fn: cfg.hidden_act, + }) + } +} + +impl Module for Qwen2_5VLVisionMLP { + fn forward(&self, xs: &Tensor) -> candle_core::Result { + let lhs = xs.apply(&self.gate_proj)?.apply(&self.act_fn)?; + let rhs = xs.apply(&self.up_proj)?; + (lhs * rhs)?.apply(&self.down_proj) + } +} + +#[derive(Debug, Clone)] +struct Qwen2_5VLVisionAttention { + qkv: Linear, + proj: Linear, + num_heads: usize, + scale: Tensor, +} + +impl Qwen2_5VLVisionAttention { + fn new(cfg: &Config, vb: VarBuilder) -> Result { + let hidden_size = cfg.vision_config.hidden_size; + let num_heads = cfg.vision_config.num_heads; + let head_dim = hidden_size / num_heads; + let qkv = linear(hidden_size, hidden_size * 3, vb.pp("qkv"))?; + let proj = linear(hidden_size, hidden_size, vb.pp("proj"))?; + let scale = Tensor::new(vec![1f32 / (head_dim as f32).sqrt()], vb.device())? + .to_dtype(vb.dtype())?; + Ok(Self { + qkv, + proj, + num_heads, + scale, + }) + } + + fn forward( + &self, + xs: &Tensor, + cos: &Tensor, + sin: &Tensor, + attention_mask: &Tensor, + ) -> Result { + // xs: (seq_len, hidden_size) + let seq_length = xs.dim(0)?; + // (seq_len, hidden_size) -> (seq_len, hidden_size*3) + // -> (seq_len, 3, num_heads, head_dim) -> (3, seq_len, num_heads, head_dim) + let qkv_states = xs + .apply(&self.qkv)? + .reshape((seq_length, 3, self.num_heads, ()))? + .permute((1, 0, 2, 3))?; + // (seq_len, num_heads, head_dim) + let query_states = qkv_states.i(0)?.contiguous()?; + let key_states = qkv_states.i(1)?.contiguous()?; + let value_states = qkv_states.i(2)?.contiguous()?; + let (query_states, key_states) = + apply_rotary_pos_emb_vision(&query_states, &key_states, cos, sin)?; + // (seq_len, num_heads, head_dim) -> (num_heads, seq_len, head_dim) + let query_states = query_states.transpose(0, 1)?.contiguous()?; + let key_states = key_states.transpose(0, 1)?.contiguous()?; + let value_states = value_states.transpose(0, 1)?.contiguous()?; + + let attn_output = { + let attn_weights = query_states + .matmul(&key_states.transpose(D::Minus2, D::Minus1)?)? + .broadcast_mul(&self.scale)?; + let attn_weights = attn_weights.broadcast_add(&attention_mask)?; + let attn_weights = candle_nn::ops::softmax_last_dim(&attn_weights)?; + attn_weights.matmul(&value_states)? + }; + // (num_heads, seq_len, head_dim) -> (seq_len, num_heads, head_dim) -> (seq_len, hidden_size) + let attn_output = attn_output + .transpose(0, 1)? + .reshape((seq_length, ()))? + .contiguous()?; + let attn_ouput = attn_output.apply(&self.proj)?; + Ok(attn_ouput) + } +} + +#[derive(Debug, Clone)] +struct Qwen2_5VLVisionBlock { + attn: Qwen2_5VLVisionAttention, + mlp: Qwen2_5VLVisionMLP, + norm1: RmsNorm, + norm2: RmsNorm, +} + +impl Qwen2_5VLVisionBlock { + fn new(cfg: &Config, vb: VarBuilder) -> Result { + let attn = Qwen2_5VLVisionAttention::new(cfg, vb.pp("attn"))?; + let mlp = Qwen2_5VLVisionMLP::new(cfg, vb.pp("mlp"))?; + let norm1 = rms_norm( + cfg.vision_config.hidden_size, + cfg.rms_norm_eps, + vb.pp("norm1"), + )?; + let norm2 = rms_norm( + cfg.vision_config.hidden_size, + cfg.rms_norm_eps, + vb.pp("norm2"), + )?; + + Ok(Self { + attn, + mlp, + norm1, + norm2, + }) + } + + fn forward( + &self, + xs: &Tensor, + cos: &Tensor, + sin: &Tensor, + attention_mask: &Tensor, + ) -> Result { + let residual = xs; + let xs = self.norm1.forward(xs)?; + let xs = self.attn.forward(&xs, cos, sin, attention_mask)?; + let xs = (xs + residual)?; + let residual = &xs; + let xs = xs.apply(&self.norm2)?.apply(&self.mlp)?; + let xs = (residual + xs)?; + Ok(xs) + } +} + +pub struct Qwen2_5VLVisionModel { + spatial_merge_size: usize, + patch_size: usize, + fullatt_block_indexes: Vec, + window_size: usize, + spatial_merge_unit: usize, + patch_embed: Qwen2_5VisionPatchEmbed, + rotary_pos_emb: Qwen2_5VisionRotaryEmbedding, + blocks: Vec, + merger: Qwen2_5VLPatchMerger, + dtype: DType, +} + +impl Qwen2_5VLVisionModel { + pub fn new(cfg: &Config, vb: VarBuilder) -> Result { + let spatial_merge_size = cfg.vision_config.spatial_merge_size; + let patch_size = cfg.vision_config.patch_size; + let fullatt_block_indexes = cfg.vision_config.fullatt_block_indexes.clone(); + let window_size = cfg.vision_config.window_size; + let spatial_merge_unit = spatial_merge_size * spatial_merge_size; + let head_dim = cfg.vision_config.hidden_size / cfg.vision_config.num_heads; + let patch_embed = Qwen2_5VisionPatchEmbed::new(cfg, vb.pp("patch_embed"))?; + let rotary_pos_emb = Qwen2_5VisionRotaryEmbedding::new(head_dim / 2, None); + let mut blocks = Vec::new(); + let vb_blocks = vb.pp("blocks"); + for i in 0..cfg.vision_config.depth { + let block = Qwen2_5VLVisionBlock::new(cfg, vb_blocks.pp(i))?; + blocks.push(block); + } + let merger = Qwen2_5VLPatchMerger::new(cfg, vb.pp("merger"))?; + let dtype = vb.dtype(); + Ok(Self { + spatial_merge_size, + patch_size, + fullatt_block_indexes, + window_size, + spatial_merge_unit, + patch_embed, + rotary_pos_emb, + blocks, + merger, + dtype, + }) + } + + pub fn rot_pos_emb(&self, grid_thw: &Tensor) -> Result { + let mut pos_ids = Vec::new(); + for i in 0..grid_thw.dim(0)? { + let [t, h, w] = grid_thw.i(i)?.to_vec1::()?[..] else { + return Err(anyhow!(format!("grid_thw Expected exactly 3 elements"))); + }; + // hpos_ids shape (h, w) + let hpos_ids = Tensor::arange(0, h, grid_thw.device())? + .unsqueeze(1)? + .expand((h as usize, w as usize))?; + let hpos_ids = hpos_ids.reshape(( + h as usize / self.spatial_merge_size, + self.spatial_merge_size, + w as usize / self.spatial_merge_size, + self.spatial_merge_size, + ))?; + let hpos_ids = hpos_ids.permute((0, 2, 1, 3))?.flatten_all()?; + let wpos_ids = Tensor::arange(0, w, grid_thw.device())? + .unsqueeze(0)? + .expand((h as usize, w as usize))?; + let wpos_ids = wpos_ids.reshape(( + h as usize / self.spatial_merge_size, + self.spatial_merge_size, + w as usize / self.spatial_merge_size, + self.spatial_merge_size, + ))?; + let wpos_ids = wpos_ids.permute((0, 2, 1, 3))?.flatten_all()?; + // thw_pos_ids shape (h*w, 2) + let thw_pos_ids = + Tensor::stack(&[&hpos_ids, &wpos_ids], D::Minus1)?.repeat((t as usize, 1))?; + pos_ids.push(thw_pos_ids); + } + let pos_ids = Tensor::cat(&pos_ids, 0)?.contiguous()?; + let max_grid_size = grid_thw.i((.., 1..))?.max_all()?.to_scalar::()?; + let rotary_pos_emb_full = self + .rotary_pos_emb + .forward(max_grid_size as usize, grid_thw.device())?; + + // contiguous()一定要加!!!很重要!!!!,不然index_select出来的是错的 + // 找错找了半天,都是泪啊,做维度索引操作后contiguous顺手写上总没错 + // 第一列是h维度的索引 + let pos_ids_h = pos_ids.i((.., 0))?.contiguous()?; + // 第二列是w维度的索引 + let pos_ids_w = pos_ids.i((.., 1))?.contiguous()?; + let rotary_pos_emb_h = rotary_pos_emb_full.index_select(&pos_ids_h, 0)?; + let rotary_pos_emb_w = rotary_pos_emb_full.index_select(&pos_ids_w, 0)?; + // 每个patch融合h索引和w索引两个的位置编码信息 + let rotary_pos_emb = Tensor::cat(&[rotary_pos_emb_h, rotary_pos_emb_w], 1)?.contiguous()?; + Ok(rotary_pos_emb) + } + + pub fn get_window_index(&self, grid_thw: &Tensor) -> Result<(Tensor, Tensor)> { + let mut window_index = Vec::new(); + let mut cu_window_seqlens = vec![0]; + let mut window_index_id = 0_i64; + + let vit_merger_window_size = + (self.window_size / self.spatial_merge_size / self.patch_size) as u32; + for i in 0..grid_thw.dim(0)? { + let [grid_t, grid_h, grid_w] = grid_thw.i(i)?.to_vec1::()?[..] else { + return Err(anyhow!(format!("grid_thw Expected exactly 3 elements"))); + }; + let llm_grid_h = grid_h / self.spatial_merge_size as u32; + let llm_grid_w = grid_w / self.spatial_merge_size as u32; + // 因为后续需要使用-100来做填充,所以需要int类型 + // candle好像不支持i32, DType里面都没有定义i32, 所以这里使用i64 + let mut index = Tensor::arange( + window_index_id, + window_index_id + (grid_t * llm_grid_h * llm_grid_w) as i64, + grid_thw.device(), + )? + .reshape((grid_t as usize, llm_grid_h as usize, llm_grid_w as usize))? + .contiguous()?; + // python transformers 中实现如下 + // let pad_h = (vit_merger_window_size - llm_grid_h % vit_merger_window_size); + // 后面加上 % vit_merger_window_size,保证llm_grid_h能整除vit_merger_window_size时不需要pad + // 按理说能整除应该是不需要pad的,transformers中这样实现不知道是不是有什么其他原因 + let pad_h = (vit_merger_window_size - llm_grid_h % vit_merger_window_size) + % vit_merger_window_size; + let pad_w = (vit_merger_window_size - llm_grid_w % vit_merger_window_size) + % vit_merger_window_size; + let num_window_h = (llm_grid_h + pad_h) / vit_merger_window_size; + let num_window_w = (llm_grid_w + pad_w) / vit_merger_window_size; + if pad_h > 0 { + let pad_h_t = Tensor::new(vec![-100_i64], grid_thw.device())? + .broadcast_as((grid_t as usize, pad_h as usize, llm_grid_w as usize))? + .contiguous()?; + index = Tensor::cat(&[&index, &pad_h_t], 1)?; + } + if pad_w > 0 { + let pad_w_t = Tensor::new(vec![-100_i64], grid_thw.device())? + .broadcast_as(( + grid_t as usize, + (llm_grid_h + pad_h) as usize, + pad_w as usize, + ))? + .contiguous()?; + index = Tensor::cat(&[&index, &pad_w_t], 2)?; + } + let index_padded = index + .reshape(( + grid_t as usize, + num_window_h as usize, + vit_merger_window_size as usize, + num_window_w as usize, + vit_merger_window_size as usize, + ))? + .permute((0, 1, 3, 2, 4))?; + let index_padded = index_padded + .reshape(( + grid_t as usize, + (num_window_h * num_window_w) as usize, + vit_merger_window_size as usize, + vit_merger_window_size as usize, + ))? + .contiguous()?; + let is_pad = Tensor::new(vec![-100_i64], grid_thw.device())?; + let seqlens = index_padded + .broadcast_ne(&is_pad)? + .sum((2, 3))? + .flatten_all()?; + let index_padded = index_padded.flatten_all()?; + let not_pad = index_padded.broadcast_ne(&is_pad)?.to_vec1::()?; + let indices: Vec = not_pad + .iter() + .enumerate() + .filter_map(|(idx, &val)| if val != 0 { Some(idx as u32) } else { None }) + .collect(); + let indices_tensor = Tensor::from_slice(&indices, indices.len(), grid_thw.device())?; + let index_new = index_padded.gather(&indices_tensor, 0)?; + window_index.push(index_new); + + let seq_len_last = cu_window_seqlens[cu_window_seqlens.len() - 1]; + // cumsum方法i64类型执行会报错,先转成F64计算后再转回i64 + let cu_seqlens_tmp = seqlens + .to_dtype(candle_core::DType::F64)? + .cumsum(0)? + .to_dtype(candle_core::DType::I64)? + .broadcast_mul(&Tensor::new( + vec![self.spatial_merge_unit as i64], + grid_thw.device(), + )?)? + .broadcast_add(&Tensor::new(vec![seq_len_last], grid_thw.device())?)?; + cu_window_seqlens.extend_from_slice(&cu_seqlens_tmp.to_vec1::()?); + window_index_id += (grid_t * llm_grid_h * llm_grid_w) as i64; + } + let window_index_tensor = Tensor::cat(&window_index, 0)?; + let cu_window_seqlens_tensor = Tensor::from_slice( + &cu_window_seqlens, + cu_window_seqlens.len(), + grid_thw.device(), + )? + .to_dtype(candle_core::DType::U32)?; + Ok((window_index_tensor, cu_window_seqlens_tensor)) + } + + pub fn get_attention_mask( + &self, + cu_seqlens: &Tensor, + seq_len: usize, + device: &Device, + dtype: DType, + ) -> Result { + let mut attention_mask = + Tensor::new(f32::NEG_INFINITY, device)?.broadcast_as((1, seq_len, seq_len))?; + for i in 1..cu_seqlens.dim(0)? { + let start = cu_seqlens.i(i - 1)?.to_scalar::()? as usize; + let end = cu_seqlens.i(i)?.to_scalar::()? as usize; + let block_size = end - start; + let zeros = + Tensor::zeros((1, block_size, block_size), candle_core::DType::F32, device)?; + attention_mask = + attention_mask.slice_assign(&[(0..1), (start..end), (start..end)], &zeros)?; + } + let attention_mask = attention_mask.to_dtype(dtype)?.contiguous()?; + Ok(attention_mask) + } + + pub fn forward(&self, hidden_states: &Tensor, grid_thw: &Tensor) -> Result { + // hidden_states: (seq_len, hidden_size) + // grid_thw: (num_images_or_videos, 3), temporal, height, width + let hidden_states = hidden_states.to_dtype(self.dtype)?; + let hidden_states = self.patch_embed.forward(&hidden_states)?; + let rotary_pos_emb = self.rot_pos_emb(grid_thw)?; + let (window_index, cu_window_seqlens) = self.get_window_index(grid_thw)?; + let seq_len = hidden_states.dim(0)?; + let hidden_states = hidden_states + .reshape(( + seq_len / self.spatial_merge_unit, + self.spatial_merge_unit, + (), + ))? + .contiguous()?; + let hidden_states = hidden_states.index_select(&window_index, 0)?; + let mut hidden_states = hidden_states.reshape((seq_len, ()))?; + let rotary_pos_emb = rotary_pos_emb.reshape(( + seq_len / self.spatial_merge_unit, + self.spatial_merge_unit, + (), + ))?; + let rotary_pos_emb = rotary_pos_emb.index_select(&window_index, 0)?; + let rotary_pos_emb = rotary_pos_emb.reshape((seq_len, ()))?; + let emb = Tensor::cat(&[&rotary_pos_emb, &rotary_pos_emb], D::Minus1)?; + let cos = emb.cos()?.to_dtype(hidden_states.dtype())?; + let sin = emb.sin()?.to_dtype(hidden_states.dtype())?; + let cu_seqlens = grid_thw.i((.., 1))?.mul(&grid_thw.i((.., 2))?)?; + let grid_t = grid_thw.i((.., 0))?.to_vec1::()?; + let cu_seqlens_full = match cu_seqlens.rank() { + 1 => cu_seqlens.repeat(grid_t[0] as usize)?, + 2 => { + let mut cu_seqlens_repeat = Vec::new(); + for (index, t) in grid_t.iter().enumerate() { + cu_seqlens_repeat.push(cu_seqlens.i(index)?.repeat(t.clone() as usize)?); + } + Tensor::cat(&cu_seqlens_repeat, 0)?.flatten_all()? + } + _ => { + return Err(anyhow!(format!("create cu_seqlens error"))); + } + }; + let cu_seqlens = cu_seqlens_full + .to_dtype(DType::F64)? + .cumsum(0)? + .to_dtype(DType::U32)?; + let pad_zero = Tensor::from_vec(vec![0_u32], 1, hidden_states.device())?; + let cu_seqlens = Tensor::cat(&[&pad_zero, &cu_seqlens], D::Minus1)?; + let attention_mask_window = self.get_attention_mask( + &cu_window_seqlens, + seq_len, + hidden_states.device(), + hidden_states.dtype(), + )?; + let attention_mask_full = self.get_attention_mask( + &cu_seqlens, + seq_len, + hidden_states.device(), + hidden_states.dtype(), + )?; + let mut attention_mask = attention_mask_window.clone(); + for (layer_num, block) in self.blocks.iter().enumerate() { + if self.fullatt_block_indexes.contains(&layer_num) { + attention_mask = attention_mask_full.clone(); + } else { + attention_mask = attention_mask_window.clone(); + } + hidden_states = block.forward(&hidden_states, &cos, &sin, &attention_mask)?; + } + let hidden_states = self.merger.forward(&hidden_states)?; + let reverse_indices = safe_arg_sort_last_dim(&window_index, true)?; + let hidden_states = hidden_states.index_select(&reverse_indices, 0)?; + Ok(hidden_states) + } +} + +pub fn repeat_kv(xs: Tensor, n_rep: usize) -> Result { + if n_rep == 1 { + Ok(xs) + } else { + let (b_sz, n_kv_head, seq_len, head_dim) = xs.dims4()?; + // Using cat is faster than a broadcast as it avoids going through a potentially + // strided copy. + // https://github.com/huggingface/candle/pull/2043 + let kv = Tensor::cat(&vec![&xs; n_rep], 2)?.reshape(( + b_sz, + n_kv_head * n_rep, + seq_len, + head_dim, + ))?; + Ok(kv) + } +} + +#[derive(Debug, Clone)] +struct Qwen2_5VLTextMLP { + gate_proj: Linear, + up_proj: Linear, + down_proj: Linear, + act_fn: Activation, +} + +impl Qwen2_5VLTextMLP { + fn new(cfg: &Config, vb: VarBuilder) -> Result { + let hidden_sz = cfg.hidden_size; + let intermediate_sz = cfg.intermediate_size; + let gate_proj = linear_no_bias(hidden_sz, intermediate_sz, vb.pp("gate_proj"))?; + let up_proj = linear_no_bias(hidden_sz, intermediate_sz, vb.pp("up_proj"))?; + let down_proj = linear_no_bias(intermediate_sz, hidden_sz, vb.pp("down_proj"))?; + + Ok(Self { + gate_proj, + up_proj, + down_proj, + act_fn: cfg.hidden_act, + }) + } +} + +impl Module for Qwen2_5VLTextMLP { + fn forward(&self, xs: &Tensor) -> candle_core::Result { + let lhs = xs.apply(&self.gate_proj)?.apply(&self.act_fn)?; + let rhs = xs.apply(&self.up_proj)?; + (lhs * rhs)?.apply(&self.down_proj) + } +} + +#[derive(Debug, Clone)] +struct Qwen2_5VLTextAttention { + q_proj: Linear, + k_proj: Linear, + v_proj: Linear, + o_proj: Linear, + num_heads: usize, + num_kv_heads: usize, + num_kv_groups: usize, + head_dim: usize, + hidden_size: usize, + kv_cache: Option<(Tensor, Tensor)>, +} + +impl Qwen2_5VLTextAttention { + fn new(cfg: &Config, vb: VarBuilder) -> Result { + let hidden_size = cfg.hidden_size; + let num_heads = cfg.num_attention_heads; + let num_kv_heads = cfg.num_key_value_heads; + let num_kv_groups = num_heads / num_kv_heads; + let head_dim = hidden_size / num_heads; + let q_proj = linear(hidden_size, num_heads * head_dim, vb.pp("q_proj"))?; + let k_proj = linear(hidden_size, num_kv_heads * head_dim, vb.pp("k_proj"))?; + let v_proj = linear(hidden_size, num_kv_heads * head_dim, vb.pp("v_proj"))?; + let o_proj = linear_no_bias(hidden_size, hidden_size, vb.pp("o_proj"))?; + Ok(Self { + q_proj, + k_proj, + v_proj, + o_proj, + num_heads, + num_kv_heads, + num_kv_groups, + head_dim, + hidden_size, + kv_cache: None, + }) + } + + fn forward( + &mut self, + xs: &Tensor, + cos: &Tensor, + sin: &Tensor, + attention_mask: Option<&Tensor>, + ) -> Result { + let (b_sz, q_len, _) = xs.dims3()?; + let query_states = self.q_proj.forward(xs)?; + let key_states = self.k_proj.forward(xs)?; + let value_states = self.v_proj.forward(xs)?; + let query_states = query_states + .reshape((b_sz, q_len, self.num_heads, self.head_dim))? + .transpose(1, 2)?; + let key_states = key_states + .reshape((b_sz, q_len, self.num_kv_heads, self.head_dim))? + .transpose(1, 2)?; + let value_states = value_states + .reshape((b_sz, q_len, self.num_kv_heads, self.head_dim))? + .transpose(1, 2)?; + let (query_states, key_states) = + apply_rotary_pos_emb(&query_states, &key_states, cos, sin)?; + let (key_states, value_states) = match &self.kv_cache { + None => (key_states, value_states), + Some((prev_k, prev_v)) => { + let key_states = Tensor::cat(&[prev_k, &key_states], 2)?; + let value_states = Tensor::cat(&[prev_v, &value_states], 2)?; + (key_states, value_states) + } + }; + + self.kv_cache = Some((key_states.clone(), value_states.clone())); + + let key_states = repeat_kv(key_states, self.num_kv_groups)?.contiguous()?; + let value_states = repeat_kv(value_states, self.num_kv_groups)?.contiguous()?; + let query_states = query_states.contiguous()?; + let attn_output = { + let scale = 1f64 / f64::sqrt(self.head_dim as f64); + #[cfg(not(feature = "flash-attn"))] + { + let attn_weights = + query_states.matmul(&key_states.transpose(D::Minus2, D::Minus1)?)?; + let attn_weights = (attn_weights * scale)?; + let attn_weights = match attention_mask { + None => attn_weights, + Some(mask) => attn_weights.broadcast_add(mask)?, + }; + let attn_weights = candle_nn::ops::softmax_last_dim(&attn_weights)?; + let attn_weights = attn_weights.matmul(&value_states)?; + attn_weights + } + #[cfg(feature = "flash-attn")] + { + // use flash-attn, + // flash-attn shape: (bs, seq_len, num_head, head_dim) + let query_states = query_states.transpose(1, 2)?; + let key_states = key_states.transpose(1, 2)?; + let value_states = value_states.transpose(1, 2)?; + let attn_output = candle_flash_attn::flash_attn( + &query_states, + &key_states, + &value_states, + scale as f32, + attention_mask.is_some(), + )? + .transpose(1, 2)?; + attn_output + } + }; + let attn_output = + attn_output + .transpose(1, 2)? + .contiguous()? + .reshape((b_sz, q_len, self.hidden_size))?; + let attn_output = attn_output.apply(&self.o_proj)?; + Ok(attn_output) + } + + fn clear_kv_cache(&mut self) { + self.kv_cache = None + } +} + +#[derive(Debug, Clone)] +struct Qwen2_5VLTextDecoderLayer { + self_attn: Qwen2_5VLTextAttention, + mlp: Qwen2_5VLTextMLP, + input_layernorm: RmsNorm, + post_attention_layernorm: RmsNorm, +} + +impl Qwen2_5VLTextDecoderLayer { + fn new(cfg: &Config, vb: VarBuilder) -> Result { + let self_attn = Qwen2_5VLTextAttention::new(cfg, vb.pp("self_attn"))?; + let mlp = Qwen2_5VLTextMLP::new(cfg, vb.pp("mlp"))?; + let input_layernorm = + rms_norm(cfg.hidden_size, cfg.rms_norm_eps, vb.pp("input_layernorm"))?; + let post_attention_layernorm = rms_norm( + cfg.hidden_size, + cfg.rms_norm_eps, + vb.pp("post_attention_layernorm"), + )?; + Ok(Self { + self_attn, + mlp, + input_layernorm, + post_attention_layernorm, + }) + } + + fn forward( + &mut self, + xs: &Tensor, + cos: &Tensor, + sin: &Tensor, + attention_mask: Option<&Tensor>, + ) -> Result { + let residual = xs; + let xs = self.input_layernorm.forward(xs)?; + let xs = self.self_attn.forward(&xs, cos, sin, attention_mask)?; + let xs = (xs + residual)?; + let residual = &xs; + let xs = xs.apply(&self.post_attention_layernorm)?.apply(&self.mlp)?; + let xs = (residual + xs)?; + Ok(xs) + } + + fn clear_kv_cache(&mut self) { + self.self_attn.clear_kv_cache() + } +} + +#[derive(Debug, Clone)] +pub struct Qwen2_5VLTextModel { + pub embed_tokens: candle_nn::Embedding, + layers: Vec, + norm: RmsNorm, + rotary_emb: Qwen2_5VLTextRotaryEmbedding, + dtype: DType, + sliding_window: usize, + device: Device, + rope_scaling: RopeScaling, +} + +impl Qwen2_5VLTextModel { + pub fn new(cfg: &Config, vb: VarBuilder) -> Result { + let embed_tokens = + candle_nn::embedding(cfg.vocab_size, cfg.hidden_size, vb.pp("embed_tokens"))?; + let head_dim = cfg.hidden_size / cfg.num_attention_heads; + let rotary_emb = Qwen2_5VLTextRotaryEmbedding::new(head_dim, cfg.rope_theta); + let mut layers = Vec::with_capacity(cfg.num_hidden_layers); + let vb_l = vb.pp("layers"); + for layer_idx in 0..cfg.num_hidden_layers { + let layer = Qwen2_5VLTextDecoderLayer::new(cfg, vb_l.pp(layer_idx))?; + layers.push(layer) + } + let norm = rms_norm(cfg.hidden_size, cfg.rms_norm_eps, vb.pp("norm"))?; + let sliding_window = cfg.sliding_window; + let rope_scaling = cfg.rope_scaling.clone(); + Ok(Self { + embed_tokens, + layers, + norm, + rotary_emb, + dtype: vb.dtype(), + sliding_window, + device: vb.device().clone(), + rope_scaling, + }) + } + + fn prepare_causal_attention_mask( + &self, + b_size: usize, + tgt_len: usize, + seqlen_offset: usize, + ) -> Result { + // Sliding window mask? + let mask: Vec<_> = (0..tgt_len) + .flat_map(|i| { + (0..tgt_len).map(move |j| { + if i < j || j + self.sliding_window < i { + f32::NEG_INFINITY + } else { + 0. + } + }) + }) + .collect(); + let mask = Tensor::from_slice(&mask, (tgt_len, tgt_len), &self.device)?; + let mask = if seqlen_offset > 0 { + let mask0 = Tensor::zeros((tgt_len, seqlen_offset), self.dtype, &self.device)?; + Tensor::cat(&[&mask0, &mask], D::Minus1)? + } else { + mask + }; + let mask = mask + .expand((b_size, 1, tgt_len, tgt_len + seqlen_offset))? + .to_dtype(self.dtype)?; + Ok(mask) + } + pub fn forward( + &mut self, + inputs_embeds: &Tensor, + seqlen_offset: usize, + position_ids: Option<&Tensor>, + ) -> Result { + let (b_size, seq_len, _) = inputs_embeds.dims3()?; + + let position_ids = match position_ids { + Some(ids) => ids.clone(), + None => Tensor::arange( + seqlen_offset as u32, + (seq_len + seqlen_offset) as u32, + inputs_embeds.device(), + )? + .unsqueeze(0)? + .unsqueeze(0)? + .broadcast_as((3, b_size, seq_len))?, + }; + let (cos, sin) = self.rotary_emb.forward( + &position_ids, + self.dtype, + self.rope_scaling.mrope_section.clone(), + )?; + let mut xs = inputs_embeds.clone(); + let attention_mask: Option<&Tensor> = { + if seq_len <= 1 { + None + } else { + Some(&self.prepare_causal_attention_mask(b_size, seq_len, seqlen_offset)?) + } + }; + for layer in self.layers.iter_mut() { + xs = layer.forward(&xs, &cos, &sin, attention_mask)?; + } + let xs = xs.apply(&self.norm)?; + Ok(xs) + } + + pub fn clear_kv_cache(&mut self) { + for layer in self.layers.iter_mut() { + layer.clear_kv_cache() + } + } +} + +pub struct Qwen2_5VLModel { + visual: Qwen2_5VLVisionModel, + model: Qwen2_5VLTextModel, + pub cfg: Config, + lm_head: Linear, + rope_deltas: Option, +} + +impl Qwen2_5VLModel { + pub fn new(cfg: Config, vb: VarBuilder) -> Result { + let visual = Qwen2_5VLVisionModel::new(&cfg, vb.pp("visual"))?; + let model = Qwen2_5VLTextModel::new(&cfg, vb.pp("model"))?; + let vocab_size = cfg.vocab_size; + let lm_head = if cfg.tie_word_embeddings { + Linear::new(model.embed_tokens.embeddings().clone(), None) + } else { + linear_no_bias(cfg.hidden_size, vocab_size, vb.pp("lm_head"))? + }; + + Ok(Self { + visual, + model, + cfg, + lm_head, + rope_deltas: None, + }) + } + + pub fn get_rope_index( + &self, + input_ids: &Tensor, + image_grid_thw: Option<&Tensor>, + video_grid_thw: Option<&Tensor>, + mask: Option<&Tensor>, + second_per_grid_ts: Option>, + ) -> Result<(Tensor, Tensor)> { + let spatial_merge_size = self.cfg.vision_config.spatial_merge_size; + let mut mrope_position_deltas: Vec = Vec::new(); + if image_grid_thw.is_some() || video_grid_thw.is_some() { + let total_input_ids = input_ids.clone(); + let mut mask_; + if mask.is_none() { + mask_ = Tensor::ones_like(&total_input_ids)?; + } else { + mask_ = mask.unwrap().clone(); + } + let mut position_ids = Tensor::ones( + (3, input_ids.dim(0)?, input_ids.dim(1)?), + input_ids.dtype(), + input_ids.device(), + )?; + let mut image_index = 0; + let mut video_index = 0; + for i in 0..total_input_ids.dim(0)? { + let mut input_ids_i = total_input_ids.i(i)?; + let mask_i = mask_.i(i)?; + // 推理时, attention_mask如果是全1向量,取非0索引的操作没必要 + if mask_i.sum_all()?.to_scalar::()? != mask_i.dim(0)? as u32 { + let nonzero_idx = nonzero_index(&mask_i)?; + input_ids_i = input_ids_i.gather(&nonzero_idx, 0)?; + } + let mut text_start = 0; + let mut text_end = 0; + let mut thw = vec![]; + let mut second_per_grid_t = 0_f32; + let mut llm_pos_ids_list: Vec = Vec::new(); + // vision start的下一个索引 + let vision_indices = + get_vision_next_indices(&input_ids_i, self.cfg.vision_start_token_id as u32); + match vision_indices { + Ok(indeices) => { + let vision_tokens = input_ids_i.gather(&indeices, 0)?.to_vec1::()?; + let vision_indices_vec = indeices.to_vec1::()?; + for (j, &token) in vision_tokens.iter().enumerate() { + if token == self.cfg.image_token_id as u32 { + thw = image_grid_thw.unwrap().i(image_index)?.to_vec1::()?; + image_index += 1; + text_end = vision_indices_vec[j]; + second_per_grid_t = 0.0; + } + if token == self.cfg.video_token_id as u32 { + thw = video_grid_thw.unwrap().i(video_index)?.to_vec1::()?; + text_end = vision_indices_vec[j]; + second_per_grid_t = match second_per_grid_ts { + None => 1.0, + Some(ref vec) => vec[video_index], + }; + video_index += 1; + } + let llm_grid_t = thw[0]; + let llm_grid_h = thw[1] / spatial_merge_size as u32; + let llm_grid_w = thw[2] / spatial_merge_size as u32; + let text_len = text_end - text_start; + let start_idx = if llm_pos_ids_list.len() > 0 { + llm_pos_ids_list[llm_pos_ids_list.len() - 1] + .max_all()? + .to_scalar::()? + + 1 + } else { + 0 + }; + let pos_ids = Tensor::arange( + start_idx, + start_idx + text_len, + input_ids_i.device(), + )? + .unsqueeze(0)? + .broadcast_as((3usize, text_len as usize))?; + llm_pos_ids_list.push(pos_ids); + let range_tensor = Tensor::arange(0, llm_grid_t, input_ids_i.device())? + .unsqueeze(D::Minus1)?; + let expanded_range = range_tensor.broadcast_as(( + llm_grid_t as usize, + (llm_grid_h * llm_grid_w) as usize, + ))?; + let time_tensor = expanded_range + .broadcast_mul(&Tensor::new( + vec![ + (second_per_grid_t + * self.cfg.vision_config.tokens_per_second as f32) + as u32, + ], + input_ids_i.device(), + )?)? + .broadcast_add(&Tensor::new( + vec![start_idx + text_len], + input_ids_i.device(), + )?)?; + let t_index = time_tensor.flatten_all()?; + let h_index = Tensor::arange( + start_idx + text_len, + start_idx + text_len + llm_grid_h, + input_ids_i.device(), + )? + .unsqueeze(0)? + .unsqueeze(D::Minus1)? + .broadcast_as(( + llm_grid_t as usize, + llm_grid_h as usize, + llm_grid_w as usize, + ))? + .flatten_all()?; + let w_index = Tensor::arange( + start_idx + text_len, + start_idx + text_len + llm_grid_w, + input_ids_i.device(), + )? + .unsqueeze(0)? + .unsqueeze(0)? + .broadcast_as(( + llm_grid_t as usize, + llm_grid_h as usize, + llm_grid_w as usize, + ))? + .flatten_all()?; + + let thw_index = Tensor::stack(&[t_index, h_index, w_index], 0)?; + llm_pos_ids_list.push(thw_index); + text_start = text_end + llm_grid_t * llm_grid_h * llm_grid_w; + } + } + Err(e) => { + println!("get vision_indices err: {}", e); + } + }; + + if text_start < input_ids_i.dim(0)? as u32 { + let start_idx = if llm_pos_ids_list.len() > 0 { + llm_pos_ids_list[llm_pos_ids_list.len() - 1] + .max_all()? + .to_scalar::()? + + 1 + } else { + 0 + }; + let text_len = input_ids_i.dim(0)? as u32 - text_start; + let pos_ids = + Tensor::arange(start_idx, start_idx + text_len, input_ids_i.device())? + .unsqueeze(0)? + .broadcast_as((3usize, text_len as usize))?; + llm_pos_ids_list.push(pos_ids); + } + let llm_position = Tensor::cat(&llm_pos_ids_list, 1)?.reshape((3, 1, ()))?; + position_ids = position_ids + .slice_assign(&[(0..3), (i..i + 1), (0..input_ids.dim(1)?)], &llm_position)?; + let position_deltas = llm_position.max_all()?.to_scalar::()? as i64 + 1 + - input_ids_i.dim(0)? as i64; + mrope_position_deltas.push(position_deltas); + } + + let mut mrope_position_deltas = Tensor::new(mrope_position_deltas, input_ids.device())?; + if mrope_position_deltas.rank() == 1 { + mrope_position_deltas = mrope_position_deltas.unsqueeze(0)?; + } + return Ok((position_ids.contiguous()?, mrope_position_deltas)); + } else { + if mask.is_some() { + let mut position_ids = mask + .unwrap() + .to_dtype(candle_core::DType::F64)? + .cumsum(D::Minus1)? + .to_dtype(candle_core::DType::U32)? + .broadcast_sub(&Tensor::new(vec![1_u32], input_ids.device())?)?; + for i in 0..position_ids.dim(0)? { + let mut position_ids_i = position_ids.i(i)?; + let mask_i = mask.unwrap().i(i)?; + // 如果有pad, 将填充位置置为1 + // 当bs>1, 可能存在不同序列长度,需要添加pad使seq_len长度一致 + if mask_i.sum_all()?.to_scalar::()? != mask_i.dim(0)? as u32 { + let zero_indices = zero_index(&mask_i)?; + let replace_1 = Tensor::ones( + zero_indices.dim(0)?, + candle_core::DType::U32, + input_ids.device(), + )?; + position_ids_i = position_ids_i + .scatter(&zero_indices, &replace_1, 0)? + .unsqueeze(0)?; + position_ids = position_ids.slice_assign( + &[(i..i + 1), (0..position_ids.dim(1)?)], + &position_ids_i, + )?; + } + } + position_ids = position_ids + .unsqueeze(0)? + .broadcast_as((3, input_ids.dim(0)?, input_ids.dim(1)?))? + .contiguous()?; + let mut mrope_position_deltas = position_ids + .max(0)? + .max(D::Minus1)? + .broadcast_sub(&Tensor::new( + vec![mask.unwrap().dim(D::Minus1)? as u32 - 1], + input_ids.device(), + )?)? + .contiguous()?; + if mrope_position_deltas.rank() == 1 { + mrope_position_deltas = mrope_position_deltas.unsqueeze(0)?; + } + return Ok((position_ids, mrope_position_deltas)); + } else { + let position_ids = + Tensor::arange(0_u32, input_ids.dim(D::Minus1)? as u32, input_ids.device())? + .unsqueeze(0)? + .unsqueeze(0)? + .broadcast_as((3, input_ids.dim(0)?, input_ids.dim(D::Minus1)?))? + .contiguous()?; + let mrope_position_deltas = Tensor::zeros( + (input_ids.dim(0)?, 1), + input_ids.dtype(), + input_ids.device(), + )?; + Ok((position_ids, mrope_position_deltas)) + } + } + } + + pub fn forward( + &mut self, + input_ids: &Tensor, + pixel_values: Option<&Tensor>, + image_grid_thw: Option<&Tensor>, + pixel_values_video: Option<&Tensor>, + video_grid_thw: Option<&Tensor>, + mask: &Tensor, + cache_position: Option<&Tensor>, + seqlen_offset: usize, + second_per_grid_ts: Option>, + ) -> Result { + // input_ids shape: (bs, seq_len) + let mut inputs_embeds = self.model.embed_tokens.forward(&input_ids)?; + // inputs_embeds shape: (bs, seq_len, hidden_dim) + if pixel_values.is_some() && image_grid_thw.is_some() { + // image_embed shape: (seq_len, hidden_dim) + let image_embed = self + .visual + .forward(pixel_values.unwrap(), image_grid_thw.unwrap())?; + let vision_mask = get_equal_mask(&input_ids, self.cfg.image_token_id as u32)?; + + let n_image_tokens = vision_mask.sum_all()?.to_scalar::()?; + if n_image_tokens as usize != image_embed.dim(0)? { + return Err(anyhow!(format!( + "n_image_token num: {} not equal to image_embed len: {}", + n_image_tokens, + image_embed.dim(0)? + ))); + } + inputs_embeds = masked_scatter_dim0(&inputs_embeds, &image_embed, &vision_mask)?; + } + if pixel_values_video.is_some() && video_grid_thw.is_some() { + let video_embed = self + .visual + .forward(pixel_values_video.unwrap(), video_grid_thw.unwrap())?; + + let vision_mask = get_equal_mask(&input_ids, self.cfg.video_token_id as u32)?; + let n_video_tokens = vision_mask.sum_all()?.to_scalar::()?; + if n_video_tokens as usize != video_embed.dim(0)? { + return Err(anyhow!(format!( + "n_image_token num: {} not equal to image_embed len: {}", + n_video_tokens, + video_embed.dim(0)? + ))); + } + inputs_embeds = masked_scatter_dim0(&inputs_embeds, &video_embed, &vision_mask)?; + } + let mut position_ids; + let mut rope_deltas; + if (cache_position.is_some() && cache_position.unwrap().i(0)?.to_scalar::()? == 0) + || self.rope_deltas.is_none() + { + (position_ids, rope_deltas) = self.get_rope_index( + input_ids, + image_grid_thw, + video_grid_thw, + Some(mask), + second_per_grid_ts, + )?; + self.rope_deltas = Some(rope_deltas); + } else { + let (bs, seq_len, _) = inputs_embeds.dims3()?; + let delta = if cache_position.is_some() { + cache_position + .unwrap() + .i(0)? + .to_dtype(self.rope_deltas.as_ref().unwrap().dtype())? + .broadcast_add(&self.rope_deltas.as_ref().unwrap())? + .contiguous()? + .to_dtype(candle_core::DType::U32)? + } else { + Tensor::zeros(1, inputs_embeds.dtype(), inputs_embeds.device())? + }; + position_ids = Tensor::arange(0u32, seq_len as u32, input_ids.device())? + .unsqueeze(0)? + .broadcast_as((bs, seq_len))? + .broadcast_add(&delta)? + .unsqueeze(0)? + .broadcast_as((3, bs, seq_len))? + .contiguous()?; + } + let outputs = self + .model + .forward(&inputs_embeds, seqlen_offset, Some(&position_ids))?; + let seq_len = outputs.dim(1)?; + let hidden_state = outputs.narrow(1, seq_len - 1, 1)?; + let logits = self.lm_head.forward(&hidden_state)?; + Ok(logits) + } + pub fn clear_kv_cache(&mut self) { + self.model.clear_kv_cache(); + } +} diff --git a/src/models/qwen2_5vl/processor.rs b/src/models/qwen2_5vl/processor.rs new file mode 100644 index 0000000..eb173d1 --- /dev/null +++ b/src/models/qwen2_5vl/processor.rs @@ -0,0 +1,462 @@ +use std::collections::HashMap; + +use anyhow::{Result, anyhow}; +use candle_core::{DType, Device, IndexOp, Shape, Tensor}; +use ffmpeg_next as ffmpeg; +use image::DynamicImage; +use num::integer::lcm; +use openai_dive::v1::resources::chat::{ + ChatCompletionParameters, ChatMessage, ChatMessageContent, ChatMessageContentPart, +}; + +use crate::{ + models::qwen2_5vl::config::VisionSetting, + utils::{ + img_utils::get_image, + utils::{ceil_by_factor, floor_by_factor, round_by_factor}, + }, +}; + +#[derive(Clone)] +pub struct VisionInput { + pub data: Tensor, + pub grid_thw: Tensor, +} + +#[derive(Clone)] +pub struct GeneralInput { + pub replace_text: String, + pub pixel_values: Option, + pub image_grid_thw: Option, + pub pixel_values_video: Option, + pub video_grid_thw: Option, + pub second_per_grid_ts: Option>, +} + +pub struct Qwen2_5VLProcessor { + vision_setting: VisionSetting, + device: Device, + dtype: DType, + image_token: String, + video_token: String, +} + +impl Qwen2_5VLProcessor { + pub fn new(device: &Device, dtype: DType) -> Result { + let vision_setting = VisionSetting::default(); + let image_token = "<|image_pad|>".to_string(); + let video_token = "<|video_pad|>".to_string(); + Ok(Self { + vision_setting, + device: device.clone(), + dtype, + image_token, + video_token, + }) + } + + pub fn extract_vision_info( + &self, + mes: &ChatCompletionParameters, + ) -> Result>> { + let mut vision_map = HashMap::new(); + vision_map.insert("image".to_string(), Vec::new()); + vision_map.insert("video".to_string(), Vec::new()); + for chat_mes in mes.messages.clone() { + match chat_mes { + ChatMessage::User { content, name } => match content { + ChatMessageContent::ContentPart(part_vec) => { + for part in part_vec { + match part { + ChatMessageContentPart::Image(img_part) => { + let img_url = img_part.image_url; + vision_map.get_mut("image").unwrap().push(img_url.url); + } + _ => {} + } + } + } + _ => {} + }, + _ => {} + } + } + Ok(vision_map) + } + + pub fn process_img( + &self, + img: &DynamicImage, + img_mean: &Tensor, + img_std: &Tensor, + ) -> Result { + let img_h = img.height(); + let img_w = img.width(); + // h,w resize成 28的倍数 + let (resize_h, resize_w) = smart_resize(img_h, img_w, &self.vision_setting, true, None)?; + let img = img.resize_exact(resize_w, resize_h, image::imageops::FilterType::CatmullRom); + let img_vec = img.to_rgb8().into_raw(); + // (h, w, c) => (c, h, w) + let img_tensor = Tensor::from_slice( + &img_vec, + (resize_h as usize, resize_w as usize, 3), + &self.device, + )? + .permute((2, 0, 1))? + .to_dtype(self.dtype)?; + // 0-255 rescale to 0-1 + let img_tensor = img_tensor.affine(1.0 / 255.0, 0.)?; + // normalize + let img_tensor = img_tensor + .broadcast_sub(&img_mean)? + .broadcast_div(&img_std)?; + // (c, h, w) => (1, c, h, w) + let img_tensor = img_tensor.unsqueeze(0)?; + Ok(img_tensor) + } + + pub fn process_vision_tensor(&self, img_tensor: &Tensor) -> Result<(Tensor, Tensor)> { + let channel = img_tensor.dim(1)?; + let grid_t = img_tensor.dim(0)? / self.vision_setting.temporal_patch_size; + let grid_h = img_tensor.dim(2)? / self.vision_setting.patch_size; + let grid_w = img_tensor.dim(3)? / self.vision_setting.patch_size; + let shape = Shape::from(vec![ + grid_t, + self.vision_setting.temporal_patch_size, + channel, + grid_h / self.vision_setting.merge_size, + self.vision_setting.merge_size, + self.vision_setting.patch_size, + grid_w / self.vision_setting.merge_size, + self.vision_setting.merge_size, + self.vision_setting.patch_size, + ]); + let img_tensor = img_tensor.reshape(shape)?; + // shape to // grid_t, + // grid_h / merge_size, + // grid_w / merge_size, + // merge_size, + // merge_size, + // channel, + // temporal_patch_size, + // patch_size, + // patch_size, + let img_tensor = img_tensor.permute(vec![0, 3, 6, 4, 7, 2, 1, 5, 8])?; + let img_tensor = img_tensor + .reshape(( + grid_t * grid_h * grid_w, + channel + * self.vision_setting.temporal_patch_size + * self.vision_setting.patch_size + * self.vision_setting.patch_size, + ))? + .contiguous()?; + let grid_thw = Tensor::from_vec( + vec![grid_t as u32, grid_h as u32, grid_w as u32], + (1, 3), + &self.device, + )?; + Ok((img_tensor, grid_thw)) + } + + pub fn process_images( + &self, + imgs: Vec, + img_mean: &Tensor, + img_std: &Tensor, + ) -> Result { + let mut pixel_values_vec = Vec::new(); + let mut vision_grid_thws_vec = Vec::new(); + + for img in imgs { + let img_tensor = self.process_img(&img, &img_mean, &img_std)?; + let img_tensor = Tensor::cat(&[&img_tensor, &img_tensor], 0)?.contiguous()?; + let (img_tensor, grid_thw) = self.process_vision_tensor(&img_tensor)?; + pixel_values_vec.push(img_tensor); + vision_grid_thws_vec.push(grid_thw); + } + let pixel_values = Tensor::cat(&pixel_values_vec, 0)?; + let vision_grid_thws = Tensor::cat(&vision_grid_thws_vec, 0)?; + Ok(VisionInput { + data: pixel_values, + grid_thw: vision_grid_thws, + }) + } + + pub fn process_videos( + &self, + data: Vec, + img_mean: &Tensor, + img_std: &Tensor, + ) -> Result { + let mut pixel_values_vec = Vec::new(); + let mut vision_grid_thws_vec = Vec::new(); + for single_video in data { + // 0-255 rescale to 0-1 + let video_tensor = single_video.to_dtype(self.dtype)?.affine(1.0 / 255.0, 0.)?; + // normalize + let video_tensor = video_tensor + .broadcast_sub(&img_mean)? + .broadcast_div(&img_std)? + .contiguous()?; + let (video_tensor, video_grid_thw) = self.process_vision_tensor(&video_tensor)?; + pixel_values_vec.push(video_tensor); + vision_grid_thws_vec.push(video_grid_thw); + } + let pixel_values = Tensor::cat(&pixel_values_vec, 0)?.contiguous()?; + let vision_grid_thws = Tensor::cat(&vision_grid_thws_vec, 0)?.contiguous()?; + Ok(VisionInput { + data: pixel_values, + grid_thw: vision_grid_thws, + }) + } + + pub fn process_info( + &self, + messages: &ChatCompletionParameters, + text: &str, + ) -> Result { + let mut pixel_values = None; + let mut image_grid_thw = None; + let mut pixel_values_video = None; + let mut video_grid_thw = None; + let mut second_per_grid_ts = None; + let vision_map = self.extract_vision_info(messages)?; + let img_mean = + Tensor::from_slice(&self.vision_setting.image_mean, (3, 1, 1), &self.device)? + .to_dtype(self.dtype)?; + let img_std = Tensor::from_slice(&self.vision_setting.image_std, (3, 1, 1), &self.device)? + .to_dtype(self.dtype)?; + for (key, vec) in vision_map { + // println!("key: {}, \nvalue: {:?}", key, vec); + if key.eq("image") { + let mut file_vec = Vec::new(); + for file in &vec { + let image = get_image(file); + match image { + Ok(img) => file_vec.push(img), + Err(e) => println!("get_image err: {:?}", e), + }; + } + if file_vec.len() > 0 { + let vision_input = self.process_images(file_vec, &img_mean, &img_std); + match vision_input { + Ok(img_input) => { + pixel_values = Some(img_input.data); + image_grid_thw = Some(img_input.grid_thw); + } + Err(e) => println!("img process_images err: {:?}", e), + }; + } + } + if key.eq("video") { + let mut file_vec = Vec::new(); + for file in &vec { + let video_data = get_video_data(file, &self.vision_setting, &self.device); + match video_data { + Ok(tensor) => file_vec.push(tensor), + Err(e) => println!("get_video_data err: {:?}", e), + }; + } + if file_vec.len() > 0 { + let vision_input = self.process_videos(file_vec, &img_mean, &img_std); + match vision_input { + Ok(video_input) => { + let video_num = video_input.grid_thw.dim(0)?; + pixel_values_video = Some(video_input.data); + video_grid_thw = Some(video_input.grid_thw); + let second_per_grid = vec![ + self.vision_setting.temporal_patch_size + as f32 + / self.vision_setting.fps; + video_num + ]; + second_per_grid_ts = Some(second_per_grid); + } + Err(e) => println!("video process_videos err: {:?}", e), + }; + } + } + } + let merge_length = self.vision_setting.merge_size.pow(2); + let mut text = text.to_string(); + if image_grid_thw.is_some() { + let mut index = 0; + while text.contains(&self.image_token) { + let grid_i = image_grid_thw.as_ref().unwrap().i(index)?; + let repeat_num = + grid_i.to_vec1::()?.iter().product::() as usize / merge_length; + let replace = "<|placeholder|>".repeat(repeat_num); + text = text.replacen(&self.image_token, &replace, 1); + index += 1; + } + text = text.replace("<|placeholder|>", &self.image_token); + } + if video_grid_thw.is_some() { + let mut index = 0; + while text.contains(&self.video_token) { + let grid_i = video_grid_thw.as_ref().unwrap().i(index)?; + let repeat_num = + grid_i.to_vec1::()?.iter().product::() as usize / merge_length; + let replace = "<|placeholder|>".repeat(repeat_num); + text = text.replacen(&self.video_token, &replace, 1); + index += 1; + } + text = text.replace("<|placeholder|>", &self.video_token); + } + let input = GeneralInput { + replace_text: text, + pixel_values, + image_grid_thw, + pixel_values_video, + video_grid_thw, + second_per_grid_ts, + }; + Ok(input) + } +} + +pub fn smart_resize( + img_h: u32, + img_w: u32, + vision_setting: &VisionSetting, + is_img: bool, + video_ratio: Option, +) -> Result<(u32, u32)> { + if std::cmp::max(img_h, img_w) / std::cmp::min(img_h, img_w) > vision_setting.max_ratio { + return Err(anyhow!(format!( + "absolute aspect ratio mush be smaller than {}, got {}", + vision_setting.max_ratio, + std::cmp::max(img_h, img_w) / std::cmp::min(img_h, img_w) + ))); + } + let mut image_factor = vision_setting.image_factor; + if let Some(ratio) = video_ratio { + image_factor = lcm(image_factor, ratio); + } + let mut h_bar = std::cmp::max(image_factor, round_by_factor(img_h, image_factor)); + let mut w_bar = std::cmp::max(image_factor, round_by_factor(img_w, image_factor)); + let mut max_pixels = 0u32; + let mut min_pixels = 0u32; + if is_img { + min_pixels = vision_setting.min_pixels; + max_pixels = vision_setting.max_pixels; + } else { + min_pixels = vision_setting.video_min_pixels; + max_pixels = vision_setting.video_max_pixels; + } + if h_bar * w_bar > max_pixels { + let beta = ((img_h * img_w) as f32 / max_pixels as f32).sqrt(); + h_bar = floor_by_factor(img_h as f32 / beta, image_factor); + w_bar = floor_by_factor(img_w as f32 / beta, image_factor); + } else if h_bar * w_bar < min_pixels { + let beta = (min_pixels as f32 / (img_h * img_w) as f32).sqrt(); + h_bar = ceil_by_factor(img_h as f32 * beta, image_factor); + w_bar = ceil_by_factor(img_w as f32 * beta, image_factor); + } + Ok((h_bar, w_bar)) +} + +pub fn get_video_data( + file: &String, + vision_setting: &VisionSetting, + device: &Device, +) -> Result { + ffmpeg::init().map_err(|e| anyhow!(format!("Failed to initialize ffmpeg: {}", e)))?; + + let mut ictx = ffmpeg::format::input(&file) + .map_err(|e| anyhow!(format!("Failed to open video file: {}", e)))?; + let input = ictx + .streams() + .best(ffmpeg::media::Type::Video) + .ok_or_else(|| anyhow!(format!("No video stream found")))?; + let video_stream_index = input.index(); + let context_decoder = ffmpeg::codec::context::Context::from_parameters(input.parameters()) + .map_err(|e| anyhow!(format!("Failed to crate decoder context: {}", e)))?; + let mut decoder = context_decoder + .decoder() + .video() + .map_err(|e| anyhow!(format!("Failed to decoder video: {}", e)))?; + + let video_h = decoder.height(); + let video_w = decoder.width(); + let format = decoder.format(); + + let frames = input.frames(); + let rate = (input.rate().0 as f32 / input.rate().1 as f32).round() as u32; + // 1s取两帧 + let min_frames = ceil_by_factor( + vision_setting.fps_min_frames as f32, + vision_setting.frame_factor, + ); + let max_frames = floor_by_factor( + vision_setting.fps_max_frames as f32, + vision_setting.frame_factor, + ); + let nframes = (frames as f32 / rate as f32 * vision_setting.fps) as u32; + let nframes = std::cmp::min(std::cmp::max(nframes, min_frames), max_frames); + let nframes = round_by_factor(nframes, vision_setting.frame_factor); + let sample_interval = (frames as f32 / nframes as f32).round() as u32; + let mut frame_id = 0_u32; + + // 图片帧使用scaler reshape的时候需要保证宽高是16的倍数,不然reshape出来的是损坏的图片 + // 所以计算resize的目标宽高时,需要用16和image_factor的最小公倍数 + let (resize_h, resize_w) = smart_resize(video_h, video_w, vision_setting, false, Some(16))?; + let mut scaler = ffmpeg::software::scaling::context::Context::get( + format, + video_w, + video_h, + ffmpeg::format::Pixel::RGB24, + resize_w, + resize_h, + ffmpeg::software::scaling::flag::Flags::BILINEAR + | ffmpeg::software::scaling::flag::Flags::ACCURATE_RND, + ) + .map_err(|e| anyhow!(format!("Failed to crate scaler: {}", e)))?; + + let mut frames_vec = Vec::new(); + let mut receive_and_process_decoded_frames = + |decoder: &mut ffmpeg::decoder::Video| -> Result<()> { + let mut decoded = ffmpeg::frame::Video::empty(); + while decoder.receive_frame(&mut decoded).is_ok() { + if frame_id % sample_interval == 0 { + let mut rgb_frame = ffmpeg::frame::Video::empty(); + scaler + .run(&decoded, &mut rgb_frame) + .map_err(|e| anyhow!(format!("Failed to scaler run decoded: {}", e)))?; + + // save_file(&rgb_frame, frame_id as usize); + let frame_data = rgb_frame.data(0); + let frame_tensor = Tensor::from_slice( + frame_data, + (resize_h as usize, resize_w as usize, 3), + device, + )? + .permute((2, 0, 1))?; + frames_vec.push(frame_tensor); + } + frame_id += 1; + } + Ok(()) + }; + + for (stream, packet) in ictx.packets() { + if stream.index() == video_stream_index { + decoder + .send_packet(&packet) + .map_err(|e| anyhow!(format!("Failed to send packet: {}", e)))?; + receive_and_process_decoded_frames(&mut decoder)?; + } + } + decoder + .send_eof() + .map_err(|e| anyhow!(format!("Failed to decoder.send_eof(): {}", e)))?; + receive_and_process_decoded_frames(&mut decoder)?; + + if frames_vec.is_empty() { + return Err(anyhow!("No frames extracted from video".to_string())); + } + // (t, c, h, w) + let frames_tensor = Tensor::stack(&frames_vec, 0)?.contiguous()?; + Ok(frames_tensor) +} diff --git a/src/position_embed/mod.rs b/src/position_embed/mod.rs new file mode 100644 index 0000000..f67f4f0 --- /dev/null +++ b/src/position_embed/mod.rs @@ -0,0 +1 @@ +pub mod rope; diff --git a/src/position_embed/rope.rs b/src/position_embed/rope.rs new file mode 100644 index 0000000..165bcf0 --- /dev/null +++ b/src/position_embed/rope.rs @@ -0,0 +1,182 @@ +use anyhow::Result; +use candle_core::{D, DType, Device, IndexOp, Tensor}; +use candle_transformers::models::deepseek2::SplitOp; + +use crate::models::qwen2_5vl::config::RopeScaling; + +pub fn compute_default_rope_parameters(dim: usize, base: f32) -> Vec { + let inv_freq: Vec = (0..dim) + .step_by(2) + .map(|i| 1.0_f32 / base.powf(i as f32 / dim as f32)) + .collect(); + inv_freq +} + +pub fn rotate_half(x: &Tensor) -> Result { + let half_dim = x.dim(D::Minus1)? / 2; + let x1 = x.narrow(D::Minus1, 0, half_dim)?; + let x2 = x.narrow(D::Minus1, half_dim, half_dim)?; + let x2 = x2.affine(-1.0, 0.0)?; + let rotate_x = Tensor::cat(&[&x2, &x1], D::Minus1)?.contiguous()?; + Ok(rotate_x) +} + +pub fn apply_multimodel_rotary_pos_emb( + q: &Tensor, + k: &Tensor, + cos: &Tensor, + sin: &Tensor, + mrope_section: Vec, +) -> Result<(Tensor, Tensor)> { + let mrope_section = mrope_section.repeat(2); + let cos_select: Vec = cos + .split(&mrope_section, D::Minus1)? + .iter() + .enumerate() + .map(|(i, m)| m.i(i % 3).unwrap()) + .collect(); + let cos = Tensor::cat(&cos_select, D::Minus1)? + .unsqueeze(1)? + .contiguous()?; + let sin_select: Vec = sin + .split(&mrope_section, D::Minus1)? + .iter() + .enumerate() + .map(|(i, m)| m.i(i % 3).unwrap()) + .collect(); + let sin = Tensor::cat(&sin_select, D::Minus1)? + .unsqueeze(1)? + .contiguous()?; + let q_embed = q + .broadcast_mul(&cos)? + .add(&rotate_half(&q)?.broadcast_mul(&sin)?)?; + let k_embed = k + .broadcast_mul(&cos)? + .add(&rotate_half(&k)?.broadcast_mul(&sin)?)?; + Ok((q_embed, k_embed)) +} + +pub fn apply_rotary_pos_emb_vision( + q: &Tensor, + k: &Tensor, + cos: &Tensor, + sin: &Tensor, +) -> Result<(Tensor, Tensor)> { + // q, k -> (seq_len, num_heads, head_dim) + // cos, sin -> (seq_len, head_dim) -> (seq_len, 1, head_dim) + let cos = cos.unsqueeze(D::Minus2)?; + let sin = sin.unsqueeze(D::Minus2)?; + let q_embed = q + .broadcast_mul(&cos)? + .add(&rotate_half(q)?.broadcast_mul(&sin)?)?; + let k_embed = k + .broadcast_mul(&cos)? + .add(&rotate_half(k)?.broadcast_mul(&sin)?)?; + Ok((q_embed, k_embed)) +} + +pub fn apply_rotary_pos_emb( + q: &Tensor, + k: &Tensor, + cos: &Tensor, + sin: &Tensor, +) -> Result<(Tensor, Tensor)> { + // sin/cos: (bs, 1, seq_len, head_dim) + // q/k: (bs, n_head, seq_len, head_dim) + let q_embed = q + .broadcast_mul(&cos)? + .add(&rotate_half(q)?.broadcast_mul(&sin)?)?; + let k_embed = k + .broadcast_mul(&cos)? + .add(&rotate_half(k)?.broadcast_mul(&sin)?)?; + Ok((q_embed, k_embed)) +} + +#[derive(Debug, Clone)] +pub struct Qwen2_5VLTextRotaryEmbedding { + inv_freq: Vec, +} + +impl Qwen2_5VLTextRotaryEmbedding { + pub fn new(dim: usize, theta_base: f32) -> Self { + let inv_freq = compute_default_rope_parameters(dim, theta_base); + Self { inv_freq } + } + pub fn forward( + &self, + position_ids: &Tensor, + dtype: DType, + mrope_section: Vec, + ) -> Result<(Tensor, Tensor)> { + // position_ids shape: (3, bs, position) -> (3, bs, 1, position) + let position_ids_expanded = position_ids + .unsqueeze(D::Minus2)? + .to_dtype(DType::F32)? + .contiguous()?; + // inv_freq Vec -> Tensor(1, 1, head_dim / 2, 1) -> (3, bs, head_dim / 2, 1) + let inv_freq_expanded = Tensor::from_vec( + self.inv_freq.clone(), + (1, 1, self.inv_freq.len(), 1), + position_ids.device(), + )? + .broadcast_as((3, position_ids.dim(1)?, self.inv_freq.len(), 1))? + .to_dtype(DType::F32)? + .contiguous()?; + + // (3, bs, head_dim / 2, 1) matmul (3, bs, 1, position) + // -> (3, bs, head_dim / 2, seq_len) -> (3, bs, seq_len, head_dim / 2) + let freqs = inv_freq_expanded + .matmul(&position_ids_expanded)? + .transpose(2, 3)?; + // let freqs = position_ids_expanded.matmul(&inv_freq_expanded)?; + // (3, bs, seq_len, head_dim / 2) -> (3, bs, seq_len, head_dim) + let emb = Tensor::cat(&[&freqs, &freqs], D::Minus1)?.contiguous()?; + let cos = emb.cos()?; + let sin = emb.sin()?; + let mrope_section = mrope_section.repeat(2); + let cos_select: Vec = cos + .split(&mrope_section, D::Minus1)? + .iter() + .enumerate() + .map(|(i, m)| m.i(i % 3).unwrap()) + .collect(); + // (bs, seq_len, head_dim) -> (bs, 1, seq_len, head_dim) + let cos = Tensor::cat(&cos_select, D::Minus1)? + .unsqueeze(1)? + .contiguous()?; + let sin_select: Vec = sin + .split(&mrope_section, D::Minus1)? + .iter() + .enumerate() + .map(|(i, m)| m.i(i % 3).unwrap()) + .collect(); + // (bs, seq_len, head_dim) -> (bs, 1, seq_len, head_dim) + let sin = Tensor::cat(&sin_select, D::Minus1)? + .unsqueeze(1)? + .contiguous()?; + Ok((cos.to_dtype(dtype)?, sin.to_dtype(dtype)?)) + } +} + +#[derive(Debug, Clone)] +pub struct Qwen2_5VisionRotaryEmbedding { + inv_freq: Vec, +} + +impl Qwen2_5VisionRotaryEmbedding { + pub fn new(dim: usize, theta_base: Option) -> Self { + let theta_base = match theta_base { + Some(theta) => theta, + None => 10000.0_f32, + }; + let inv_freq = compute_default_rope_parameters(dim, theta_base); + Self { inv_freq } + } + + pub fn forward(&self, seqlen: usize, device: &Device) -> Result { + let seq = Tensor::arange(0.0_f32, seqlen as f32, device)?.reshape((seqlen, 1))?; + let inv_freq = Tensor::from_vec(self.inv_freq.clone(), (1, self.inv_freq.len()), device)?; + let freqs = seq.matmul(&inv_freq)?; + Ok(freqs) + } +} diff --git a/src/tokenizer/mod.rs b/src/tokenizer/mod.rs new file mode 100644 index 0000000..5768976 --- /dev/null +++ b/src/tokenizer/mod.rs @@ -0,0 +1 @@ +pub mod tokenizer; \ No newline at end of file diff --git a/src/tokenizer/tokenizer.rs b/src/tokenizer/tokenizer.rs new file mode 100644 index 0000000..8040a76 --- /dev/null +++ b/src/tokenizer/tokenizer.rs @@ -0,0 +1,44 @@ +use anyhow::{Result, anyhow}; +use candle_core::{Device, Tensor}; +use tokenizers::Tokenizer; + +pub struct TokenizerModel { + tokenizer: Tokenizer, +} + +impl TokenizerModel { + pub fn init(path: &str) -> Result { + let path = path.to_string(); + assert!( + std::path::Path::new(&path).exists(), + "model path file not exists" + ); + let tokenizer_file = path.clone() + "/tokenizer.json"; + assert!( + std::path::Path::new(&tokenizer_file).exists(), + "tokenizer.json not exists in model path" + ); + let tokenizer = Tokenizer::from_file(tokenizer_file) + .map_err(|e| anyhow!(format!("tokenizer from file error{}", e)))?; + Ok(Self { tokenizer }) + } + + pub fn text_encode(&self, text: String, device: &Device) -> Result { + let token_id = self + .tokenizer + .encode(text, true) + .map_err(|e| anyhow!(format!("tokenizer encode error: {}", e)))? + .get_ids() + .to_vec(); + let token_tensor = Tensor::from_slice(&token_id, (1, token_id.len()), device)?; + Ok(token_tensor) + } + + pub fn token_decode(&self, tokens: Vec) -> Result { + let decode = self + .tokenizer + .decode(&tokens, true) + .map_err(|e| anyhow!(format!("tokenizer encode error{}", e)))?; + Ok(decode) + } +} diff --git a/src/utils/audio_utils.rs b/src/utils/audio_utils.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/img_utils.rs b/src/utils/img_utils.rs new file mode 100644 index 0000000..84b438b --- /dev/null +++ b/src/utils/img_utils.rs @@ -0,0 +1,62 @@ +use std::io::Cursor; + +use anyhow::{Result, anyhow}; +use base64::{Engine, engine::general_purpose}; +use image::{DynamicImage, ImageReader}; + +pub fn load_image_from_url(url: &str) -> Result { + let response = reqwest::blocking::get(url) + .map_err(|e| anyhow!(format!("Failed to fetch image from url: {}", e)))?; + let bytes = response + .bytes() + .map_err(|e| anyhow!(format!("Failed to get image bytes: {}", e)))?; + + let cursor = Cursor::new(bytes); + let img = ImageReader::new(cursor) + .with_guessed_format() + .map_err(|e| anyhow!(format!("Failed to read image format: {}", e)))? + .decode() + .map_err(|e| anyhow!(format!("Failed to decode image: {}", e)))?; + Ok(img) +} + +pub fn load_image_from_base64(base64_data: &str) -> Result { + let image_data = general_purpose::STANDARD + .decode(base64_data) + .map_err(|e| anyhow!(format!("Failed to decode image: {}", e)))?; + let cursor = Cursor::new(image_data); + let img = ImageReader::new(cursor) + .with_guessed_format() + .map_err(|e| anyhow!(format!("Failed to read image format: {}", e)))? + .decode() + .map_err(|e| anyhow!(format!("Failed to decode image: {}", e)))?; + Ok(img) +} + +pub fn get_image(file: &String) -> Result { + let mut img = None; + if file.starts_with("http://") || file.starts_with("https://") { + img = Some(load_image_from_url(&file)?); + } + if file.starts_with("file://") { + let mut path = file.clone(); + path = path.split_off(7); + img = Some( + ImageReader::open(path) + .map_err(|e| anyhow!(format!("Failed to open file: {}", e)))? + .decode() + .map_err(|e| anyhow!(format!("Failed to decode image: {}", e)))?, + ); + } + if file.starts_with("data:image") { + if file.contains("base64,") { + let data: Vec<&str> = file.split("base64,").collect(); + let data = data[1]; + img = Some(load_image_from_base64(data)?); + } + } + if img.is_some() { + return Ok(img.unwrap()); + } + Err(anyhow!("get image from message failed".to_string())) +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..d9f4429 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,4 @@ +pub mod tensor_utils; +pub mod utils; +pub mod img_utils; +pub mod video_utils; \ No newline at end of file diff --git a/src/utils/tensor_utils.rs b/src/utils/tensor_utils.rs new file mode 100644 index 0000000..9426025 --- /dev/null +++ b/src/utils/tensor_utils.rs @@ -0,0 +1,202 @@ +use anyhow::{Result, anyhow}; +use candle_core::{D, DType, Device, IndexOp, Tensor, shape::Dim}; + +pub fn split(t: &Tensor, splits: &[usize], dim: D) -> Result> { + let dim = dim.to_index(t.shape(), "split")?; + let mut split_res = Vec::new(); + let mut index = 0; + for split in splits { + split_res.push(t.narrow(dim, index, *split)?); + index += *split; + } + Ok(split_res) +} + +pub fn safe_arg_sort_last_dim(t: &Tensor, ascending: bool) -> Result { + // tensor在GPU上时,维度超过1024, arg_sort_last_dim方法会报错 + // 所以维度大于1024时,放到CPU上处理 + let last_dim = t.dims()[t.rank() - 1]; + if last_dim <= 1024 { + let t = t.arg_sort_last_dim(ascending)?; + Ok(t) + } else { + let cpu_tensor = t.to_device(&Device::Cpu)?; + let sorted_indices = cpu_tensor.arg_sort_last_dim(ascending)?; + let t = sorted_indices.to_device(t.device())?; + Ok(t) + } +} + +pub fn nonzero_index_vec(mask: &Tensor) -> Result> { + // 根据mask矩阵选出其中不为0的元素所在索引, 返回vec + // 只能处理1维数据 + let mut mask = mask.clone(); + if mask.dtype() != DType::U32 { + mask = mask.to_dtype(DType::U32)?; + } + match mask.rank() { + 0 => { + return Err(anyhow!(format!( + "input rank must > 0, the input tensor rank: {}", + mask.rank() + ))); + } + 1 => { + let mask_vector = mask.to_vec1::()?; + let indices: Vec = mask_vector + .iter() + .enumerate() + .filter_map(|(idx, &val)| if val != 0 { Some(idx as u32) } else { None }) + .collect(); + Ok(indices) + } + _ => { + return Err(anyhow!(format!( + "input rank not support, the input tensor rank: {}", + mask.rank() + ))); + } + } +} + +pub fn nonzero_index(mask: &Tensor) -> Result { + // 根据mask矩阵选出其中不为1的元素所在索引, 返回Tensor + let indices_tensor = match mask.rank() { + 0 => { + return Err(anyhow!(format!( + "input rank must > 0, the input tensor rank: {}", + mask.rank() + ))); + } + 1 => { + let index_vec = nonzero_index_vec(mask)?; + let indices_tensor = Tensor::from_slice(&index_vec, index_vec.len(), mask.device())?; + indices_tensor + } + _ => { + return Err(anyhow!(format!( + "input rank must == 1, the input tensor rank: {}", + mask.rank() + ))); + } + }; + Ok(indices_tensor) +} + +pub fn zero_index_vec(mask: &Tensor) -> Result> { + // 根据mask矩阵选出其中为0的元素所在索引, 返回vec + // 只能处理1维数据 + let mut mask = mask.clone(); + if mask.dtype() != DType::U32 { + mask = mask.to_dtype(DType::U32)?; + } + match mask.rank() { + 0 => { + return Err(anyhow!(format!( + "input rank must > 0, the input tensor rank: {}", + mask.rank() + ))); + } + 1 => { + let mask_vector = mask.to_vec1::()?; + let indices: Vec = mask_vector + .iter() + .enumerate() + .filter_map(|(idx, &val)| if val == 0 { Some(idx as u32) } else { None }) + .collect(); + Ok(indices) + } + _ => { + return Err(anyhow!(format!( + "input rank not support, the input tensor rank: {}", + mask.rank() + ))); + } + } +} + +pub fn zero_index(mask: &Tensor) -> Result { + let index_vec = zero_index_vec(mask)?; + let indices_tensor = Tensor::from_slice(&index_vec, index_vec.len(), mask.device())?; + Ok(indices_tensor) +} + +pub fn nonzero_slice(mask: &Tensor) -> Result> { + // 根据mask矩阵选出其中非0的元素所在索引 + // 根据索引获取连续索引间隔 + // 如不为零索引元素为[0, 3, 4, 5, 8, 9] + // 间隔为: [(0, 1), (3, 6), (8, 10)] + // 索引前闭后开 + let mut index_vec = nonzero_index_vec(mask)?; + match index_vec.len() { + 0 => { + return Ok(vec![]); + } + 1 => { + return Ok(vec![(index_vec[0] as usize, (index_vec[0] + 1) as usize)]); + } + _ => { + let mut vec_slice = vec![]; + let mut start = index_vec.remove(0); + let mut last = start; + + for i in index_vec { + if i == (last + 1) { + last = i; + continue; + } else { + vec_slice.push((start as usize, (last + 1) as usize)); + start = i; + last = i; + } + } + vec_slice.push((start as usize, (last + 1) as usize)); + Ok(vec_slice) + } + } +} + +pub fn masked_scatter_dim0(original: &Tensor, replace: &Tensor, mask: &Tensor) -> Result { + // 根据mask中非0元素所在索引,使用replace中的数据替换掉original中的数据 + // original: rank = 3: (bs, seq_len, hidden_dim) + // replace: rank = 2: (seq_len, hidden_dim) + // mask: rank = 2: (bs, seq_len) + // 推理时bs=1,为了方便替换,将bs squeeze,替换后再unsqueeze + // 按行替换 + if original.dim(0)? != 1 || mask.dim(0)? != 1 { + return Err(anyhow!(format!( + "masked_scatter_dim0 original bs: {} or mask bs :{} not equal to 1 ", + original.dim(0)?, + mask.dim(0)? != 1 + ))); + } + let mut original = original.squeeze(0)?; + let mask = mask.squeeze(0)?; + let slices = nonzero_slice(&mask)?; + let mut sub_start = 0usize; + let mut sub_end = 0usize; + for (start, end) in slices { + sub_end = sub_start + (end - start); + let sub_replace = replace.i((sub_start..sub_end, ..))?; + original = original.slice_assign(&[(start..end), (0..original.dim(1)?)], &sub_replace)?; + sub_start = sub_end; + } + original = original.unsqueeze(0)?; + Ok(original) +} + +pub fn get_equal_mask(input_ids: &Tensor, token_ids: u32) -> Result { + let image_token_id_tensor = Tensor::new(vec![token_ids], input_ids.device())?; + let mask = input_ids + .broadcast_eq(&image_token_id_tensor)? + .to_dtype(candle_core::DType::U32)?; + Ok(mask) +} + +pub fn get_vision_next_indices(input_ids: &Tensor, token_id: u32) -> Result { + // input_ids -> shape: (seq_len) + let mask = get_equal_mask(&input_ids, token_id)?; + let indices = nonzero_index(&mask)?; + let indices = indices.broadcast_add(&Tensor::new(vec![1u32], input_ids.device())?)?; + Ok(indices) +} diff --git a/src/utils/utils.rs b/src/utils/utils.rs new file mode 100644 index 0000000..fd26e1d --- /dev/null +++ b/src/utils/utils.rs @@ -0,0 +1,87 @@ +use anyhow::Result; +use candle_core::{DType, Device}; + +pub fn get_device(device: Option<&Device>) -> Device { + match device { + Some(d) => d.clone(), + None => { + #[cfg(feature = "cuda")] + { + Device::new_cuda(0).unwrap_or(Device::Cpu) + } + #[cfg(not(feature = "cuda"))] + { + Device::Cpu + } + } + } +} + +pub fn get_dtype(dtype: Option, cfg_dtype: &str) -> DType { + match dtype { + Some(d) => d, + None => { + #[cfg(feature = "cuda")] + { + match cfg_dtype { + "float32" | "float" => DType::F32, + "float64" | "double" => DType::F64, + "float16" => DType::F16, + "bfloat16" => DType::BF16, + "uint8" => DType::U8, + "int8" | "int16" | "int32" | "int64" => DType::I64, + _ => DType::F32, + } + } + #[cfg(not(feature = "cuda"))] + { + match cfg_dtype { + "float32" | "float" => DType::F32, + "float64" | "double" => DType::F64, + "float16" | "bfloat16" => DType::F16, // cpu上bfloat16有问题 + "uint8" => DType::U8, + "int8" | "int16" | "int32" | "int64" => DType::I64, + _ => DType::F32, + } + } + } + } +} + +pub fn string_to_static_str(s: String) -> &'static str { + Box::leak(s.into_boxed_str()) +} + +pub fn find_safetensors_files(path: &str) -> Result> { + let mut files = Vec::new(); + + for entry in std::fs::read_dir(path)? { + let entry = entry?; + let file_path = entry.path(); + + if file_path.is_file() { + if let Some(extension) = file_path.extension() { + if extension == "safetensors" { + files.push(file_path.to_string_lossy().to_string()); + } + } + } + } + + Ok(files) +} + +pub fn round_by_factor(num: u32, factor: u32) -> u32 { + let round = (num as f32 / factor as f32).round() as u32; + round * factor +} + +pub fn floor_by_factor(num: f32, factor: u32) -> u32 { + let floor = (num / factor as f32).floor() as u32; + floor * factor +} + +pub fn ceil_by_factor(num: f32, factor: u32) -> u32 { + let ceil = (num / factor as f32).ceil() as u32; + ceil * factor +} diff --git a/src/utils/video_utils.rs b/src/utils/video_utils.rs new file mode 100644 index 0000000..417928c --- /dev/null +++ b/src/utils/video_utils.rs @@ -0,0 +1,13 @@ +use ffmpeg_next as ffmpeg; +use std::{fs::File, io::Write}; + +#[allow(unused)] +fn save_file( + frame: &ffmpeg::frame::Video, + index: usize, +) -> std::result::Result<(), std::io::Error> { + let mut file = File::create(format!("frame{}.ppm", index))?; + file.write_all(format!("P6\n{} {}\n255\n", frame.width(), frame.height()).as_bytes())?; + file.write_all(frame.data(0))?; + Ok(()) +} diff --git a/tests/config_tests.rs b/tests/config_tests.rs new file mode 100644 index 0000000..8e41bf1 --- /dev/null +++ b/tests/config_tests.rs @@ -0,0 +1,12 @@ +use anyhow::Result; +use aha::models::qwen2_5vl::config::Config; + +#[test] +fn qwen2_5vl_config() -> Result<()> { + // cargo test qwen2_5vl_config -- --nocapture + let model_path = "/home/jhq/huggingface_model/Qwen/Qwen2.5-VL-3B-Instruct/"; + let config_path = model_path.to_string() + "/config.json"; + let config: Config = serde_json::from_slice(&std::fs::read(config_path)?)?; + println!("{:?}", config); + Ok(()) +} \ No newline at end of file diff --git a/tests/test_qwen2_5vl.rs b/tests/test_qwen2_5vl.rs new file mode 100644 index 0000000..3c5bcd5 --- /dev/null +++ b/tests/test_qwen2_5vl.rs @@ -0,0 +1,56 @@ +use std::time::Instant; + +use aha::{models::{qwen2_5vl::generate::Qwen2_5VLGenerateModel, GenerateModel}, ModelType}; +use anyhow::{Result}; +use candle_core::{DType, Device}; +use openai_dive::v1::resources::chat::ChatCompletionParameters; + + +#[test] +fn qwen2_5vl_generate() -> Result<()> { + // test with cpu :(太慢了, : RUST_BACKTRACE=1 cargo test qwen2_5vl_generate -- --nocapture + // test with cuda: RUST_BACKTRACE=1 cargo test -F cuda qwen2_5vl_generate -- --nocapture + // test with cuda+flash-attn: RUST_BACKTRACE=1 cargo test -F cuda,flash-attn qwen2_5vl_generate -- --nocapture + let device = Device::cuda_if_available(0)?; + let dtype = DType::BF16; + + let model_path = "/home/jhq/huggingface_model/Qwen/Qwen2.5-VL-3B-Instruct/"; + + let message = r#" + { + "model": "qwen2.5vl", + "messages": [ + { + "role": "user", + "content": [ + { + "type": "image", + "image_url": + { + "url": "file://./assets/img/ocr_test.png" + } + }, + { + "type": "text", + "text": "请分析图片并提取所有可见文本内容,按从左到右、从上到下的布局,返回纯文本" + } + ] + } + ] + } + "#; + let mes:ChatCompletionParameters = serde_json::from_str(message)?; + let i_start = Instant::now(); + // let mut model = Qwen2_5VLGenerateModel::init(model_path, &device, dtype)?; + let mut model = ModelType::init(ModelType::Qwen2_5VL, model_path, None, None)?; + let i_duration = i_start.elapsed(); + println!("Time elapsed in load model is: {:?}", i_duration); + + let i_start = Instant::now(); + let result = model.generate(mes)?; + println!("generate: \n{}", result); + let i_duration = i_start.elapsed(); + println!("Time elapsed in generate is: {:?}", i_duration); + + Ok(()) +} \ No newline at end of file