From e53e601e629f2e3b74e8ce2f600e07c73be22841 Mon Sep 17 00:00:00 2001 From: Justin Icenhour Date: Thu, 9 Jul 2026 09:54:44 -0500 Subject: [PATCH 1/7] =?UTF-8?q?feat(p0):=20cargo=20workspace=20scaffold=20?= =?UTF-8?q?=E2=80=94=20mummu=20lib=20+=20mummu-bench=20criterion=20stub?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Workspace with the parity-pinned combo (burn 0.21 wgpu/ndarray/fusion/autotune/store, wgpu 29, tokenizers 0.22), criterion 0.7 harness wired and verified green (fmt / clippy --all-targets / test / build / bench all pass). Cargo.lock committed for reproducible builds. Co-Authored-By: Claude Opus 4.8 --- Cargo.lock | 6330 ++++++++++++++++++++++++++ Cargo.toml | 44 + ROADMAP.md | 8 +- crates/mummu-bench/Cargo.toml | 18 + crates/mummu-bench/benches/runner.rs | 15 + crates/mummu-bench/src/lib.rs | 2 + crates/mummu/Cargo.toml | 18 + crates/mummu/src/lib.rs | 10 + 8 files changed, 6442 insertions(+), 3 deletions(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 crates/mummu-bench/Cargo.toml create mode 100644 crates/mummu-bench/benches/runner.rs create mode 100644 crates/mummu-bench/src/lib.rs create mode 100644 crates/mummu/Cargo.toml create mode 100644 crates/mummu/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7e1c505 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,6330 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[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.4", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anyhow" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading 0.8.9", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atomic_float" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628d228f918ac3b82fe590352cc719d30664a0c13ca3a60266fe02c7132d480a" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror 2.0.18", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom 8.0.0", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[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 = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "serde", + "unty", +] + +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.3", + "shlex 1.3.0", + "syn", +] + +[[package]] +name = "bit-set" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +dependencies = [ + "serde_core", +] + +[[package]] +name = "bitstream-io" +version = "4.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" +dependencies = [ + "no_std_io2", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "bon" +version = "3.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a602c73c7b0148ec6d12af6fd5cc7a46e2eacc8878271a999abac56eed12f561" +dependencies = [ + "bon-macros", + "rustversion", +] + +[[package]] +name = "bon-macros" +version = "3.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dee98b0db6a962de883bf5d20362dee4d7ca0d12fe39a7c6c73c844e1cd7c1f" +dependencies = [ + "darling 0.23.0", + "ident_case", + "prettyplease", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "built" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "burn" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39474be398d30e08681f1f6a8ff446b1e4f1403b5cf7749649a8871fd5945f3a" +dependencies = [ + "burn-autodiff", + "burn-candle", + "burn-core", + "burn-cpu", + "burn-cuda", + "burn-dispatch", + "burn-flex", + "burn-ir", + "burn-ndarray", + "burn-nn", + "burn-optim", + "burn-rocm", + "burn-router", + "burn-std", + "burn-store", + "burn-tch", + "burn-vision", + "burn-wgpu", +] + +[[package]] +name = "burn-autodiff" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b93a80e43bfab909399444b29ce3894ff51f7e879720bfc75b6007ae6a01c3d" +dependencies = [ + "burn-backend", + "burn-std", + "derive-new", + "hashbrown 0.16.1", + "log", + "num-traits", + "parking_lot", + "portable-atomic", + "spin", +] + +[[package]] +name = "burn-backend" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64491519ac0867f550fa9c726cd443e5db94608c629050986cf2614c8c5ad39f" +dependencies = [ + "burn-std", + "bytemuck", + "cubecl", + "derive-new", + "enumset", + "hashbrown 0.16.1", + "num-traits", + "portable-atomic-util", + "rand 0.10.2", + "rand_distr 0.6.0", + "serde", + "spin", + "thiserror 2.0.18", +] + +[[package]] +name = "burn-candle" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917fbbe1238d80ec1483c2c360ed9fa0146fdf57b2e66015c0824ccf5155276c" +dependencies = [ + "burn-backend", + "burn-std", + "candle-core", + "derive-new", +] + +[[package]] +name = "burn-core" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bde1e3b8e7e39b0aa71cd5905207ceea5c3478bec47bdb64acf72dcd0d6f978" +dependencies = [ + "ahash", + "bincode", + "burn-derive", + "burn-std", + "burn-tensor", + "data-encoding", + "derive-new", + "flate2", + "half", + "hashbrown 0.16.1", + "log", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rand 0.10.2", + "regex", + "rmp-serde", + "serde", + "serde_json", + "spin", + "uuid", +] + +[[package]] +name = "burn-cpu" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681478c5438ab6c753a55b85f5b42e5bd4c1f69a8564f2afa2f698ac396ba556" +dependencies = [ + "burn-backend", + "burn-cubecl", + "burn-fusion", + "cubecl", +] + +[[package]] +name = "burn-cubecl" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26efbec96ca3f691593c966fa90f98ef8a72867f62627904920b619ba3a9e6b6" +dependencies = [ + "burn-backend", + "burn-cubecl-fusion", + "burn-fusion", + "burn-ir", + "burn-std", + "cubecl", + "cubek", + "derive-new", + "futures-lite", + "log", + "serde", + "text_placeholder", +] + +[[package]] +name = "burn-cubecl-fusion" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6993d15b8ed588f55626e835ca768c1c3c7aa7a79d59beff4c987c40fc82d1f4" +dependencies = [ + "burn-backend", + "burn-fusion", + "burn-ir", + "burn-std", + "cubecl", + "cubek", + "derive-new", + "hashbrown 0.16.1", + "serde", + "spin", +] + +[[package]] +name = "burn-cuda" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6c66b49136fc11f59773054358f401fbec7fa0d69615f55ef22c29cf241c9b7" +dependencies = [ + "burn-backend", + "burn-cubecl", + "burn-fusion", + "cubecl", +] + +[[package]] +name = "burn-derive" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce7be43cdfc9903c43dbaf3f4821543e0497d79047d8fc9ee9084e448844446" +dependencies = [ + "derive-new", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "burn-dispatch" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2087b9e12323e0d25cc0ebdccfee40427dfdec4a23fa6d3f49ba2aa1f94ac17" +dependencies = [ + "burn-autodiff", + "burn-backend", + "burn-cpu", + "burn-cuda", + "burn-flex", + "burn-ndarray", + "burn-rocm", + "burn-tch", + "burn-wgpu", + "paste", +] + +[[package]] +name = "burn-flex" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0098bf6555c151517b4c98ca0e8ed1ab6a976e40f5f4e967c117b00eee21e192" +dependencies = [ + "burn-backend", + "burn-ir", + "burn-std", + "bytemuck", + "gemm", + "half", + "libm", + "num-traits", +] + +[[package]] +name = "burn-fusion" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95e58fcfd52a60cdf2f7da34fbebf0ed38f960fbd8675d384bacd5795cf2bdf2" +dependencies = [ + "burn-backend", + "burn-ir", + "burn-std", + "derive-new", + "hashbrown 0.16.1", + "log", + "serde", + "smallvec", + "spin", + "tracing", +] + +[[package]] +name = "burn-ir" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a81a55c32f35c2265d9e15ba2e796013d9780d5a49f9e21ea0ac4d29609dbf13" +dependencies = [ + "burn-backend", + "hashbrown 0.16.1", + "serde", +] + +[[package]] +name = "burn-ndarray" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dceb9692e292782bab7ad0259e8e8f5ee80ba2b0329a78f0ba589a26a9633dd6" +dependencies = [ + "atomic_float", + "burn-autodiff", + "burn-backend", + "burn-ir", + "burn-std", + "const-random", + "libm", + "macerator", + "matrixmultiply", + "ndarray 0.17.2", + "num-traits", + "paste", + "portable-atomic", + "portable-atomic-util", + "rand 0.10.2", +] + +[[package]] +name = "burn-nn" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e4acd90806fa8663610f1071f3a8992eb98637d2d0816ee3062b334f61af00" +dependencies = [ + "burn-core", + "num-traits", +] + +[[package]] +name = "burn-optim" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c5ea466ae0b85bf18f7656cdc152050dc8c581057fbbe33bbed267e22600228" +dependencies = [ + "burn-core", + "derive-new", + "hashbrown 0.16.1", + "log", + "num-traits", + "serde", +] + +[[package]] +name = "burn-rocm" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66af82fdaaf2ad0fa4b0dae90119aa94f333105d76d520e0eb48917717c06fde" +dependencies = [ + "burn-backend", + "burn-cubecl", + "burn-fusion", + "cubecl", +] + +[[package]] +name = "burn-router" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7960a5d43918c3158629da2bd7956e38dda16ef54de95cca83b4b7b1477e6185" +dependencies = [ + "burn-backend", + "burn-ir", + "burn-std", + "hashbrown 0.16.1", + "log", + "spin", +] + +[[package]] +name = "burn-std" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81c7eb60eb2c316854af5b214fd55f59fc7b0e25dfde7db671f09cc49f269048" +dependencies = [ + "bytemuck", + "bytes", + "cubecl", + "cubecl-common", + "cubecl-zspace", + "half", + "num-traits", + "serde", + "smallvec", + "spin", +] + +[[package]] +name = "burn-store" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f64a4fe2a0616d18f07d1d0d19c79e655e70e1828cbcb5e9df7b52211091023" +dependencies = [ + "burn-core", + "burn-tensor", + "byteorder", + "bytes", + "half", + "hashbrown 0.16.1", + "memmap2", + "regex", + "safetensors 0.7.0", + "textdistance", +] + +[[package]] +name = "burn-tch" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa36b630bd7a790395b4561f7d0e315c0d5e1c557db7dd4cf1b48d5c0d4aff2" +dependencies = [ + "burn-backend", + "cc", + "libc", + "log", + "tch", + "torch-sys", +] + +[[package]] +name = "burn-tensor" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "223ed3804bb9436e401fc57b87e44c86267070b870813520ed9f706c80c81442" +dependencies = [ + "burn-backend", + "burn-std", + "colored", + "derive-new", + "num-traits", + "serde", +] + +[[package]] +name = "burn-vision" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b5a3e9096977462cd22bf713087f9777e427f7693508871f86f5b1e8347a70" +dependencies = [ + "aligned-vec", + "bon", + "burn-cubecl", + "burn-fusion", + "burn-ir", + "burn-tensor", + "bytemuck", + "cubecl", + "derive-new", + "half", + "image", + "macerator", + "ndarray 0.17.2", + "num-traits", + "paste", + "serde", +] + +[[package]] +name = "burn-wgpu" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eb009254af15922cb37814f3b3b61043046193ba2fde97c3879a25fbd51a92e" +dependencies = [ + "burn-backend", + "burn-cubecl", + "burn-fusion", + "cubecl", +] + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +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.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "candle-core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd9895436c1ba5dc1037a19935d084b838db066ff4e15ef7dded020b7c12a4a" +dependencies = [ + "byteorder", + "float8", + "gemm", + "half", + "libm", + "memmap2", + "num-traits", + "num_cpus", + "rand 0.9.4", + "rand_distr 0.5.1", + "rayon", + "safetensors 0.7.0", + "thiserror 2.0.18", + "tokenizers", + "yoke", + "zip 7.2.0", +] + +[[package]] +name = "caseless" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6fd507454086c8edfd769ca6ada439193cdb209c7681712ef6275cccbfe5d8" +dependencies = [ + "unicode-normalization", +] + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[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.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex 2.0.1", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading 0.8.9", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "compact_str" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "serde", + "static_assertions", +] + +[[package]] +name = "comrak" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fefab951771fc3beeed0773ce66a4f7b706273fc6c4c95b08dd1615744abcf5" +dependencies = [ + "caseless", + "entities", + "memchr", + "slug", + "typed-arena", + "unicode_categories", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c" +dependencies = [ + "encode_unicode", + "libc", + "unicode-width", + "windows-sys 0.61.2", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "constcat" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d3e02915a2cea4d74caa8681e2d44b1c3254bdbf17d11d41d587ff858832c" + +[[package]] +name = "convert_case" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "criterion" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools 0.13.0", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" +dependencies = [ + "cast", + "itertools 0.13.0", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-deque" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cubecl" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd203fef6e359472e4cb8f6c0ef3eca062815a1edd560eb6d6c1d5c1397838d9" +dependencies = [ + "cubecl-core", + "cubecl-cpu", + "cubecl-cuda", + "cubecl-hip", + "cubecl-ir", + "cubecl-runtime", + "cubecl-std", + "cubecl-wgpu", + "half", +] + +[[package]] +name = "cubecl-common" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc956c2dcc993f16f748d03bfdbd900f75cab4d469f4e5d8924f8ee128e861ad" +dependencies = [ + "backtrace", + "bytemuck", + "bytes", + "cfg-if", + "cfg_aliases", + "ciborium", + "derive-new", + "derive_more", + "dirs", + "embassy-futures", + "embassy-time", + "float4", + "float8", + "futures-lite", + "half", + "hashbrown 0.16.1", + "log", + "num-traits", + "oneshot", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "rand 0.10.2", + "sanitize-filename", + "serde", + "serde_bytes", + "serde_json", + "spin", + "toml", + "tracing", + "tynm", + "wasm-bindgen-futures", + "web-time", + "xxhash-rust", +] + +[[package]] +name = "cubecl-core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7522e7acf25d7848032c7b5270780f9f2abe84e13c7ed6345aa27ba70c312ca" +dependencies = [ + "bitflags", + "bytemuck", + "cubecl-common", + "cubecl-ir", + "cubecl-macros", + "cubecl-runtime", + "cubecl-zspace", + "derive-new", + "derive_more", + "enumset", + "float-ord", + "half", + "hashbrown 0.16.1", + "log", + "num-traits", + "paste", + "serde", + "serde_json", + "tracing", + "variadics_please", +] + +[[package]] +name = "cubecl-cpp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411af04828cbf4583ecd388579fb30cd7a644eec19a334bb8f0d9d08522e1458" +dependencies = [ + "bytemuck", + "cubecl-common", + "cubecl-core", + "cubecl-opt", + "cubecl-runtime", + "derive-new", + "half", + "itertools 0.14.0", + "log", +] + +[[package]] +name = "cubecl-cpu" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f572143f54e42a49ee0635a4ec36005f639e62be029e4f49890c808985981b35" +dependencies = [ + "bytemuck", + "cubecl-common", + "cubecl-core", + "cubecl-opt", + "cubecl-runtime", + "cubecl-std", + "derive-new", + "half", + "log", + "paste", + "serde", + "sysinfo", + "tracel-llvm", + "tracel-llvm-bundler", +] + +[[package]] +name = "cubecl-cuda" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b0a69ff45688d322ad8e92c8bf645167b9ca490fa8fa087fc6adac8c5e46be" +dependencies = [ + "bytemuck", + "cubecl-common", + "cubecl-core", + "cubecl-cpp", + "cubecl-runtime", + "cudarc", + "derive-new", + "half", + "log", + "serde", + "tracing", +] + +[[package]] +name = "cubecl-hip" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6b510a9348f06ecd56b32cf10eb6241639e927a87f5c09ec61cc7a7610d5a3" +dependencies = [ + "bytemuck", + "cubecl-common", + "cubecl-core", + "cubecl-cpp", + "cubecl-hip-sys", + "cubecl-runtime", + "derive-new", + "half", + "log", + "paste", + "serde", + "tracing", +] + +[[package]] +name = "cubecl-hip-sys" +version = "7.2.5321100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58887c6d217859c2261a868a3a6b66aa8f44ea2f4bfa51d0dbe4dcb0d1f5768d" +dependencies = [ + "libc", + "regex", +] + +[[package]] +name = "cubecl-ir" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d28a897a40c8ee6c57a8b8d76d8823ba2e97f4c2b83db9338f17a0752132d486" +dependencies = [ + "cubecl-common", + "cubecl-macros-internal", + "derive-new", + "derive_more", + "enumset", + "float-ord", + "fnv", + "foldhash 0.2.0", + "half", + "hashbrown 0.16.1", + "num-traits", + "portable-atomic", + "serde", + "variadics_please", +] + +[[package]] +name = "cubecl-macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77aa6df563e8e6a0926d2e9eeacb968737940a0e1ae9be819f6a65a341006e12" +dependencies = [ + "cubecl-common", + "darling 0.23.0", + "derive-new", + "ident_case", + "inflections", + "prettyplease", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cubecl-macros-internal" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a515651a5a91e25d87f71f311f80a088e6cf815798b9922560a97636da6b8740" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cubecl-opt" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a599c6c3efefdcee8636994c90e58ef696c7efbed2d18b5b5ad8d5f29923abb" +dependencies = [ + "cubecl-common", + "cubecl-core", + "cubecl-ir", + "float-ord", + "log", + "num", + "petgraph", + "smallvec", + "stable-vec", + "type-map", +] + +[[package]] +name = "cubecl-runtime" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b68491bf5b3e997ae36bdc4e63b4ccd6d2f0e86b3b596a5d7a48d2b9e92622a0" +dependencies = [ + "ahash", + "async-channel", + "bytemuck", + "cfg-if", + "cfg_aliases", + "cubecl-common", + "cubecl-ir", + "cubecl-zspace", + "derive-new", + "derive_more", + "dirs", + "enumset", + "hashbrown 0.16.1", + "log", + "md5", + "serde", + "serde_json", + "spin", + "thiserror 2.0.18", + "toml", + "tracing", + "wasm-bindgen-futures", + "web-time", +] + +[[package]] +name = "cubecl-std" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b391b584a4897683dd9fc0767f96337ac712b733126ce0f68a9ee8a2df073d2d" +dependencies = [ + "cubecl-common", + "cubecl-core", + "cubecl-runtime", + "half", + "num-traits", + "paste", + "serde", + "spin", + "variadics_please", +] + +[[package]] +name = "cubecl-wgpu" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3663c6e1a86187172b2cee495b6b533e7d91a2cb37e26f421649e315fe4c3a" +dependencies = [ + "async-channel", + "bytemuck", + "cfg-if", + "cfg_aliases", + "cubecl-common", + "cubecl-core", + "cubecl-ir", + "cubecl-runtime", + "derive-new", + "derive_more", + "half", + "hashbrown 0.16.1", + "log", + "sanitize-filename", + "tracing", + "wasm-bindgen-futures", + "wgpu", +] + +[[package]] +name = "cubecl-zspace" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04054d95698afab785a4dda9f949e297831dd9e4cc6d036a93e6603f88e88b07" +dependencies = [ + "derive-new", + "serde", + "smallvec", +] + +[[package]] +name = "cubek" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62fb5043d5eb723017415eec57c541f41901beea984500e2e16b57d75fa717" +dependencies = [ + "cubecl", + "cubek-attention", + "cubek-convolution", + "cubek-fft", + "cubek-matmul", + "cubek-quant", + "cubek-random", + "cubek-reduce", + "cubek-std", +] + +[[package]] +name = "cubek-attention" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75ca5c363f05a9297b9d1c0e90959cc5f9fd90bc596e4e537f26f20602ec63a5" +dependencies = [ + "bytemuck", + "cubecl", + "cubecl-common", + "cubek-matmul", + "cubek-std", + "half", + "serde", +] + +[[package]] +name = "cubek-convolution" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f2755a8eb9ea5509c17edac78e4a9dc4be89c433d8e2ee709703a93b96f749" +dependencies = [ + "bytemuck", + "cubecl", + "cubecl-common", + "cubek-matmul", + "cubek-std", + "derive-new", + "enumset", + "half", + "serde", +] + +[[package]] +name = "cubek-fft" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd7c2972363332dc4609067a2ccc21856308d98089e676d3f0c52747ae61a5b" +dependencies = [ + "cubecl", +] + +[[package]] +name = "cubek-matmul" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a4cea5f0f439907dc953c7638a6204b3f055f1bcbd10db91dfc5faa030ac1c" +dependencies = [ + "bytemuck", + "cubecl", + "cubecl-common", + "cubek-std", + "half", + "serde", +] + +[[package]] +name = "cubek-quant" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "802ff1b5f19597a7b7cb308c7c69e9eef28b78d57dab7794c816bb8f7d3d1b85" +dependencies = [ + "cubecl", + "cubecl-common", + "half", + "serde", +] + +[[package]] +name = "cubek-random" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dfecce959bdbeb3b355120e586d1ea9e45a0fc7f2ca354f4260a571952551be" +dependencies = [ + "cubecl", + "cubecl-common", + "half", + "num-traits", + "rand 0.10.2", + "serde", +] + +[[package]] +name = "cubek-reduce" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86758b84452b06e9121f25200da13b4909ecd6235cefb5f33f459bea24dac41a" +dependencies = [ + "cubecl", + "cubek-std", + "half", + "num-traits", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "cubek-std" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a587d0a44b6f14f4c3711ac10717179b48adb59203b2b2bffe334876bf713187" +dependencies = [ + "cubecl", + "cubecl-common", + "half", +] + +[[package]] +name = "cudarc" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42310153e06cf4cd532901f7096beb27504d681736a29ee90728ae4e2d93b2a8" +dependencies = [ + "libloading 0.9.0", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[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_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "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 0.20.11", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn", +] + +[[package]] +name = "dary_heap" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1e3a325bc115f096c8b77bbf027a7c2592230e70be2d985be950d3d5e60ebe" +dependencies = [ + "serde", +] + +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "derive-new" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cdc8d50f426189eef89dac62fabfa0abb27d5cc008f25bf4156a0203325becc" +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 0.20.11", + "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 = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case 0.10.0", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "deunicode" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading 0.8.9", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dyn-stack" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c4713e43e2886ba72b8271aa66c93d722116acf7a75555cce11dcde84388fe8" +dependencies = [ + "bytemuck", + "dyn-stack-macros", +] + +[[package]] +name = "dyn-stack-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d926b4d407d372f141f93bb444696142c29d32962ccbd3531117cf3aa0bfa9" + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "embassy-futures" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" + +[[package]] +name = "embassy-time" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "592b0c143ec626e821d4d90da51a2bd91d559d6c442b7c74a47d368c9e23d97a" +dependencies = [ + "cfg-if", + "critical-section", + "document-features", + "embassy-time-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "futures-core", +] + +[[package]] +name = "embassy-time-driver" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ee71af1b3a0deaa53eaf2d39252f83504c853646e472400b763060389b9fcc9" +dependencies = [ + "document-features", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "entities" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" + +[[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 = "enumset" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839c4174b41e75c8f7306110b2c51996a293b8d1d850edd529011841d9fede7d" +dependencies = [ + "enumset_derive", + "serde", +] + +[[package]] +name = "enumset_derive" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd536557b58c682b217b8fb199afdff47cd3eff260623f19e77074eb073d63a" +dependencies = [ + "darling 0.21.3", + "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.2", +] + +[[package]] +name = "esaxx-rs" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6" +dependencies = [ + "cc", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "exr" +version = "1.74.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6be87932f10230a4339ab394edd8e4611fcb72553d8295b4d52ea55249b3daa5" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "num-complex", + "pulp", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + +[[package]] +name = "float4" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a5404bf31d22893d61cf24d4dda149d8e6b2ff07601c3cb3be651031f61a4ed" + +[[package]] +name = "float8" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d1f04709a8ac06e8e8042875a3c466cc4832d3c1a18dbcb9dba3c6e83046bc" +dependencies = [ + "half", + "num-traits", + "rand 0.9.4", + "rand_distr 0.5.1", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[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.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gemm" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa0673db364b12263d103b68337a68fbecc541d6f6b61ba72fe438654709eacb" +dependencies = [ + "dyn-stack", + "gemm-c32", + "gemm-c64", + "gemm-common", + "gemm-f16", + "gemm-f32", + "gemm-f64", + "num-complex", + "num-traits", + "paste", + "raw-cpuid", + "seq-macro", +] + +[[package]] +name = "gemm-c32" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "086936dbdcb99e37aad81d320f98f670e53c1e55a98bee70573e83f95beb128c" +dependencies = [ + "dyn-stack", + "gemm-common", + "num-complex", + "num-traits", + "paste", + "raw-cpuid", + "seq-macro", +] + +[[package]] +name = "gemm-c64" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c8aeeeec425959bda4d9827664029ba1501a90a0d1e6228e48bef741db3a3f" +dependencies = [ + "dyn-stack", + "gemm-common", + "num-complex", + "num-traits", + "paste", + "raw-cpuid", + "seq-macro", +] + +[[package]] +name = "gemm-common" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88027625910cc9b1085aaaa1c4bc46bb3a36aad323452b33c25b5e4e7c8e2a3e" +dependencies = [ + "bytemuck", + "dyn-stack", + "half", + "libm", + "num-complex", + "num-traits", + "once_cell", + "paste", + "pulp", + "raw-cpuid", + "rayon", + "seq-macro", + "sysctl", +] + +[[package]] +name = "gemm-f16" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3df7a55202e6cd6739d82ae3399c8e0c7e1402859b30e4cb780e61525d9486e" +dependencies = [ + "dyn-stack", + "gemm-common", + "gemm-f32", + "half", + "num-complex", + "num-traits", + "paste", + "raw-cpuid", + "rayon", + "seq-macro", +] + +[[package]] +name = "gemm-f32" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e0b8c9da1fbec6e3e3ab2ce6bc259ef18eb5f6f0d3e4edf54b75f9fd41a81c" +dependencies = [ + "dyn-stack", + "gemm-common", + "num-complex", + "num-traits", + "paste", + "raw-cpuid", + "seq-macro", +] + +[[package]] +name = "gemm-f64" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "056131e8f2a521bfab322f804ccd652520c79700d81209e9d9275bbdecaadc6a" +dependencies = [ + "dyn-stack", + "gemm-common", + "num-complex", + "num-traits", + "paste", + "raw-cpuid", + "seq-macro", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "gif" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "glow" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-allocator" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" +dependencies = [ + "ash", + "hashbrown 0.16.1", + "log", + "presser", + "thiserror 2.0.18", + "windows", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags", +] + +[[package]] +name = "h2" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "bytemuck", + "cfg-if", + "crunchy", + "num-traits", + "rand 0.9.4", + "rand_distr 0.5.1", + "serde", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", + "serde", + "serde_core", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[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 = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +dependencies = [ + "bytes", + "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.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots 1.0.8", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +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.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89194689a993ab15268672e99e7b0e19da2da3268ac682e8f02d29d4d1434cd7" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "indicatif" +version = "0.18.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9433806cd6b4ec1aba79c021c7e4c58fb4c3b9977c085062e611ac929998fb0c" +dependencies = [ + "console", + "portable-atomic", + "unicode-width", + "unit-prefix", + "web-time", +] + +[[package]] +name = "inflections" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[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 = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[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.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading 0.8.9", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[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.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libloading" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "liblzma" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45aec2360b3933207e27908049d8e4df4e476b58180afb1e56b2a4fb72efe4ba" +dependencies = [ + "liblzma-sys", + "num_cpus", +] + +[[package]] +name = "liblzma-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a046c7f353ba30f810545151e04f63545833803f5b86ee3ddf1517247fe560a5" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "macerator" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "508a2f720538bb7e3ea3cb6d098615b107cb82ae387d77d906276905e9ec894b" +dependencies = [ + "bytemuck", + "cfg_aliases", + "half", + "macerator-macros", + "moddef", + "num-traits", + "paste", + "rustc_version", +] + +[[package]] +name = "macerator-macros" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5ce85961d618ce9794bdf822bfe96fe9dd341aa5b033b454f7a8d96e79b9b1" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn", +] + +[[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 = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[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 = "md5" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebb8d8732c6a6df3d8f032a82911cfc747e00efb95cc46e8d0acd5b5b88570c" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", + "stable_deref_trait", +] + +[[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.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moddef" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0b3262dc837d2513fe2ef31ff8461352ef932dcca31ba0c0abe33547cf6b9b" + +[[package]] +name = "monostate" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3341a273f6c9d5bef1908f17b7267bbab0e95c9bf69a0d4dcf8e9e1b2c76ef67" +dependencies = [ + "monostate-impl", + "serde", + "serde_core", +] + +[[package]] +name = "monostate-impl" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4db6d5580af57bf992f59068d4ea26fd518574ff48d7639b255a36f9de6e7e9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "mummu" +version = "0.1.0" +dependencies = [ + "burn", + "half", + "once_cell", + "pollster", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokenizers", + "wgpu", +] + +[[package]] +name = "mummu-bench" +version = "0.1.0" +dependencies = [ + "criterion", + "mummu", +] + +[[package]] +name = "naga" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2bf919621e7975acb27d881bae2fb993e0d45c8e0446e85e6272971e00dc8df" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags", + "cfg-if", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.16.1", + "hexf-parse", + "indexmap", + "libm", + "log", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "spirv", + "thiserror 2.0.18", + "unicode-ident", +] + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "ndarray" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", +] + +[[package]] +name = "ndarray" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "no_std_io2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" +dependencies = [ + "memchr", +] + +[[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 = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +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-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[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.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "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 = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "oneshot" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe21416a02c693fb9f980befcb230ecc70b0b3d1cc4abf88b9675c4c1457f0c" + +[[package]] +name = "onig" +version = "6.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc3cbf698f9438986c11a880c90a6d04b9de27575afd28bbf45b154b6c709e2" +dependencies = [ + "bitflags", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e68317604e77e53b85896388e1a803c1d21b74c899ec9e5e1112db90735edd7" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-float" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", + "serde", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +dependencies = [ + "serde", +] + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "pulp" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046aa45b989642ec2e4717c8e72d677b13edd831a4d3b6cf37d9a3e54912496a" +dependencies = [ + "bytemuck", + "cfg-if", + "libm", + "num-complex", + "paste", + "pulp-wasm-simd-flag", + "raw-cpuid", + "reborrow", + "version_check", +] + +[[package]] +name = "pulp-wasm-simd-flag" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8f70e07b9c3962945a74e59ca1c511bba65b6419468acc217c457d93f3c740" + +[[package]] +name = "pxfm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" + +[[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 = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.3", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.2", + "rand_pcg", + "ring", + "rustc-hash 2.1.3", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +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 = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[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.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[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.4", +] + +[[package]] +name = "rand_distr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d431c2703ccf129de4d45253c03f49ebb22b97d6ad79ee3ecfc7e3f4862c1d8" +dependencies = [ + "num-traits", + "rand 0.10.2", +] + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "range-alloc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.14.0", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand 0.9.4", + "rand_chacha 0.9.0", + "simd_helpers", + "thiserror 2.0.18", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "raw-window-metal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" +dependencies = [ + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +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 = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", +] + +[[package]] +name = "regex" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 1.0.8", +] + +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" + +[[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.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rmp" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "safetensors" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93279b86b3de76f820a8854dd06cbc33cfa57a417b19c47f6a25280112fb1df" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "safetensors" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675656c1eabb620b921efea4f9199f97fc86e36dd6ffd1fbbe48d0f59a4987f5" +dependencies = [ + "hashbrown 0.16.1", + "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 = "sanitize-filename" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc984f4f9ceb736a7bb755c3e3bd17dc56370af2600c9780dcc48c66453da34d" +dependencies = [ + "regex", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[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.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[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 = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[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.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "slug" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" +dependencies = [ + "deunicode", + "wasm-bindgen", +] + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +dependencies = [ + "lock_api", + "portable-atomic", +] + +[[package]] +name = "spirv" +version = "0.4.0+sdk-1.4.341.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" +dependencies = [ + "bitflags", +] + +[[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 7.1.3", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "stable-vec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dac7bc0f7d0d44329b200020effbc25a534d89fa142af95e3ddf76113412a5e" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[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.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +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.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc" +dependencies = [ + "bitflags", + "byteorder", + "enum-as-inner", + "libc", + "thiserror 1.0.69", + "walkdir", +] + +[[package]] +name = "sysinfo" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows", +] + +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tch" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e09b91610202dc4820c21eb474a42b386ef69f323b1c0902b5472ba7456ebb5" +dependencies = [ + "half", + "lazy_static", + "libc", + "ndarray 0.16.1", + "rand 0.8.6", + "safetensors 0.3.3", + "thiserror 1.0.69", + "torch-sys", + "zip 0.6.6", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "text_placeholder" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5008f74a09742486ef0047596cf35df2b914e2a8dca5727fcb6ba6842a766b" +dependencies = [ + "hashbrown 0.13.2", + "serde", + "serde_json", +] + +[[package]] +name = "textdistance" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa672c55ab69f787dbc9126cc387dbe57fdd595f585e4524cf89018fa44ab819" + +[[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.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[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.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + +[[package]] +name = "time" +version = "0.3.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokenizers" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b238e22d44a15349529690fb07bd645cf58149a1b1e44d6cb5bd1641ff1a6223" +dependencies = [ + "ahash", + "aho-corasick", + "compact_str", + "dary_heap", + "derive_builder", + "esaxx-rs", + "getrandom 0.3.4", + "indicatif", + "itertools 0.14.0", + "log", + "macro_rules_attribute", + "monostate", + "onig", + "paste", + "rand 0.9.4", + "rayon", + "rayon-cond", + "regex", + "regex-syntax", + "serde", + "serde_json", + "spm_precompiled", + "thiserror 2.0.18", + "unicode-normalization-alignments", + "unicode-segmentation", + "unicode_categories", +] + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "torch-sys" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef40c585e342df95b66a1fa7c923188623999c2b657227befb481dfb03a6a42" +dependencies = [ + "anyhow", + "cc", + "libc", + "serde", + "serde_json", + "ureq", + "zip 0.6.6", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[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 = "tracel-llvm" +version = "20.1.4-7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982535db9eb1a30ac0f2c50239a0eec3e5cf50993a88e92b04747bd2f4d365b2" +dependencies = [ + "tracel-mlir-rs", + "tracel-mlir-sys", +] + +[[package]] +name = "tracel-llvm-bundler" +version = "20.1.4-7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c75b8e477cb8d49d907afab029ca74d48459f5b88c27bdb4c6cd6acb5e61977" +dependencies = [ + "anyhow", + "bytes", + "constcat", + "dirs", + "liblzma", + "regex", + "reqwest", + "serde", + "serde_json", + "sha2", + "tar", + "walkdir", +] + +[[package]] +name = "tracel-mlir-rs" +version = "20.1.4-7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a478a35efd68d0ba73f747adfb7923b121c64e7f5be9cd8364ca1dcb772d5c" +dependencies = [ + "tracel-mlir-rs-macros", + "tracel-mlir-sys", +] + +[[package]] +name = "tracel-mlir-rs-macros" +version = "20.1.4-7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a94f36868c3b10b1825945223d99d106c73f4d249f063caa4651deeb9379344" +dependencies = [ + "comrak", + "convert_case 0.8.0", + "proc-macro2", + "quote", + "regex", + "syn", + "tracel-llvm-bundler", + "tracel-tblgen-rs", + "unindent", +] + +[[package]] +name = "tracel-mlir-sys" +version = "20.1.4-7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02f26d31af0c225a6d2e3d65d012fd6de848c9fc776897b152ee83b7d1bd15c4" +dependencies = [ + "tracel-llvm-bundler", +] + +[[package]] +name = "tracel-tblgen-rs" +version = "20.1.4-7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d2581070380418ccc33b500f3739e4d4869421fdb477fcea51ff97c6253a52" +dependencies = [ + "bindgen", + "cc", + "paste", + "thiserror 2.0.18", + "tracel-llvm-bundler", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +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 = "tynm" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21cdb0fc8f85c98b1ec812bc4cd69faf6c0fa2fc17d44ea3c2cdd38dc08e999" +dependencies = [ + "nom 8.0.0", +] + +[[package]] +name = "type-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" +dependencies = [ + "rustc-hash 2.1.3", +] + +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[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.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" + +[[package]] +name = "unit-prefix" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64 0.22.1", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "url", + "webpki-roots 0.26.11", +] + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +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 = "uuid" +version = "1.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" + +[[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 = "variadics_please" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[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 = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +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 = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.8", +] + +[[package]] +name = "webpki-roots" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "wgpu" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e8840e1ba2881d4cbb18d2147627a56af426ff064c0401eb0c8410c6325d07" +dependencies = [ + "arrayvec", + "bitflags", + "bytemuck", + "cfg-if", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "js-sys", + "log", + "naga", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f519832254e56965a9940c4af57dcb75f702b6f6fa4a0b172f685395843a4d7" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags", + "bytemuck", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.18", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-naga-bridge", + "wgpu-types", +] + +[[package]] +name = "wgpu-core-deps-apple" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e39e26c4c0e07589e67d18546cf79ff45383659fc72fca4dd293358a0347f3" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e09be551dc939498bdd5f6b2c66e55ab275dad25825267a08605a80fc9f0af" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e592c1bbef6ad047647ae6e666ebd8cee7a32bb4544d9700ec96cbf73230257" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-hal" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ace1c17727311c22a46e4e3faf56ea6de81af99dcc839bdfb54857b94d448d" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags", + "block2", + "bytemuck", + "cfg-if", + "cfg_aliases", + "glow", + "glutin_wgl_sys", + "gpu-allocator", + "gpu-descriptor", + "hashbrown 0.16.1", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.9", + "log", + "naga", + "ndk-sys", + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-metal", + "objc2-quartz-core", + "once_cell", + "ordered-float", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "profiling", + "range-alloc", + "raw-window-handle", + "raw-window-metal", + "renderdoc-sys", + "smallvec", + "thiserror 2.0.18", + "wasm-bindgen", + "wayland-sys", + "web-sys", + "wgpu-naga-bridge", + "wgpu-types", + "windows", + "windows-core", + "windows-result", +] + +[[package]] +name = "wgpu-naga-bridge" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95226013f547544b223281cd16a4fb549aa9dcb562adbda0faae4c73ffbbc161" +dependencies = [ + "naga", + "wgpu-types", +] + +[[package]] +name = "wgpu-types" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84bf84cd9ca8ca45e2b223a3868f1adf9bfc0c66aeac212e76ee7e40fdadf8f5" +dependencies = [ + "bitflags", + "bytemuck", + "js-sys", + "log", + "raw-window-handle", + "web-sys", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[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.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + +[[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_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "xxhash-rust" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d93c89cdc2d3a63c3ec48ffe926931bdc069eafa8e4402fe6d8f790c9d1e576" + +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2", + "sha1", + "time", + "zstd", +] + +[[package]] +name = "zip" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0" +dependencies = [ + "crc32fast", + "indexmap", + "memchr", + "typed-path", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[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.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1a006a3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,44 @@ +[workspace] +resolver = "3" +members = ["crates/mummu", "crates/mummu-bench"] + +[workspace.package] +version = "0.1.0" +edition = "2024" +license = "MIT OR Apache-2.0" +repository = "https://github.com/physics515/Mummu" + +[workspace.dependencies] +# Intentional pins (see ROADMAP "Stack"): burn 0.21 + wgpu 29 + tokenizers 0.22 are the +# parity-validated combination proven in laurelane (byte-identical logits vs Candle). +# `fusion` + `autotune` are the throughput levers: fusion compiles streams of tensor ops +# into far fewer GPU kernels and autotune picks the fastest kernel variant; with `fusion`, +# `Wgpu` transparently becomes `Fusion`. +burn = { version = "0.21", default-features = false, features = [ + "std", + "ndarray", + "wgpu", + "fusion", + "autotune", + "store", +] } +# Direct handle on wgpu (the same version Burn resolves) for the cheap startup +# adapter probe: enumerate GPUs to decide GPU vs CPU before building a backend. +wgpu = "29" +# Block on wgpu 29's async adapter enumeration from the sync startup probe. +pollster = "0.4" +# f16 element type for the GPU backend; the same `half` version Burn pulls. +half = "2" +tokenizers = "0.22" + +serde = { version = "1", features = ["derive"] } +serde_json = "1" +thiserror = "2" +once_cell = "1" + +criterion = "0.7" + +[profile.release] +# Model inference is the hot path; fat LTO buys measurable decode throughput. +lto = "fat" +codegen-units = 1 diff --git a/ROADMAP.md b/ROADMAP.md index ec8c8d7..28df749 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -40,9 +40,11 @@ a benchmark holds/improves its budget; README perf claims link an artifact. ## Phases ### P0 — Workspace scaffold -- [ ] Cargo workspace: `crates/mummu` (the library), model code generic over `B: Backend`. `.gitignore` - (Rust); commit `Cargo.lock` for reproducible builds/benchmarks. -- [ ] `cargo build` / `test` / `clippy --all-targets` green baseline; `mummu-bench` (criterion) crate stub. +- [x] Cargo workspace: `crates/mummu` (the library), model code generic over `B: Backend`. `.gitignore` + (Rust); commit `Cargo.lock` for reproducible builds/benchmarks. *(2026-07-09) Workspace + both + crates; pinned combo burn 0.21 / wgpu 29 / tokenizers 0.22 / criterion 0.7; release profile fat-LTO.* +- [x] `cargo build` / `test` / `clippy --all-targets` green baseline; `mummu-bench` (criterion) crate stub. + *(2026-07-09) All green; criterion harness wired via a smoke bench.* ### P1 — Backends & device *(ex-laurelane)* - [ ] Backend abstraction generic over `B: Backend`; one binary compiling BOTH `Wgpu` (Vulkan/DX12/Metal, diff --git a/crates/mummu-bench/Cargo.toml b/crates/mummu-bench/Cargo.toml new file mode 100644 index 0000000..1f6b75d --- /dev/null +++ b/crates/mummu-bench/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "mummu-bench" +description = "Criterion benchmarks for Mummu: TTFT, decode tok/s, VRAM per model x device-set." +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +publish = false + +[dependencies] +mummu = { path = "../mummu" } + +[dev-dependencies] +criterion = { workspace = true } + +[[bench]] +name = "runner" +harness = false diff --git a/crates/mummu-bench/benches/runner.rs b/crates/mummu-bench/benches/runner.rs new file mode 100644 index 0000000..bd295ba --- /dev/null +++ b/crates/mummu-bench/benches/runner.rs @@ -0,0 +1,15 @@ +//! Runner benchmark stub. Real benches (TTFT, decode tok/s, VRAM per model × +//! device-set) land with the decode engine (P5); until then this measures a +//! trivial baseline so the criterion harness itself is wired and green. + +use criterion::{Criterion, criterion_group, criterion_main}; +use std::hint::black_box; + +fn bench_harness_smoke(c: &mut Criterion) { + c.bench_function("harness_smoke", |b| { + b.iter(|| black_box(1u64).wrapping_add(black_box(1u64))); + }); +} + +criterion_group!(benches, bench_harness_smoke); +criterion_main!(benches); diff --git a/crates/mummu-bench/src/lib.rs b/crates/mummu-bench/src/lib.rs new file mode 100644 index 0000000..c3efccd --- /dev/null +++ b/crates/mummu-bench/src/lib.rs @@ -0,0 +1,2 @@ +//! Benchmark support crate. The criterion benches live in `benches/`; this +//! lib intentionally stays empty so `cargo test` has nothing to run here. diff --git a/crates/mummu/Cargo.toml b/crates/mummu/Cargo.toml new file mode 100644 index 0000000..62af7ea --- /dev/null +++ b/crates/mummu/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "mummu" +description = "From-scratch Burn model runner: import any open model, quantize it to fit, run it on all the hardware you have." +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +burn = { workspace = true } +wgpu = { workspace = true } +pollster = { workspace = true } +half = { workspace = true } +tokenizers = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } +once_cell = { workspace = true } diff --git a/crates/mummu/src/lib.rs b/crates/mummu/src/lib.rs new file mode 100644 index 0000000..73d84fa --- /dev/null +++ b/crates/mummu/src/lib.rs @@ -0,0 +1,10 @@ +//! Mummu — a from-scratch [Burn](https://burn.dev) model runner. +//! +//! One library that imports open models (LLM, embeddings, later vision), +//! quantizes them to fit, and runs them natively in Rust on whatever hardware +//! is present — CPU, one GPU, or several. All model code is generic over +//! `B: Backend`; consumers (laurelane, Nanna) pick a device at runtime and +//! keep their domain glue out of this crate. + +// Burn's `fusion` feature wraps backends in deeply nested generic types. +#![recursion_limit = "512"] From 9887df802cd4714af78e0ef5eab45cf7d306772b Mon Sep 17 00:00:00 2001 From: Justin Icenhour Date: Thu, 9 Jul 2026 10:04:42 -0500 Subject: [PATCH 2/7] =?UTF-8?q?feat(p1):=20backend=20layer=20=E2=80=94=20r?= =?UTF-8?q?untime=20GPU=20probe,=20device=20inventory,=20Gpu/GpuF16/Cpu=20?= =?UTF-8?q?aliases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One binary compiles both Wgpu (Fusion, via fusion+autotune) and NdArray. A cheap adapter-enumeration probe (no device creation), cached in a OnceCell, picks GPU else CPU; the inventory records per-adapter/per-API SHADER_F16 for the P6 planner. Verified on hardware: RTX 4070 Ti SUPER (Vulkan f16=true, DX12 f16=false) + an integrated AMD adapter. 5 unit tests. Co-Authored-By: Claude Opus 4.8 --- ROADMAP.md | 14 ++- crates/mummu/src/backend.rs | 178 ++++++++++++++++++++++++++++++++++++ crates/mummu/src/lib.rs | 2 + 3 files changed, 189 insertions(+), 5 deletions(-) create mode 100644 crates/mummu/src/backend.rs diff --git a/ROADMAP.md b/ROADMAP.md index 28df749..d2a09d0 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -47,11 +47,15 @@ a benchmark holds/improves its budget; README perf claims link an artifact. *(2026-07-09) All green; criterion harness wired via a smoke bench.* ### P1 — Backends & device *(ex-laurelane)* -- [ ] Backend abstraction generic over `B: Backend`; one binary compiling BOTH `Wgpu` (Vulkan/DX12/Metal, - no CUDA toolchain) and `NdArray` (CPU). -- [ ] Runtime `use_gpu()` probe (`wgpu` adapter enumerate; `pollster::block_on` for the async probe), - cached in a `OnceCell`; pick GPU else CPU. No feature-split builds. -- [ ] `fusion` + `autotune` on (`Wgpu` becomes `Fusion`; needs `recursion_limit = 512`). +- [x] Backend abstraction generic over `B: Backend`; one binary compiling BOTH `Wgpu` (Vulkan/DX12/Metal, + no CUDA toolchain) and `NdArray` (CPU). *(2026-07-09) `backend::{Gpu, GpuF16, Cpu}` aliases; no + feature splits.* +- [x] Runtime `use_gpu()` probe (`wgpu` adapter enumerate; `pollster::block_on` for the async probe), + cached in a `OnceCell`; pick GPU else CPU. No feature-split builds. *(2026-07-09) `backend::inventory()` + also records per-adapter/per-API `SHADER_F16` for the P6 planner; on the dev box: 4070 Ti SUPER + f16=true on Vulkan, false on DX12, + an integrated AMD GPU (a real second adapter for multi-GPU).* +- [x] `fusion` + `autotune` on (`Wgpu` becomes `Fusion`; needs `recursion_limit = 512`). + *(2026-07-09) Workspace features + crate-level `recursion_limit`.* ### P2 — Model zoo (from scratch, generic over `B`) *(ex-laurelane)* - [ ] Shared blocks: RmsNorm, GQA attention, RoPE (manual rotate-half), SwiGLU MLP, tied lm-head, diff --git a/crates/mummu/src/backend.rs b/crates/mummu/src/backend.rs new file mode 100644 index 0000000..8893c3a --- /dev/null +++ b/crates/mummu/src/backend.rs @@ -0,0 +1,178 @@ +//! Backend selection: one binary compiles BOTH backends and picks at runtime. +//! +//! The model code is generic over `B: Backend`; nothing here forces a device +//! choice on a consumer. What this module owns is the *default* policy proven +//! in laurelane: enumerate GPU adapters once with a cheap `wgpu` probe (no +//! device creation), run on **wgpu (GPU via Vulkan/DX12/Metal — no CUDA +//! toolchain)** when a hardware adapter is present, else **ndarray (CPU)**. +//! No feature-split builds. +//! +//! The probe also records which adapters advertise `SHADER_F16` — the input +//! the hardware planner (P6) uses to decide whether the f16 element type is +//! viable on this machine. + +use once_cell::sync::OnceCell; + +/// GPU backend (wgpu: Vulkan / DX12 / Metal). With the workspace `fusion` +/// feature this is transparently `Fusion` — streams of tensor ops are +/// compiled into far fewer GPU kernels. +pub type Gpu = burn::backend::Wgpu; + +/// GPU backend with the f16 element type — ~2x throughput and half the VRAM +/// where `SHADER_F16` is available (check [`DeviceInventory::any_shader_f16`]). +pub type GpuF16 = burn::backend::Wgpu; + +/// CPU backend (pure-Rust ndarray). +pub type Cpu = burn::backend::NdArray; + +/// One enumerated GPU adapter, as reported by wgpu. +#[derive(Debug, Clone)] +pub struct GpuAdapter { + /// Driver-reported adapter name, e.g. `"NVIDIA GeForce RTX 4070 Ti SUPER"`. + pub name: String, + /// Graphics API carrying this adapter (`Vulkan`, `Dx12`, `Metal`, ...). + pub backend: wgpu::Backend, + /// Discrete / integrated / virtual — never [`wgpu::DeviceType::Cpu`] + /// (software rasterizers are filtered out of the inventory). + pub device_type: wgpu::DeviceType, + /// Does this adapter advertise `SHADER_F16` (native f16 shader arithmetic)? + pub shader_f16: bool, +} + +/// Every hardware GPU visible to wgpu, enumerated once per process. +#[derive(Debug, Clone, Default)] +pub struct DeviceInventory { + /// Hardware adapters across the primary graphics APIs. The same physical + /// card appears once per API that exposes it (e.g. Vulkan AND DX12) — + /// deliberate, because features like `SHADER_F16` differ per API. + pub gpus: Vec, +} + +impl DeviceInventory { + /// Is at least one hardware GPU present (on any API)? + #[must_use] + pub fn has_gpu(&self) -> bool { + !self.gpus.is_empty() + } + + /// Does any adapter advertise `SHADER_F16`? Gates the [`GpuF16`] backend. + #[must_use] + pub fn any_shader_f16(&self) -> bool { + self.gpus.iter().any(|g| g.shader_f16) + } +} + +/// Enumerate hardware adapters on `backends`. Cheap: adapter listing only, no +/// device creation. wgpu 29's enumeration is async, so block on it here. +fn enumerate(instance: &wgpu::Instance, backends: wgpu::Backends) -> Vec { + pollster::block_on(instance.enumerate_adapters(backends)) + .into_iter() + .filter_map(|adapter| { + let info = adapter.get_info(); + if matches!(info.device_type, wgpu::DeviceType::Cpu) { + return None; // software rasterizer, not a hardware GPU + } + let shader_f16 = adapter.features().contains(wgpu::Features::SHADER_F16); + Some(GpuAdapter { + name: info.name, + backend: info.backend, + device_type: info.device_type, + shader_f16, + }) + }) + .collect() +} + +/// The process-lifetime device inventory. Enumerated once (first call pays +/// ~tens of milliseconds); every later call is a cache read. +pub fn inventory() -> &'static DeviceInventory { + static INVENTORY: OnceCell = OnceCell::new(); + INVENTORY.get_or_init(|| { + let instance = wgpu::Instance::default(); + let gpus = enumerate(&instance, wgpu::Backends::PRIMARY); + let inv = DeviceInventory { gpus }; + // Positive space: every inventoried adapter is real hardware. + debug_assert!( + inv.gpus + .iter() + .all(|g| !matches!(g.device_type, wgpu::DeviceType::Cpu)), + "CPU adapters must be filtered out of the GPU inventory" + ); + inv + }) +} + +/// Default device policy: run on the GPU when a hardware adapter is present. +/// Stable for the process lifetime (backed by [`inventory`]). +#[must_use] +pub fn use_gpu() -> bool { + let gpu = inventory().has_gpu(); + // Negative space: the decision must agree with the inventory it came from. + debug_assert!(gpu == !inventory().gpus.is_empty()); + gpu +} + +/// Human label for where the default policy will run. +#[must_use] +pub fn device_label() -> &'static str { + if use_gpu() { + "GPU (wgpu)" + } else { + "CPU (ndarray)" + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn inventory_is_cached_and_consistent() { + let first = inventory(); + let second = inventory(); + // Same allocation: the OnceCell caches, never re-enumerates. + assert!(std::ptr::eq(first, second)); + assert_eq!(first.has_gpu(), !first.gpus.is_empty()); + } + + #[test] + fn no_cpu_adapters_in_inventory() { + assert!( + inventory() + .gpus + .iter() + .all(|g| !matches!(g.device_type, wgpu::DeviceType::Cpu)) + ); + } + + #[test] + fn device_label_matches_policy() { + let label = device_label(); + if use_gpu() { + assert_eq!(label, "GPU (wgpu)"); + } else { + assert_eq!(label, "CPU (ndarray)"); + } + } + + #[test] + fn f16_gate_requires_a_gpu() { + let inv = inventory(); + // Negative space: SHADER_F16 can't be advertised with no GPUs at all. + if inv.any_shader_f16() { + assert!(inv.has_gpu()); + } + } + + /// Print the inventory so the nightly log records what this machine has. + #[test] + fn report_inventory() { + for gpu in &inventory().gpus { + eprintln!( + "[mummu] {:?} / {} ({:?}): SHADER_F16 = {}", + gpu.backend, gpu.name, gpu.device_type, gpu.shader_f16 + ); + } + eprintln!("[mummu] policy: {}", device_label()); + } +} diff --git a/crates/mummu/src/lib.rs b/crates/mummu/src/lib.rs index 73d84fa..4d32d70 100644 --- a/crates/mummu/src/lib.rs +++ b/crates/mummu/src/lib.rs @@ -8,3 +8,5 @@ // Burn's `fusion` feature wraps backends in deeply nested generic types. #![recursion_limit = "512"] + +pub mod backend; From 2b6afc34d510edf8987d48a9d1ccc63290587207 Mon Sep 17 00:00:00 2001 From: Justin Icenhour Date: Thu, 9 Jul 2026 10:16:33 -0500 Subject: [PATCH 3/7] =?UTF-8?q?feat(p2):=20shared=20transformer=20blocks?= =?UTF-8?q?=20=E2=80=94=20cache-aware=20GQA,=20RoPE,=20SwiGLU,=20LIV=20sho?= =?UTF-8?q?rt-conv?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracted from laurelane's parity-validated ports, generic over B: Backend. GqaAttention covers both proven variants (Qwen2 bias / LFM2 per-head q-k RMSNorm) with a per-layer KV cache; ShortConv is LFM2's double-gated causal conv with the rolling K-1 decode state. 18 unit tests, including prefill+decode == full-forward equivalence for both cache kinds, strict causality, and RoPE offset consistency. Co-Authored-By: Claude Opus 4.8 --- ROADMAP.md | 7 +- crates/mummu/src/lib.rs | 1 + crates/mummu/src/nn/attention.rs | 370 +++++++++++++++++++++++++++++++ crates/mummu/src/nn/conv.rs | 248 +++++++++++++++++++++ crates/mummu/src/nn/mlp.rs | 115 ++++++++++ crates/mummu/src/nn/mod.rs | 22 ++ crates/mummu/src/nn/rope.rs | 153 +++++++++++++ 7 files changed, 914 insertions(+), 2 deletions(-) create mode 100644 crates/mummu/src/nn/attention.rs create mode 100644 crates/mummu/src/nn/conv.rs create mode 100644 crates/mummu/src/nn/mlp.rs create mode 100644 crates/mummu/src/nn/mod.rs create mode 100644 crates/mummu/src/nn/rope.rs diff --git a/ROADMAP.md b/ROADMAP.md index d2a09d0..f902e3e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -58,8 +58,11 @@ a benchmark holds/improves its budget; README perf claims link an artifact. *(2026-07-09) Workspace features + crate-level `recursion_limit`.* ### P2 — Model zoo (from scratch, generic over `B`) *(ex-laurelane)* -- [ ] Shared blocks: RmsNorm, GQA attention, RoPE (manual rotate-half), SwiGLU MLP, tied lm-head, - depthwise causal conv (for hybrids). +- [x] Shared blocks: RmsNorm, GQA attention, RoPE (manual rotate-half), SwiGLU MLP, tied lm-head, + depthwise causal conv (for hybrids). *(2026-07-09) `mummu::nn`: cache-aware `GqaAttention` + (optional per-head q/k RMSNorm covers Qwen2 AND LFM2), `SwiGluMlp`, `ShortConv` (LIV) with rolling + state, RoPE + causal mask; 18 unit tests incl. the prefill+decode ≡ full-forward equivalence for + both the KV cache and the conv state. RmsNorm/tied-head come from burn::nn / the model files.* - [ ] **Qwen2 / Qwen2.5** decoder (1.5B / 0.5B tiers). - [ ] **LFM2.5-1.2B** hybrid (6 GQA-attention w/ per-head q/k RMSNorm + 10 double-gated short-conv "LIV" blocks, SwiGLU, tied head, conv-state cache; ChatML, EOS `<|im_end|>`). diff --git a/crates/mummu/src/lib.rs b/crates/mummu/src/lib.rs index 4d32d70..53e5c94 100644 --- a/crates/mummu/src/lib.rs +++ b/crates/mummu/src/lib.rs @@ -10,3 +10,4 @@ #![recursion_limit = "512"] pub mod backend; +pub mod nn; diff --git a/crates/mummu/src/nn/attention.rs b/crates/mummu/src/nn/attention.rs new file mode 100644 index 0000000..8eb910d --- /dev/null +++ b/crates/mummu/src/nn/attention.rs @@ -0,0 +1,370 @@ +//! Cache-aware grouped-query attention (GQA), shared by every decoder in the +//! zoo. Covers both proven variants: plain GQA with projection bias (Qwen2) +//! and per-head q/k RMSNorm without bias (LFM2). + +use burn::module::Module; +use burn::nn::{Linear, LinearConfig, RmsNorm, RmsNormConfig}; +use burn::tensor::{Tensor, TensorData, activation, backend::Backend}; + +use super::MAX_CONTEXT_TOKENS; +use super::rope::apply_rope; + +/// Per-layer KV cache entry: cached keys and values, each `[b, nkv, seq, hd]`. +/// `None` until the layer's first forward (the prompt prefill seeds it). +pub type LayerKv = Option<(Tensor, Tensor)>; + +/// Additive causal mask `[1, 1, t, past+t]`: query row `i` (absolute position +/// `past+i`) may attend to key columns `0..=past+i`; future columns get a +/// large negative. `-1e4` (not `-inf`) so the f16 GPU path (max ~65504) never +/// overflows — `exp(-1e4)` is still exactly 0 after softmax. +pub fn causal_mask(t: usize, past: usize, device: &B::Device) -> Tensor { + assert!(t >= 1, "causal_mask: need at least one query row, got t=0"); + assert!( + past + t <= MAX_CONTEXT_TOKENS, + "causal_mask: position {past}+{t} exceeds MAX_CONTEXT_TOKENS ({MAX_CONTEXT_TOKENS})" + ); + let kcols = past + t; + let mut m = vec![0f32; t * kcols]; + for i in 0..t { + let qpos = past + i; + for j in (qpos + 1)..kcols { + m[i * kcols + j] = -1e4; + } + } + Tensor::::from_data(TensorData::new(m, [t, kcols]), device).reshape([1, 1, t, kcols]) +} + +/// GQA expand: `[b, nkv, s, hd]` → `[b, nkv*group, s, hd]`, each KV head +/// repeated `group` times contiguously (HF `repeat_kv`). +pub fn repeat_kv(x: Tensor, group: usize) -> Tensor { + assert!(group >= 1, "repeat_kv: group must be >= 1"); + if group == 1 { + return x; + } + let [b, nkv, s, hd] = x.dims(); + debug_assert!(nkv >= 1 && hd >= 1, "repeat_kv: degenerate kv shape"); + x.reshape([b, nkv, 1, s, hd]) + .repeat_dim(2, group) + .reshape([b, nkv * group, s, hd]) +} + +/// Grouped-query attention with a per-layer KV cache. Field names mirror the +/// HF checkpoint layout (`q_proj`/`k_proj`/`v_proj`/`o_proj`); architectures +/// whose checkpoints differ (LFM2's `out_proj`, `q_layernorm`) remap keys at +/// load time instead of renaming fields. +#[derive(Module, Debug)] +pub struct GqaAttention { + pub q_proj: Linear, + pub k_proj: Linear, + pub v_proj: Linear, + pub o_proj: Linear, + /// Per-head RMSNorm on q, applied post-projection at `[b, t, nh, hd]` + /// (LFM2-style). `None` for architectures without it (Qwen2). + pub q_norm: Option>, + /// Per-head RMSNorm on k — present iff `q_norm` is. + pub k_norm: Option>, +} + +/// Shape/behavior config for [`GqaAttention`]. +#[derive(Debug, Clone)] +pub struct GqaAttentionConfig { + pub hidden_size: usize, + pub num_heads: usize, + pub num_kv_heads: usize, + pub head_dim: usize, + /// Projection bias on q/k/v (Qwen2: true; LFM2: false). `o_proj` never + /// has bias in either. + pub bias: bool, + /// Per-head q/k RMSNorm (LFM2: eps of the model; Qwen2: `None`). + pub qk_norm_eps: Option, +} + +impl GqaAttentionConfig { + /// Initialize the module (random weights; real weights come from import). + pub fn init(&self, device: &B::Device) -> GqaAttention { + assert!( + self.num_kv_heads >= 1 && self.num_heads.is_multiple_of(self.num_kv_heads), + "GQA: num_heads ({}) must be a positive multiple of num_kv_heads ({})", + self.num_heads, + self.num_kv_heads + ); + assert!( + self.head_dim >= 2 && self.head_dim.is_multiple_of(2), + "GQA: head_dim must be even and >= 2 for RoPE, got {}", + self.head_dim + ); + let q_dim = self.num_heads * self.head_dim; + let kv_dim = self.num_kv_heads * self.head_dim; + let norm = |eps: f64| { + RmsNormConfig::new(self.head_dim) + .with_epsilon(eps) + .init(device) + }; + GqaAttention { + q_proj: LinearConfig::new(self.hidden_size, q_dim) + .with_bias(self.bias) + .init(device), + k_proj: LinearConfig::new(self.hidden_size, kv_dim) + .with_bias(self.bias) + .init(device), + v_proj: LinearConfig::new(self.hidden_size, kv_dim) + .with_bias(self.bias) + .init(device), + o_proj: LinearConfig::new(q_dim, self.hidden_size) + .with_bias(false) + .init(device), + q_norm: self.qk_norm_eps.map(norm), + k_norm: self.qk_norm_eps.map(norm), + } + } +} + +impl GqaAttention { + /// Cache-aware forward: RoPE the new q/k at the offset positions, append + /// the new k/v to this layer's cache, attend over the full cached range. + /// + /// `x` is `[b, t, hidden]` — the prompt at prefill (`kv == None`), a + /// single new token per decode step after. Returns `[b, t, hidden]`. + #[allow(clippy::too_many_arguments)] // mirrors the proven reference signature + pub fn forward( + &self, + x: Tensor, + num_heads: usize, + num_kv_heads: usize, + head_dim: usize, + cos: &Tensor, + sin: &Tensor, + mask: Option<&Tensor>, + kv: &mut LayerKv, + ) -> Tensor { + let [b, t, _h] = x.dims(); + let (nh, nkv, hd) = (num_heads, num_kv_heads, head_dim); + assert!( + nkv >= 1 && nh.is_multiple_of(nkv), + "GQA forward: num_heads ({nh}) must be a positive multiple of num_kv_heads ({nkv})" + ); + debug_assert!( + self.q_norm.is_some() == self.k_norm.is_some(), + "GQA forward: q_norm and k_norm must be both present or both absent" + ); + + // Per-head q/k RMSNorm (when present) applies post-projection, before + // transpose + RoPE — the LFM2 ordering, validated against Ollama. + let q = self.q_proj.forward(x.clone()).reshape([b, t, nh, hd]); + let q = match &self.q_norm { + Some(norm) => norm.forward(q), + None => q, + } + .swap_dims(1, 2); + let k_new = self.k_proj.forward(x.clone()).reshape([b, t, nkv, hd]); + let k_new = match &self.k_norm { + Some(norm) => norm.forward(k_new), + None => k_new, + } + .swap_dims(1, 2); + let v_new = self + .v_proj + .forward(x) + .reshape([b, t, nkv, hd]) + .swap_dims(1, 2); + + let q = apply_rope(q, cos, sin); + let k_new = apply_rope(k_new, cos, sin); + + // Append to (or seed) the cache, then attend over everything so far. + let (k_all, v_all) = match kv.take() { + Some((pk, pv)) => ( + Tensor::cat(vec![pk, k_new], 2), + Tensor::cat(vec![pv, v_new], 2), + ), + None => (k_new, v_new), + }; + *kv = Some((k_all.clone(), v_all.clone())); + + let group = nh / nkv; + let k = repeat_kv(k_all, group); + let v = repeat_kv(v_all, group); + + let scale = 1.0 / (hd as f32).sqrt(); + let mut scores = q.matmul(k.swap_dims(2, 3)).mul_scalar(scale); + if let Some(m) = mask { + scores = scores.add(m.clone()); + } + let probs = activation::softmax(scores, 3); + let ctx = probs.matmul(v).swap_dims(1, 2).reshape([b, t, nh * hd]); + self.o_proj.forward(ctx) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::backend::Cpu; + use crate::nn::rope::rope_tables; + + type Dev = burn::tensor::Device; + + const HIDDEN: usize = 16; + const HEADS: usize = 4; + const KV_HEADS: usize = 2; + const HEAD_DIM: usize = 4; + const THETA: f32 = 1e4; + + fn attn(qk_norm: bool, device: &Dev) -> GqaAttention { + GqaAttentionConfig { + hidden_size: HIDDEN, + num_heads: HEADS, + num_kv_heads: KV_HEADS, + head_dim: HEAD_DIM, + bias: true, + qk_norm_eps: qk_norm.then_some(1e-5), + } + .init(device) + } + + /// Deterministic pseudo-random input `[1, t, HIDDEN]`. + fn input(t: usize, seed: f32, device: &Dev) -> Tensor { + let data: Vec = (0..t * HIDDEN) + .map(|i| ((i as f32 + seed) * 0.7).sin()) + .collect(); + Tensor::::from_data(TensorData::new(data, [t, HIDDEN]), device) + .reshape([1, t, HIDDEN]) + } + + /// Full forward over `t` positions in one call (prefill-style). + fn full_forward(a: &GqaAttention, x: Tensor, device: &Dev) -> Vec { + let t = x.dims()[1]; + let (cos, sin) = rope_tables::(t, 0, HEAD_DIM, THETA, device); + let mask = causal_mask::(t, 0, device); + let mut kv: LayerKv = None; + a.forward( + x, + HEADS, + KV_HEADS, + HEAD_DIM, + &cos, + &sin, + Some(&mask), + &mut kv, + ) + .into_data() + .to_vec::() + .unwrap() + } + + #[test] + fn causal_mask_is_strictly_causal() { + let device = Dev::default(); + let m = causal_mask::(3, 2, &device); + assert_eq!(m.dims(), [1, 1, 3, 5]); + let v = m.into_data().to_vec::().unwrap(); + for i in 0..3 { + for j in 0..5 { + let expect = if j > 2 + i { -1e4 } else { 0.0 }; + assert_eq!(v[i * 5 + j], expect, "row {i} col {j}"); + } + } + // A single decode step attends to everything cached: all zeros. + let one = causal_mask::(1, 4, &device); + assert!( + one.into_data() + .to_vec::() + .unwrap() + .iter() + .all(|&x| x == 0.0) + ); + } + + #[test] + fn repeat_kv_repeats_each_head_contiguously() { + let device = Dev::default(); + let x = Tensor::::from_floats([1.0, 2.0, 3.0, 4.0], &device).reshape([1, 2, 1, 2]); // 2 kv heads, hd=2 + let y = repeat_kv(x, 2); // -> 4 heads + assert_eq!(y.dims(), [1, 4, 1, 2]); + assert_eq!( + y.into_data().to_vec::().unwrap(), + vec![1.0, 2.0, 1.0, 2.0, 3.0, 4.0, 3.0, 4.0] + ); + } + + /// The load-bearing invariant: prefill + one-token-at-a-time decode + /// through the KV cache must equal one full forward over the same tokens. + #[test] + fn kv_cache_decode_matches_full_forward() { + let device = Dev::default(); + for qk_norm in [false, true] { + let a = attn(qk_norm, &device); + let x = input(6, 3.0, &device); + + // Reference: all 6 positions in one causal forward; keep the last row. + let full = full_forward(&a, x.clone(), &device); + let last_full = &full[5 * HIDDEN..]; + + // Cached: prefill 5, then decode position 5 alone. + let mut kv: LayerKv = None; + let prefill = x.clone().narrow(1, 0, 5); + let (cos, sin) = rope_tables::(5, 0, HEAD_DIM, THETA, &device); + let mask = causal_mask::(5, 0, &device); + let _ = a.forward( + prefill, + HEADS, + KV_HEADS, + HEAD_DIM, + &cos, + &sin, + Some(&mask), + &mut kv, + ); + + let step = x.narrow(1, 5, 1); + let (cos1, sin1) = rope_tables::(1, 5, HEAD_DIM, THETA, &device); + let out = a + .forward(step, HEADS, KV_HEADS, HEAD_DIM, &cos1, &sin1, None, &mut kv) + .into_data() + .to_vec::() + .unwrap(); + + for (i, (c, f)) in out.iter().zip(last_full).enumerate() { + assert!( + (c - f).abs() < 1e-4, + "qk_norm={qk_norm} elem {i}: cached {c} vs full {f}" + ); + } + } + } + + /// Causality: changing a future token must not change an earlier output row. + #[test] + fn future_tokens_cannot_affect_past_outputs() { + let device = Dev::default(); + let a = attn(false, &device); + let x1 = input(4, 1.0, &device); + // Same first 3 tokens, different 4th. + let x2 = Tensor::cat(vec![x1.clone().narrow(1, 0, 3), input(1, 99.0, &device)], 1); + let (o1, o2) = (full_forward(&a, x1, &device), full_forward(&a, x2, &device)); + // Rows 0..3 identical; row 3 differs. + for i in 0..3 * HIDDEN { + assert!((o1[i] - o2[i]).abs() < 1e-6, "past row changed at {i}"); + } + let last_differs = o1[3 * HIDDEN..] + .iter() + .zip(&o2[3 * HIDDEN..]) + .any(|(a, b)| (a - b).abs() > 1e-6); + assert!(last_differs, "the changed token's own row should differ"); + } + + #[test] + #[should_panic(expected = "must be a positive multiple")] + fn init_rejects_indivisible_head_grouping() { + let device = Dev::default(); + let _ = GqaAttentionConfig { + hidden_size: HIDDEN, + num_heads: 3, + num_kv_heads: 2, + head_dim: HEAD_DIM, + bias: false, + qk_norm_eps: None, + } + .init::(&device); + } +} diff --git a/crates/mummu/src/nn/conv.rs b/crates/mummu/src/nn/conv.rs new file mode 100644 index 0000000..3bc4d33 --- /dev/null +++ b/crates/mummu/src/nn/conv.rs @@ -0,0 +1,248 @@ +//! LFM2's double-gated causal short-convolution ("LIV") operator: +//! `in_proj` → split B/C/x → `B*x` → causal depthwise conv1d → `C*conv` → +//! `out_proj`, with a rolling `K-1` state so decode steps cost O(K) instead +//! of re-forwarding the sequence. The decode path was verified algebraically +//! equivalent to the padded conv in laurelane (greedy parity vs Ollama). + +use burn::module::Module; +use burn::nn::conv::{Conv1d, Conv1dConfig}; +use burn::nn::{Linear, LinearConfig, PaddingConfig1d}; +use burn::tensor::{Tensor, backend::Backend}; + +/// Rolling decode state: the last `K-1` gated inputs `[b, d, K-1]`, `None` +/// until the first forward. +pub type ConvState = Option>; + +/// Double-gated causal short conv (LFM2 "LIV" block). Field names mirror the +/// HF checkpoint (`in_proj`/`conv`/`out_proj`). +#[derive(Module, Debug)] +pub struct ShortConv { + pub in_proj: Linear, + pub conv: Conv1d, + pub out_proj: Linear, +} + +/// Shape config for [`ShortConv`]. +#[derive(Debug, Clone)] +pub struct ShortConvConfig { + pub hidden_size: usize, + /// Conv kernel length `K` (LFM2's `conv_L_cache`). + pub kernel_len: usize, +} + +impl ShortConvConfig { + /// Initialize the module (random weights; real weights come from import). + pub fn init(&self, device: &B::Device) -> ShortConv { + let (d, k) = (self.hidden_size, self.kernel_len); + assert!(d >= 1, "ShortConv: hidden_size must be >= 1"); + assert!( + k >= 2, + "ShortConv: kernel_len must be >= 2 (a 1-tap conv is a no-op gate)" + ); + ShortConv { + in_proj: LinearConfig::new(d, 3 * d).with_bias(false).init(device), + conv: Conv1dConfig::new(d, d, k) + .with_groups(d) + .with_padding(PaddingConfig1d::Explicit(k - 1, k - 1)) + .with_bias(false) + .init(device), + out_proj: LinearConfig::new(d, d).with_bias(false).init(device), + } + } +} + +impl ShortConv { + /// Cache-aware forward. `x` is `[b, t, d]`: the whole prompt at prefill, + /// one token per decode step after. Rolls `state` forward either way. + pub fn forward( + &self, + x: Tensor, + kernel_len: usize, + state: &mut ConvState, + ) -> Tensor { + let [b, t, d] = x.dims(); + let kk = kernel_len; + assert!(kk >= 2, "ShortConv forward: kernel_len must be >= 2"); + assert!(t >= 1, "ShortConv forward: need at least one position"); + debug_assert!( + state.as_ref().is_none_or(|s| s.dims() == [b, d, kk - 1]), + "ShortConv forward: stale state shape" + ); + + let bcx = self.in_proj.forward(x); // [b, t, 3d] + let bb = bcx.clone().narrow(2, 0, d); + let cc = bcx.clone().narrow(2, d, d); + let xx = bcx.narrow(2, 2 * d, d); + let bx = bb.mul(xx).swap_dims(1, 2); // input gate, channel-major [b, d, t] + + let conv_out = if t > 1 { + // Prefill: the padded depthwise Conv1d gives the full causal output. + self.conv.forward(bx.clone()).narrow(2, 0, t) // [b, d, t] + } else { + // Decode: weighted sum over the last K inputs = [cached (K-1), new (1)]. + // Equivalent to the padded conv at the new position. + let window = match state { + Some(prev) => Tensor::cat(vec![prev.clone(), bx.clone()], 2), // [b, d, K] + None => { + let pad = Tensor::::zeros([b, d, kk - 1], &bx.device()); + Tensor::cat(vec![pad, bx.clone()], 2) + } + }; + let w = self.conv.weight.val().reshape([1, d, kk]); // depthwise kernel + window.mul(w).sum_dim(2) // [b, d, 1] + }; + + // Roll the state forward: keep the last (K-1) gated inputs. + let new_state = { + let combined = match state { + Some(prev) => Tensor::cat(vec![prev.clone(), bx.clone()], 2), + None => bx.clone(), + }; + let len = combined.dims()[2]; + if len >= kk - 1 { + combined.narrow(2, len - (kk - 1), kk - 1) + } else { + let pad = Tensor::::zeros([b, d, (kk - 1) - len], &combined.device()); + Tensor::cat(vec![pad, combined], 2) + } + }; + *state = Some(new_state); + + let conv_out = conv_out.swap_dims(1, 2); // [b, t, d] + self.out_proj.forward(cc.mul(conv_out)) // output gate + proj + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::backend::Cpu; + use burn::tensor::TensorData; + + type Dev = burn::tensor::Device; + + const D: usize = 6; + const K: usize = 3; + + fn conv(device: &Dev) -> ShortConv { + ShortConvConfig { + hidden_size: D, + kernel_len: K, + } + .init(device) + } + + fn input(t: usize, seed: f32, device: &Dev) -> Tensor { + let data: Vec = (0..t * D) + .map(|i| ((i as f32 + seed) * 0.9).cos()) + .collect(); + Tensor::::from_data(TensorData::new(data, [t, D]), device).reshape([1, t, D]) + } + + /// The load-bearing invariant: prefill + one-token-at-a-time decode via + /// the rolling state must equal one full prefill over the same tokens. + #[test] + fn rolling_state_decode_matches_full_prefill() { + let device = Dev::default(); + let c = conv(&device); + let x = input(7, 5.0, &device); + + // Reference: all 7 positions through the padded conv. + let mut ref_state: ConvState = None; + let full = c + .forward(x.clone(), K, &mut ref_state) + .into_data() + .to_vec::() + .unwrap(); + + // Cached: prefill 4, then decode 5th..7th one at a time. + let mut state: ConvState = None; + let _ = c.forward(x.clone().narrow(1, 0, 4), K, &mut state); + for pos in 4..7 { + let out = c + .forward(x.clone().narrow(1, pos, 1), K, &mut state) + .into_data() + .to_vec::() + .unwrap(); + let expect = &full[pos * D..(pos + 1) * D]; + for (i, (got, want)) in out.iter().zip(expect).enumerate() { + assert!( + (got - want).abs() < 1e-5, + "pos {pos} elem {i}: cached {got} vs full {want}" + ); + } + } + } + + /// The conv is causal: a future token cannot change an earlier output. + #[test] + fn conv_is_causal() { + let device = Dev::default(); + let c = conv(&device); + let x1 = input(5, 1.0, &device); + let x2 = Tensor::cat(vec![x1.clone().narrow(1, 0, 4), input(1, 77.0, &device)], 1); + let mut s1: ConvState = None; + let mut s2: ConvState = None; + let o1 = c + .forward(x1, K, &mut s1) + .into_data() + .to_vec::() + .unwrap(); + let o2 = c + .forward(x2, K, &mut s2) + .into_data() + .to_vec::() + .unwrap(); + for i in 0..4 * D { + assert!((o1[i] - o2[i]).abs() < 1e-6, "past row changed at {i}"); + } + } + + /// Decode from a fresh (None) state equals a length-1 prefill: the + /// zero-pad seeding path. + #[test] + fn first_decode_step_seeds_state_like_prefill() { + let device = Dev::default(); + let c = conv(&device); + let x = input(1, 2.0, &device); + + let mut s_decode: ConvState = None; + let via_decode = c + .forward(x.clone(), K, &mut s_decode) + .into_data() + .to_vec::() + .unwrap(); + + // Same single token inside a longer prefill whose first position it is. + let longer = Tensor::cat(vec![x, input(2, 50.0, &device)], 1); + let mut s_pre: ConvState = None; + let via_prefill = c + .forward(longer, K, &mut s_pre) + .into_data() + .to_vec::() + .unwrap(); + + for i in 0..D { + assert!( + (via_decode[i] - via_prefill[i]).abs() < 1e-5, + "elem {i}: decode {} vs prefill {}", + via_decode[i], + via_prefill[i] + ); + } + // State must exist and have the rolling shape after either path. + assert_eq!(s_decode.unwrap().dims(), [1, D, K - 1]); + assert_eq!(s_pre.unwrap().dims(), [1, D, K - 1]); + } + + #[test] + #[should_panic(expected = "kernel_len must be >= 2")] + fn init_rejects_degenerate_kernel() { + let device = Dev::default(); + let _ = ShortConvConfig { + hidden_size: D, + kernel_len: 1, + } + .init::(&device); + } +} diff --git a/crates/mummu/src/nn/mlp.rs b/crates/mummu/src/nn/mlp.rs new file mode 100644 index 0000000..f251523 --- /dev/null +++ b/crates/mummu/src/nn/mlp.rs @@ -0,0 +1,115 @@ +//! SwiGLU feed-forward block: `down(silu(gate(x)) * up(x))`. Field names +//! mirror the HF Qwen2 layout (`gate_proj`/`up_proj`/`down_proj`); LFM2's +//! `w1`/`w3`/`w2` remap onto these at load time. + +use burn::module::Module; +use burn::nn::{Linear, LinearConfig}; +use burn::tensor::{Tensor, activation, backend::Backend}; + +/// SwiGLU MLP, no biases (both proven architectures ship it bias-free). +#[derive(Module, Debug)] +pub struct SwiGluMlp { + /// SiLU branch (LFM2: `w1`). + pub gate_proj: Linear, + /// Multiplicative branch (LFM2: `w3`). + pub up_proj: Linear, + /// Projection back to the model width (LFM2: `w2`). + pub down_proj: Linear, +} + +/// Shape config for [`SwiGluMlp`]. +#[derive(Debug, Clone)] +pub struct SwiGluMlpConfig { + pub hidden_size: usize, + pub intermediate_size: usize, +} + +impl SwiGluMlpConfig { + /// Initialize the module (random weights; real weights come from import). + pub fn init(&self, device: &B::Device) -> SwiGluMlp { + assert!(self.hidden_size >= 1, "SwiGLU: hidden_size must be >= 1"); + assert!( + self.intermediate_size >= 1, + "SwiGLU: intermediate_size must be >= 1" + ); + SwiGluMlp { + gate_proj: LinearConfig::new(self.hidden_size, self.intermediate_size) + .with_bias(false) + .init(device), + up_proj: LinearConfig::new(self.hidden_size, self.intermediate_size) + .with_bias(false) + .init(device), + down_proj: LinearConfig::new(self.intermediate_size, self.hidden_size) + .with_bias(false) + .init(device), + } + } +} + +impl SwiGluMlp { + /// `[b, t, hidden]` → `[b, t, hidden]`. + pub fn forward(&self, x: Tensor) -> Tensor { + let gate = activation::silu(self.gate_proj.forward(x.clone())); + let up = self.up_proj.forward(x); + self.down_proj.forward(gate.mul(up)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::backend::Cpu; + use burn::tensor::TensorData; + + type Dev = burn::tensor::Device; + + #[test] + fn forward_preserves_shape() { + let device = Dev::default(); + let mlp = SwiGluMlpConfig { + hidden_size: 8, + intermediate_size: 20, + } + .init::(&device); + let x = Tensor::::zeros([2, 3, 8], &device); + assert_eq!(mlp.forward(x).dims(), [2, 3, 8]); + } + + #[test] + fn zero_input_gives_zero_output_without_biases() { + let device = Dev::default(); + let mlp = SwiGluMlpConfig { + hidden_size: 4, + intermediate_size: 8, + } + .init::(&device); + let x = Tensor::::zeros([1, 2, 4], &device); + let out = mlp.forward(x).into_data().to_vec::().unwrap(); + assert!( + out.iter().all(|&v| v == 0.0), + "bias-free SwiGLU must map 0 to 0" + ); + } + + #[test] + fn forward_is_position_independent() { + // An MLP acts per-position: the same row through a [1,1,h] and a + // [1,2,h] batch must give identical outputs. + let device = Dev::default(); + let mlp = SwiGluMlpConfig { + hidden_size: 4, + intermediate_size: 8, + } + .init::(&device); + let row: Vec = vec![0.3, -1.2, 0.8, 2.0]; + let single = Tensor::::from_data(TensorData::new(row.clone(), [4]), &device) + .reshape([1, 1, 4]); + let double = + Tensor::::from_data(TensorData::new([row.clone(), row].concat(), [8]), &device) + .reshape([1, 2, 4]); + let s = mlp.forward(single).into_data().to_vec::().unwrap(); + let d = mlp.forward(double).into_data().to_vec::().unwrap(); + assert_eq!(s.as_slice(), &d[..4]); + assert_eq!(s.as_slice(), &d[4..]); + } +} diff --git a/crates/mummu/src/nn/mod.rs b/crates/mummu/src/nn/mod.rs new file mode 100644 index 0000000..0f606a7 --- /dev/null +++ b/crates/mummu/src/nn/mod.rs @@ -0,0 +1,22 @@ +//! Shared transformer blocks, generic over `B: Backend`. +//! +//! Extracted from laurelane's parity-validated model ports (Qwen2 and the +//! LFM2.5 hybrid — byte-identical single-forward logits and greedy sequences +//! vs Candle / Ollama references). Every model in the zoo composes these; the +//! per-model files own only architecture wiring and weight-key remaps. + +mod attention; +mod conv; +mod mlp; +mod rope; + +pub use attention::{GqaAttention, GqaAttentionConfig, LayerKv, causal_mask, repeat_kv}; +pub use conv::{ConvState, ShortConv, ShortConvConfig}; +pub use mlp::{SwiGluMlp, SwiGluMlpConfig}; +pub use rope::{apply_rope, rope_tables, rotate_half}; + +/// Hard ceiling on `past + t` everywhere a sequence position is materialized. +/// Nothing in the zoo has a longer trained context; a position beyond this is +/// a caller bug (e.g. a decode loop that forgot its stop condition), not a +/// workload. +pub const MAX_CONTEXT_TOKENS: usize = 131_072; diff --git a/crates/mummu/src/nn/rope.rs b/crates/mummu/src/nn/rope.rs new file mode 100644 index 0000000..42b9b56 --- /dev/null +++ b/crates/mummu/src/nn/rope.rs @@ -0,0 +1,153 @@ +//! Rotary position embeddings (RoPE), HF duplicated-half layout, computed +//! manually so the same tables serve every architecture (Qwen theta 1e6 vs +//! LFM2 theta 1e6 vs others) and every backend. + +use burn::tensor::{Tensor, TensorData, backend::Backend}; + +use super::MAX_CONTEXT_TOKENS; + +/// RoPE cos/sin tables `[1, 1, t, head_dim]` for absolute positions +/// `past..past+t`, HF duplicated-half layout (each frequency written to both +/// halves of the head dim, so [`apply_rope`]'s rotate-half math lines up). +pub fn rope_tables( + t: usize, + past: usize, + head_dim: usize, + theta: f32, + device: &B::Device, +) -> (Tensor, Tensor) { + assert!(t >= 1, "rope_tables: need at least one position, got t=0"); + assert!( + head_dim >= 2 && head_dim.is_multiple_of(2), + "rope_tables: head_dim must be even and >= 2, got {head_dim}" + ); + assert!( + past + t <= MAX_CONTEXT_TOKENS, + "rope_tables: position {past}+{t} exceeds MAX_CONTEXT_TOKENS ({MAX_CONTEXT_TOKENS})" + ); + debug_assert!(theta > 0.0, "rope_tables: theta must be positive"); + + let half = head_dim / 2; + let mut cos = vec![0f32; t * head_dim]; + let mut sin = vec![0f32; t * head_dim]; + for i in 0..t { + let pos = (past + i) as f32; + for k in 0..half { + let inv = 1.0f32 / theta.powf(2.0 * k as f32 / head_dim as f32); + let (s, c) = (pos * inv).sin_cos(); + cos[i * head_dim + k] = c; + cos[i * head_dim + k + half] = c; + sin[i * head_dim + k] = s; + sin[i * head_dim + k + half] = s; + } + } + let cos = Tensor::::from_data(TensorData::new(cos, [t, head_dim]), device) + .reshape([1, 1, t, head_dim]); + let sin = Tensor::::from_data(TensorData::new(sin, [t, head_dim]), device) + .reshape([1, 1, t, head_dim]); + (cos, sin) +} + +/// `rotate_half`: split the last dim in two, return `cat([-x2, x1])`. +pub fn rotate_half(x: Tensor) -> Tensor { + let dims = x.dims(); + let half = dims[3] / 2; + assert!( + dims[3].is_multiple_of(2) && half >= 1, + "rotate_half: last dim must be even and >= 2, got {}", + dims[3] + ); + let x1 = x.clone().narrow(3, 0, half); + let x2 = x.narrow(3, half, half); + Tensor::cat(vec![x2.neg(), x1], 3) +} + +/// Apply RoPE: `x*cos + rotate_half(x)*sin`. `cos`/`sin` come from +/// [`rope_tables`] and must cover the same `t` and `head_dim` as `x`. +pub fn apply_rope( + x: Tensor, + cos: &Tensor, + sin: &Tensor, +) -> Tensor { + let (xd, cd) = (x.dims(), cos.dims()); + assert!( + xd[2] == cd[2] && xd[3] == cd[3], + "apply_rope: x {xd:?} and tables {cd:?} disagree on [t, head_dim]" + ); + debug_assert!( + cos.dims() == sin.dims(), + "apply_rope: cos/sin shape mismatch" + ); + let rh = rotate_half(x.clone()); + x.mul(cos.clone()).add(rh.mul(sin.clone())) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::backend::Cpu; + + type Dev = burn::tensor::Device; + + fn to_vec(t: Tensor) -> Vec { + t.into_data().to_vec::().unwrap() + } + + #[test] + fn rope_tables_position_zero_is_identity_rotation() { + let device = Dev::default(); + let (cos, sin) = rope_tables::(1, 0, 8, 1e6, &device); + assert!(to_vec(cos).iter().all(|&c| (c - 1.0).abs() < 1e-7)); + assert!(to_vec(sin).iter().all(|&s| s.abs() < 1e-7)); + } + + #[test] + fn rope_tables_are_unit_norm_and_offset_consistent() { + let device = Dev::default(); + // cos^2 + sin^2 == 1 everywhere. + let (cos, sin) = rope_tables::(3, 2, 16, 1e4, &device); + let (c, s) = (to_vec(cos), to_vec(sin)); + for (ci, si) in c.iter().zip(&s) { + assert!((ci * ci + si * si - 1.0).abs() < 1e-5); + } + // Row for absolute position 4 must match whether reached via past=2+i=2 + // (prefill) or past=4,t=1 (decode) — the KV-cache offset invariant. + let (cos_dec, sin_dec) = rope_tables::(1, 4, 16, 1e4, &device); + let (cd, sd) = (to_vec(cos_dec), to_vec(sin_dec)); + assert_eq!(&c[2 * 16..3 * 16], &cd[..]); + assert_eq!(&s[2 * 16..3 * 16], &sd[..]); + } + + #[test] + fn rotate_half_swaps_and_negates() { + let device = Dev::default(); + let x = Tensor::::from_floats([1.0, 2.0, 3.0, 4.0], &device).reshape([1, 1, 1, 4]); + assert_eq!(to_vec(rotate_half(x)), vec![-3.0, -4.0, 1.0, 2.0]); + } + + #[test] + fn apply_rope_at_position_zero_is_identity() { + let device = Dev::default(); + let x = Tensor::::from_floats([0.5, -1.5, 2.0, 3.5], &device).reshape([1, 1, 1, 4]); + let (cos, sin) = rope_tables::(1, 0, 4, 1e6, &device); + let y = apply_rope(x.clone(), &cos, &sin); + let (xv, yv) = (to_vec(x), to_vec(y)); + for (a, b) in xv.iter().zip(&yv) { + assert!((a - b).abs() < 1e-6); + } + } + + #[test] + #[should_panic(expected = "head_dim must be even")] + fn rope_tables_rejects_odd_head_dim() { + let device = Dev::default(); + let _ = rope_tables::(1, 0, 7, 1e6, &device); + } + + #[test] + #[should_panic(expected = "MAX_CONTEXT_TOKENS")] + fn rope_tables_rejects_runaway_positions() { + let device = Dev::default(); + let _ = rope_tables::(1, MAX_CONTEXT_TOKENS, 8, 1e6, &device); + } +} From 0aa5dc331159ad23cf20b092dd93790a5dc91ebd Mon Sep 17 00:00:00 2001 From: Justin Icenhour Date: Thu, 9 Jul 2026 11:02:09 -0500 Subject: [PATCH 4/7] feat(p2,p3,p5,p8): Qwen2 + LFM2.5 ports, checked safetensors import, decode utils, disk accounting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two decoders on the shared nn blocks, config-driven and generic over B: Backend: - models::qwen2 — verified on real weights: Qwen2.5-1.5B (3.1 GB bf16 safetensors) loads with zero missing keys and greedy-decodes '2+2 equals 4.' on the wgpu GPU (top-5 probe led by id 9707 'Hello'); toy cache-equivalence test. - models::lfm2 — the hybrid conv+attention 1.2B; real GPU decode produced a correct primes list; greedy parity test vs Ollama written but the local lfm2.5 tag now resolves to the 8.5B MoE (different weights), so the P7 gate stays open. - import — CastFloatAdapter (bf16→backend float) + load_checked (fail-loud, never silently zero-init) + typed ImportError. - decode — on-device argmax + top-k probe. manage — P8 disk usage + traversal-safe removal (ported with tests). 42 unit tests green; clippy clean. Co-Authored-By: Claude Opus 4.8 --- README.md | 15 + ROADMAP.md | 59 +++- crates/mummu/src/decode.rs | 50 +++ crates/mummu/src/import.rs | 143 +++++++++ crates/mummu/src/lib.rs | 4 + crates/mummu/src/manage.rs | 222 ++++++++++++++ crates/mummu/src/models/lfm2.rs | 436 +++++++++++++++++++++++++++ crates/mummu/src/models/mod.rs | 6 + crates/mummu/src/models/qwen2.rs | 401 ++++++++++++++++++++++++ crates/mummu/tests/parity_lfm2.rs | 107 +++++++ crates/mummu/tests/real_inference.rs | 104 +++++++ 11 files changed, 1539 insertions(+), 8 deletions(-) create mode 100644 crates/mummu/src/decode.rs create mode 100644 crates/mummu/src/import.rs create mode 100644 crates/mummu/src/manage.rs create mode 100644 crates/mummu/src/models/lfm2.rs create mode 100644 crates/mummu/src/models/mod.rs create mode 100644 crates/mummu/src/models/qwen2.rs create mode 100644 crates/mummu/tests/parity_lfm2.rs create mode 100644 crates/mummu/tests/real_inference.rs diff --git a/README.md b/README.md index 59ae20e..1bbce88 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,21 @@ It exists because two local-first apps — **[laurelane](https://github.com/phys - **Quantize to fit, fill the hardware** — a planner probes every GPU + the CPU (VRAM / RAM), then imports or **quantizes on the fly** (GGUF K-quants, GPTQ / AWQ, or Burn's own int8/int4) and chooses precision + **layer placement** so the *largest model that fits* runs and every device is used — sharded across GPUs, spilling cold layers to CPU when needed. Plus a model-management API (download progress, disk usage, remove) apps surface in their settings UI. - **Local embeddings** — a from-scratch MiniLM-class sentence embedder (CPU) for fully-offline semantic search. +## Status — what runs today + +- **Workspace + backends** — `crates/mummu` (library) + `crates/mummu-bench` (criterion); one binary + compiles both `Wgpu` (with `fusion` + `autotune`) and `NdArray`, with a cached runtime GPU probe and a + device inventory that records per-adapter/per-API `SHADER_F16`. +- **Shared blocks, generic over `B: Backend`** — cache-aware GQA attention (optional per-head q/k + RMSNorm), manual RoPE, SwiGLU, and LFM2's double-gated causal short-conv with rolling decode state; + unit tests prove prefill+decode ≡ full-forward for both cache kinds. +- **Checked safetensors import** — bf16→backend-float cast adapter, per-architecture key remaps, and a + fail-loud load (never silently zero-init); `config.json`-driven hyperparameters. +- **Two decoders ported and running on real weights** — Qwen2/2.5 and the LFM2/2.5 hybrid; Qwen2.5-1.5B + loads and greedy-decodes correctly on the reference GPU (wgpu/Vulkan). The P7 parity harness will gate + them "trusted" against a reference before the roadmap ticks them done. +- **Model-cache disk accounting** — per-model disk usage + traversal-safe removal validation (`manage`). + ## Design principles - **Local-first, offline, private** — your own hardware is the whole story; the cloud is never a dependency. diff --git a/ROADMAP.md b/ROADMAP.md index f902e3e..9e78943 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -35,7 +35,11 @@ TTFT while fitting the *target device set's* VRAM/RAM (one GPU, several, or CPU) that fits, run as fast as the hardware allows, every device busy. A change ships only when parity holds and a benchmark holds/improves its budget; README perf claims link an artifact. - [ ] `mummu-bench` (criterion) — TTFT, decode tok/s, VRAM per model × device-set (1 GPU / N GPUs / CPU / hybrid). + *(2026-07-09) Crate + harness wired (smoke bench); real model benches land with P5.* - [ ] `bench/BASELINE.md` budgets (VRAM ceiling, min decode tok/s, max TTFT) + a `cargo test` gate that fails a regression. +- [ ] Evaluate Burn 0.21's `burn.toml` project config — per-subsystem tuning + a CubeCL kernel-validation + layer without recompiling; useful as a debug switch for kernel-level parity hunts — + https://burn.dev/blog/release-0.21.0/ ## Phases @@ -56,6 +60,10 @@ a benchmark holds/improves its budget; README perf claims link an artifact. f16=true on Vulkan, false on DX12, + an integrated AMD GPU (a real second adapter for multi-GPU).* - [x] `fusion` + `autotune` on (`Wgpu` becomes `Fusion`; needs `recursion_limit = 512`). *(2026-07-09) Workspace features + crate-level `recursion_limit`.* +- [ ] Evaluate **burn-flex** (Burn 0.21's new pure-Rust CPU backend; `burn-ndarray` is now on a + deprecation path) as the `Cpu` alias replacement — SIMD + gemm, no_std, and built-in per-tensor/ + per-block quantization (~40 quantized ops) that P9 could ride on. Gate on parity + a CPU decode + bench — https://github.com/antimora/burn-flex · https://burn.dev/blog/release-0.21.0/ ### P2 — Model zoo (from scratch, generic over `B`) *(ex-laurelane)* - [x] Shared blocks: RmsNorm, GQA attention, RoPE (manual rotate-half), SwiGLU MLP, tied lm-head, @@ -63,9 +71,18 @@ a benchmark holds/improves its budget; README perf claims link an artifact. (optional per-head q/k RMSNorm covers Qwen2 AND LFM2), `SwiGluMlp`, `ShortConv` (LIV) with rolling state, RoPE + causal mask; 18 unit tests incl. the prefill+decode ≡ full-forward equivalence for both the KV cache and the conv state. RmsNorm/tied-head come from burn::nn / the model files.* -- [ ] **Qwen2 / Qwen2.5** decoder (1.5B / 0.5B tiers). +- [ ] **Qwen2 / Qwen2.5** decoder (1.5B / 0.5B tiers). *(2026-07-09) Ported (`models::qwen2`, + config-driven, checked safetensors load); REAL GPU inference verified — Qwen2.5-1.5B on the 4070 Ti + SUPER greedy-decoded "2+2 equals 4.", top-5 probe led by id 9707 "Hello"; toy-model cache-equivalence + unit test. Stays `[ ]` until the P7 parity harness passes it against a logits reference.* - [ ] **LFM2.5-1.2B** hybrid (6 GQA-attention w/ per-head q/k RMSNorm + 10 double-gated short-conv "LIV" - blocks, SwiGLU, tied head, conv-state cache; ChatML, EOS `<|im_end|>`). + blocks, SwiGLU, tied head, conv-state cache; ChatML, EOS `<|im_end|>`). *(2026-07-09) Ported + (`models::lfm2`, hybrid cache, LFM2→shared-block key remap); toy hybrid cache-equivalence test; + greedy-vs-Ollama parity test written (`tests/parity_lfm2.rs`). REAL GPU inference verified — the + 1.2B greedy-decoded a correct, coherent primes list on the 4070 Ti SUPER. Stays `[ ]` until the + gate passes: the local `ollama lfm2.5:latest` tag now resolves to the 8.5B **MoE** Q4 w/ thinking + (verified via `ollama show` 2026-07-09) — not the same weights, so no valid local reference exists; + see the P7 reference item.* - [ ] **all-MiniLM** BERT sentence-embedder (6-layer post-LN bidirectional attention + GeLU FFN, masked-mean-pool + L2-normalize). - [ ] A `Model` trait so new architectures (Hermes-class function-callers, Gemma, Qwen3, …) slot in. @@ -77,7 +94,9 @@ The subsystem that turns "a model on HuggingFace or on disk" into a loaded, pari - [ ] **Sources** — HuggingFace Hub (repo id + revision), local paths, and a bundled resources dir (checked first). Streaming download into a per-user cache: resumable (`.part`), integrity-checked, and **sharded-checkpoint aware** (read `*.index.json`, fetch + merge shards). -- [ ] **safetensors** *(ex-laurelane)* — `burn-store` `SafetensorsStore` + `PyTorchToBurnAdapter`; the primary path. +- [x] **safetensors** *(ex-laurelane)* — `burn-store` `SafetensorsStore` + `PyTorchToBurnAdapter`; the primary path. + *(2026-07-09) `import::{CastFloatAdapter, load_checked}`: bf16→backend-float cast + fail-loud load; + proven by loading the real 3.1 GB Qwen2.5-1.5B and 2.3 GB LFM2.5 checkpoints with zero missing keys.* - [ ] **PyTorch state dicts** (`.pth` / `pytorch_model*.bin`) — for models not shipped as safetensors. - [ ] **GGUF** (llama.cpp) — parse the GGUF container (metadata KV + tensor table), map tensors to modules, and **dequantize** Q4/Q5/Q8/K-quant blocks into Burn tensors (or hand keep-quantized to P9). GGUF is how @@ -87,10 +106,15 @@ The subsystem that turns "a model on HuggingFace or on disk" into a loaded, pari - [ ] **ONNX** (optional) — `burn-import` ONNX→Burn for models distributed as ONNX graphs. - [ ] **Dtype handling** — a `CastFloatAdapter` (bf16→f32/f16); quantized→dequant on import; keep-quantized handed to P9. -- [ ] **Weight-name remapping + checked load** — per-architecture key-remap tables (checkpoint naming → +- [x] **Weight-name remapping + checked load** — per-architecture key-remap tables (checkpoint naming → Mummu module names); **fail loudly** on missing/unexpected keys with a readable diff, never silently zero-init. -- [ ] **Config import** — parse `config.json` → model hyperparameters (layers, hidden, heads, kv-heads, + *(2026-07-09) Remap tables live in each model's `load_from_dir` (Qwen2: strip `model.` + RmsNorm→gamma; + LFM2: + `out_proj`/`q_layernorm`/`w1-w3` onto the shared blocks); `load_checked` errors carry the + store's readable report. A declarative table registry lands with the manifest item below.* +- [x] **Config import** — parse `config.json` → model hyperparameters (layers, hidden, heads, kv-heads, rope-theta, vocab, tie-word-embeddings, …) so a model is **config-driven**, not hardcoded per checkpoint. + *(2026-07-09) Per-architecture serde configs with validation (`Qwen2Config`, `Lfm2Config` incl. + `layer_types` + auto-adjusted ff_dim); both real checkpoints parse and drive the build.* - [ ] **Tokenizer + chat-template import** — HF `tokenizer.json` (fast), SentencePiece `tokenizer.model`, BPE merges/vocab; special-tokens map + the chat template from `tokenizer_config.json`. - [ ] **Model registry / manifest** — a declarative `ModelSpec` (repo, architecture, weight format, dtype, @@ -101,10 +125,18 @@ The subsystem that turns "a model on HuggingFace or on disk" into a loaded, pari ### P4 — Tokenizer & chat templates *(ex-laurelane)* - [ ] HF `tokenizers` (pinned); explicit chat templates (ChatML + per-model), correct special/EOS tokens. +- [ ] Hermes-style tool-use chat template (Qwen3 ships it in `tokenizer_config.json`) + LFM2.5's + bracket-notation tool-call output — the two top scorers (0.880 agent score; LFM2.5-1.2B also the + fastest at ~1.5 s) on 2026's 21-model local tool-calling benchmark; function calling is why the + apps want a local runner — https://mikeveerman.be/blog/github-2026-02-06-tool-calling-benchmark/ · + https://qwen.readthedocs.io/en/latest/framework/function_call.html ### P5 — Decode engine *(ex-laurelane)* -- [ ] Per-layer KV cache (+ conv-state cache for hybrids); prompt prefilled once, then one token/step. -- [ ] On-GPU argmax (sync only the winning index); single-token decode skips the causal mask. +- [x] Per-layer KV cache (+ conv-state cache for hybrids); prompt prefilled once, then one token/step. + *(2026-07-09) `nn::{LayerKv, ConvState}` + per-model `new_cache`/`forward(past)`; prefill+decode ≡ + full-forward proven by unit tests at block AND whole-model level, then by real GPU decode.* +- [x] On-GPU argmax (sync only the winning index); single-token decode skips the causal mask. + *(2026-07-09) `decode::argmax_id`; `t == 1` builds no mask.* - [ ] Sampling beyond greedy (temperature / top-p); **token streaming** via a callback/channel; cooperative interrupt/cancellation between tokens. - [ ] Process-lifetime model + tokenizer cache (per backend; behind a `Mutex` since Burn `Param` isn't `Sync`). @@ -132,12 +164,19 @@ that fits the model AND uses every device to the fullest. ### P7 — Parity & performance harness *(ex-laurelane)* - [ ] Parity gate: single-forward top-k logits + a short greedy sequence must match a reference (Candle, - or a local Ollama of the same model) — the trust gate every port passes. + or a local Ollama of the same model) — the trust gate every port passes. *(2026-07-09) Greedy leg + exists (`tests/parity_lfm2.rs`, Ollama raw-mode temperature-0 via curl); blocked on a same-weights + reference: `ollama lfm2.5:latest` is now the 8.5B MoE and no 1.2B tag exists.* +- [ ] Stand up same-weights references: a Candle-based logits probe (dev-dependency or a small side + harness, as laurelane's Qwen2 validation did) + pull `qwen2.5:1.5b-instruct-fp16` in Ollama for the + Qwen greedy leg. - [ ] Wire the perf suite (above) into the parity harness so a correctness *or* budget regression fails CI. ### P8 — Model management API - [ ] Download progress · disk usage · switch/remove models — an app-agnostic API the consumers' settings UIs call. *(laurelane has disk-usage + remove; add progress + active-model switch.)* + *(2026-07-09) Disk usage + traversal-safe removal validation shipped as `manage` (5 unit tests); + download progress + active-model switch still open.* ### P9 — Quantization (fit any model to the hardware) The VRAM lever the P6 planner pulls to make the largest useful model fit the user's actual devices. @@ -175,3 +214,7 @@ Each app depends on Mummu (path/git dep) and keeps its own glue: tensor-parallel), Burn 0.20 — https://www.phoronix.com/news/Burn-0.20-Released - Quantization formats 2026 (GGUF K-quants Q2_K–Q8_0, GPTQ, AWQ, int4/int8; GGUF CPU+GPU hybrid) — https://www.digitalapplied.com/blog/gguf-vs-awq-vs-gptq-vs-mlx-llm-quantization-formats-2026 +- Burn 0.21 (May 2026): 8× lower framework overhead, differentiable collectives, burn-dispatch, faster + GEMV/top-k, per-tensor + per-block int8/int4/int2 quant on some backends — https://burn.dev/blog/release-0.21.0/ +- wgpu f16 IO polyfill on Vulkan (SHADER_F16 without storageInputOutput16), wgpu PR #7884 — confirmed + live on the dev box: Vulkan advertises SHADER_F16 on the 4070 Ti SUPER, DX12 does not (2026-07-09 probe). diff --git a/crates/mummu/src/decode.rs b/crates/mummu/src/decode.rs new file mode 100644 index 0000000..3ea9a3e --- /dev/null +++ b/crates/mummu/src/decode.rs @@ -0,0 +1,50 @@ +//! Decode-loop primitives shared by every causal model: on-device argmax and +//! a top-k probe. The full engine (sampling, streaming, interrupts) is P5. + +use burn::tensor::{Tensor, backend::Backend}; + +/// Greedy next-token id from `[1, vocab]` logits. The argmax runs +/// **on-device** and only the single winning index is synced back — vs. +/// copying a whole ~150k-logit vector to the CPU every decode step. +pub fn argmax_id(logits: Tensor) -> Result { + debug_assert!(logits.dims()[0] == 1, "argmax_id expects [1, vocab] logits"); + let data = logits + .argmax(1) + .into_data() + .convert::() + .to_vec::() + .map_err(|e| format!("argmax readback: {e:?}"))?; + debug_assert!(data.len() == 1, "argmax over [1, vocab] must yield one id"); + let id = data.first().copied().ok_or("argmax returned no data")?; + Ok(id as u32) +} + +/// Indices of the `k` largest values, descending (the parity probe's top-k). +#[must_use] +pub fn top_k_ids(v: &[f32], k: usize) -> Vec { + assert!(!v.is_empty(), "top_k_ids: empty logits"); + assert!(k >= 1, "top_k_ids: k must be >= 1"); + let mut idx: Vec = (0..v.len()).collect(); + idx.sort_unstable_by(|&a, &b| v[b].partial_cmp(&v[a]).unwrap_or(std::cmp::Ordering::Equal)); + idx.into_iter().take(k).map(|i| i as u32).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::backend::Cpu; + use burn::tensor::Tensor; + + #[test] + fn argmax_id_finds_the_peak() { + let device = burn::tensor::Device::::default(); + let logits = Tensor::::from_floats([0.1, -2.0, 7.5, 3.0], &device).reshape([1, 4]); + assert_eq!(argmax_id(logits).unwrap(), 2); + } + + #[test] + fn top_k_ids_orders_descending() { + let v = [0.1f32, 5.0, -2.0, 3.0]; + assert_eq!(top_k_ids(&v, 3), vec![1, 3, 0]); + } +} diff --git a/crates/mummu/src/import.rs b/crates/mummu/src/import.rs new file mode 100644 index 0000000..828fa2c --- /dev/null +++ b/crates/mummu/src/import.rs @@ -0,0 +1,143 @@ +//! Weight import: safetensors → Burn modules, checked and loud. +//! +//! The pieces every model load shares (P3): a dtype-cast adapter (HF ships +//! bf16, which wgpu can't ingest directly), and a checked-load wrapper that +//! **fails on missing or errored params** instead of silently zero-initing — +//! a partial load is a quietly broken model. + +use std::path::{Path, PathBuf}; +use std::rc::Rc; + +use burn::module::Module; +use burn::store::{ModuleAdapter, ModuleSnapshot, SafetensorsStore, TensorSnapshot}; +use burn::tensor::DType; + +/// Everything that can go wrong turning files on disk into a loaded model. +#[derive(Debug, thiserror::Error)] +pub enum ImportError { + #[error("required file missing: {0}")] + MissingFile(PathBuf), + #[error("parse {file}: {reason}")] + Parse { file: PathBuf, reason: String }, + #[error("load weights ({file}): {reason}")] + Load { file: PathBuf, reason: String }, + #[error( + "weight load incomplete ({file}): {applied} applied, {missing} missing, {errors} errors\n{report}" + )] + Incomplete { + file: PathBuf, + applied: usize, + missing: usize, + errors: usize, + report: String, + }, +} + +/// Cast bf16/f16/f64 float tensors to a target dtype on load. HF checkpoints +/// are commonly stored in **bf16**; `burn-store` keeps the source dtype, which +/// the wgpu backend can't ingest. Mirrors Burn's `HalfPrecisionAdapter` but +/// covers bf16 → f32/f16 too. Non-float tensors pass through untouched. +#[derive(Clone)] +pub struct CastFloatAdapter { + target: DType, +} + +impl CastFloatAdapter { + #[must_use] + pub fn new(target: DType) -> Self { + assert!( + matches!(target, DType::F16 | DType::F32 | DType::F64 | DType::BF16), + "CastFloatAdapter: target must be a float dtype, got {target:?}" + ); + Self { target } + } +} + +impl ModuleAdapter for CastFloatAdapter { + fn adapt(&self, snapshot: &TensorSnapshot) -> TensorSnapshot { + let is_float = matches!( + snapshot.dtype, + DType::BF16 | DType::F16 | DType::F32 | DType::F64 + ); + if !is_float || snapshot.dtype == self.target { + return snapshot.clone(); + } + let target = self.target; + let data_fn = snapshot.clone_data_fn(); + let cast = Rc::new(move || Ok(data_fn()?.convert_dtype(target))); + TensorSnapshot::from_closure( + cast, + target, + snapshot.shape.clone(), + snapshot.path_stack.clone().unwrap_or_default(), + snapshot.container_stack.clone().unwrap_or_default(), + snapshot.tensor_id.unwrap_or_default(), + ) + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +/// Load `store` into `module`, refusing partial results: any missing param or +/// per-tensor error is an [`ImportError::Incomplete`] carrying the store's own +/// readable report. Unused checkpoint tensors are *allowed* (e.g. BERT's +/// intentionally-skipped `pooler.*`) — callers that care inspect the report. +pub fn load_checked + ModuleSnapshot, B: burn::tensor::backend::Backend>( + module: &mut M, + store: &mut SafetensorsStore, + weights_path: &Path, +) -> Result<(), ImportError> { + let report = module.load_from(store).map_err(|e| ImportError::Load { + file: weights_path.to_path_buf(), + reason: e.to_string(), + })?; + if !report.errors.is_empty() || !report.missing.is_empty() { + return Err(ImportError::Incomplete { + file: weights_path.to_path_buf(), + applied: report.applied.len(), + missing: report.missing.len(), + errors: report.errors.len(), + report: format!("{report}"), + }); + } + debug_assert!( + !report.applied.is_empty(), + "load_checked: a successful load must have applied at least one tensor" + ); + Ok(()) +} + +/// `dir/file`, or [`ImportError::MissingFile`] if absent. +pub fn required_file(dir: &Path, file: &str) -> Result { + assert!(!file.is_empty(), "required_file: empty file name"); + let path = dir.join(file); + if path.is_file() { + Ok(path) + } else { + Err(ImportError::MissingFile(path)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn required_file_finds_present_and_rejects_absent() { + let dir = std::env::temp_dir().join("mummu_import_test"); + std::fs::create_dir_all(&dir).unwrap(); + std::fs::write(dir.join("config.json"), b"{}").unwrap(); + assert!(required_file(&dir, "config.json").is_ok()); + let err = required_file(&dir, "nope.bin").unwrap_err(); + assert!(matches!(err, ImportError::MissingFile(_))); + std::fs::remove_dir_all(&dir).unwrap(); + } + + #[test] + #[should_panic(expected = "float dtype")] + fn cast_adapter_rejects_non_float_target() { + let _ = CastFloatAdapter::new(DType::I32); + } +} diff --git a/crates/mummu/src/lib.rs b/crates/mummu/src/lib.rs index 53e5c94..712073f 100644 --- a/crates/mummu/src/lib.rs +++ b/crates/mummu/src/lib.rs @@ -10,4 +10,8 @@ #![recursion_limit = "512"] pub mod backend; +pub mod decode; +pub mod import; +pub mod manage; +pub mod models; pub mod nn; diff --git a/crates/mummu/src/manage.rs b/crates/mummu/src/manage.rs new file mode 100644 index 0000000..2c3428e --- /dev/null +++ b/crates/mummu/src/manage.rs @@ -0,0 +1,222 @@ +//! Model-cache disk accounting (P8): report how much space each cached model +//! takes and validate a removal target so a consumer's settings UI can +//! reclaim disk without ever escaping the cache dir. App-agnostic and free of +//! async/UI types; ported from laurelane's unit-tested implementation. + +use std::path::{Component, Path, PathBuf}; + +/// One cached model directory and its size on disk. +#[derive(serde::Serialize, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct ModelDisk { + /// Cache subdir name, e.g. `"qwen2.5-1.5b"`. + pub name: String, + pub bytes: u64, +} + +/// A full disk report for a model cache dir. +#[derive(serde::Serialize, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct DiskReport { + /// Cached models, largest first. + pub models: Vec, + pub total_bytes: u64, + /// The cache dir being reported — the "your models live here" line. + pub location: String, +} + +/// Recursively sum the sizes of every regular file under `path`. Best-effort: +/// unreadable entries are skipped and symlinked dirs are not followed (only +/// real directories recurse), so a broken link can't send it off the rails. +#[must_use] +pub fn dir_size(path: &Path) -> u64 { + let mut total = 0; + let Ok(entries) = std::fs::read_dir(path) else { + return 0; + }; + for entry in entries.flatten() { + let Ok(ft) = entry.file_type() else { continue }; + if ft.is_dir() { + total += dir_size(&entry.path()); + } else if ft.is_file() + && let Ok(meta) = entry.metadata() + { + total += meta.len(); + } + } + total +} + +/// Immediate subdirs of `models_dir`, each with its recursive size, sorted +/// largest-first (ties broken by name). A missing dir is an empty list, not +/// an error — a fresh install has no downloaded models yet. +#[must_use] +pub fn cached_models(models_dir: &Path) -> Vec { + let mut out = Vec::new(); + let Ok(entries) = std::fs::read_dir(models_dir) else { + return out; + }; + for entry in entries.flatten() { + if entry.file_type().map(|t| t.is_dir()).unwrap_or(false) { + let name = entry.file_name().to_string_lossy().into_owned(); + out.push(ModelDisk { + name, + bytes: dir_size(&entry.path()), + }); + } + } + out.sort_by(|a, b| b.bytes.cmp(&a.bytes).then_with(|| a.name.cmp(&b.name))); + out +} + +/// A full report: cached models + their combined size + the cache location. +#[must_use] +pub fn report(models_dir: &Path) -> DiskReport { + let models = cached_models(models_dir); + let total_bytes = models.iter().map(|m| m.bytes).sum(); + debug_assert!( + models.windows(2).all(|w| w[0].bytes >= w[1].bytes), + "cached_models must sort largest-first" + ); + DiskReport { + models, + total_bytes, + location: models_dir.to_string_lossy().into_owned(), + } +} + +/// Is `name` a safe single cache-subdir component? Rejects empty, `.`/`..`, +/// anything containing a path separator, and any rooted/prefixed path — so a +/// removal can never climb out of the cache dir. +#[must_use] +pub fn is_safe_component(name: &str) -> bool { + if name.is_empty() || name == "." || name == ".." { + return false; + } + if name.contains('/') || name.contains('\\') { + return false; + } + let mut parts = Path::new(name).components(); + matches!(parts.next(), Some(Component::Normal(_))) && parts.next().is_none() +} + +/// Resolve the on-disk path to remove for cache subdir `name`, or an error if +/// `name` is unsafe or absent. The returned path is always a direct child of +/// `models_dir`. +pub fn resolve_removal(models_dir: &Path, name: &str) -> Result { + if !is_safe_component(name) { + return Err(format!("unsafe model name: {name:?}")); + } + let target = models_dir.join(name); + if !target.is_dir() { + return Err(format!("no cached model named {name:?}")); + } + debug_assert!( + target.starts_with(models_dir), + "removal must stay inside the cache" + ); + Ok(target) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use std::path::PathBuf; + + /// A unique temp dir for one test, wiped fresh. Clock-free (uses the + /// test's own tag), so parallel tests never collide. + fn scratch(tag: &str) -> PathBuf { + let dir = std::env::temp_dir().join(format!("mummu_manage_test_{tag}")); + let _ = fs::remove_dir_all(&dir); + fs::create_dir_all(&dir).unwrap(); + dir + } + + fn write(path: &Path, bytes: usize) { + fs::create_dir_all(path.parent().unwrap()).unwrap(); + fs::write(path, vec![b'x'; bytes]).unwrap(); + } + + #[test] + fn dir_size_sums_nested_files() { + let root = scratch("dirsize"); + write(&root.join("a.bin"), 100); + write(&root.join("sub/b.bin"), 250); + write(&root.join("sub/deep/c.bin"), 50); + assert_eq!(dir_size(&root), 400); + // A missing dir is zero, not a panic. + assert_eq!(dir_size(&root.join("nope")), 0); + fs::remove_dir_all(&root).unwrap(); + } + + #[test] + fn cached_models_lists_subdirs_largest_first_ignoring_loose_files() { + let root = scratch("cached"); + write(&root.join("qwen2.5-1.5b/model.safetensors"), 3000); + write(&root.join("all-minilm/model.safetensors"), 90); + write(&root.join("lfm2.5/model.safetensors"), 500); + write(&root.join("loose.txt"), 10_000); // not a dir → ignored + let got = cached_models(&root); + assert_eq!( + got, + vec![ + ModelDisk { + name: "qwen2.5-1.5b".into(), + bytes: 3000 + }, + ModelDisk { + name: "lfm2.5".into(), + bytes: 500 + }, + ModelDisk { + name: "all-minilm".into(), + bytes: 90 + }, + ], + ); + assert_eq!(report(&root).total_bytes, 3590); + fs::remove_dir_all(&root).unwrap(); + } + + #[test] + fn cached_models_of_missing_dir_is_empty() { + let missing = std::env::temp_dir().join("mummu_manage_test_absent_xyz"); + let _ = fs::remove_dir_all(&missing); + assert!(cached_models(&missing).is_empty()); + assert_eq!(report(&missing).total_bytes, 0); + } + + #[test] + fn is_safe_component_accepts_names_and_rejects_traversal() { + assert!(is_safe_component("qwen2.5-1.5b")); + assert!(is_safe_component("all-minilm")); + for bad in [ + "", + ".", + "..", + "a/b", + "a\\b", + "/etc", + "..\\..\\x", + "C:\\x", + "sub/", + ] { + assert!(!is_safe_component(bad), "should reject {bad:?}"); + } + } + + #[test] + fn resolve_removal_rejects_unsafe_and_absent_but_finds_present() { + let root = scratch("removal"); + write(&root.join("qwen2.5-0.5b/model.safetensors"), 10); + assert_eq!( + resolve_removal(&root, "qwen2.5-0.5b").unwrap(), + root.join("qwen2.5-0.5b") + ); + assert!(resolve_removal(&root, "not-there").is_err()); + assert!(resolve_removal(&root, "..").is_err()); + assert!(resolve_removal(&root, "../models").is_err()); + fs::remove_dir_all(&root).unwrap(); + } +} diff --git a/crates/mummu/src/models/lfm2.rs b/crates/mummu/src/models/lfm2.rs new file mode 100644 index 0000000..617c97f --- /dev/null +++ b/crates/mummu/src/models/lfm2.rs @@ -0,0 +1,436 @@ +//! LFM2 / LFM2.5 (Liquid Foundation Model) hybrid decoder on the shared `nn` +//! blocks: Embedding → N×{operator_norm, (gated ShortConv "LIV" | GQA attention +//! with per-head q/k RMSNorm), ffn_norm, SwiGLU} → embedding_norm → tied +//! lm-head. `layer_types[i]` in the checkpoint's `config.json` selects the +//! operator per layer (10 conv + 6 attention for the 1.2B). +//! +//! Ported from laurelane's implementation (greedy temperature-0 token match vs +//! a local Ollama `lfm2.5` reference). Mummu's parity gate (P7) re-verifies +//! here before the port is marked trusted. + +use std::path::Path; + +use burn::module::Module; +use burn::nn::{Embedding, EmbeddingConfig, RmsNorm, RmsNormConfig}; +use burn::store::{ModuleAdapter, PyTorchToBurnAdapter, SafetensorsStore}; +use burn::tensor::{Int, Tensor, TensorData, backend::Backend}; + +use crate::decode::{argmax_id, top_k_ids}; +use crate::import::{CastFloatAdapter, ImportError, load_checked, required_file}; +use crate::models::qwen2::EosIds; +use crate::nn::{ + ConvState, GqaAttention, GqaAttentionConfig, LayerKv, ShortConv, ShortConvConfig, SwiGluMlp, + SwiGluMlpConfig, causal_mask, rope_tables, +}; + +/// LFM2 architecture hyperparameters, read from the checkpoint's `config.json`. +#[derive(Debug, Clone, serde::Deserialize)] +pub struct Lfm2Config { + pub vocab_size: usize, + pub hidden_size: usize, + pub num_hidden_layers: usize, + pub num_attention_heads: usize, + pub num_key_value_heads: usize, + pub norm_eps: f64, + pub rope_theta: f32, + /// Conv kernel length `K` (the rolling decode state keeps `K-1`). + #[serde(rename = "conv_L_cache")] + pub conv_l_cache: usize, + pub block_ff_dim: usize, + pub block_multiple_of: usize, + #[serde(default)] + pub block_auto_adjust_ff_dim: bool, + /// `"full_attention"` or `"conv"` per layer. + pub layer_types: Vec, + /// `<|im_end|>` (id 7) for the instruct checkpoints. + #[serde(default)] + pub eos_token_id: EosIds, +} + +impl Lfm2Config { + #[must_use] + pub fn head_dim(&self) -> usize { + self.hidden_size / self.num_attention_heads + } + + /// SwiGLU hidden dim: `block_auto_adjust_ff_dim` shrinks to 2/3 then + /// rounds up to `block_multiple_of` (8192 for the 1.2B). + #[must_use] + pub fn ff_dim(&self) -> usize { + if self.block_auto_adjust_ff_dim { + let d = (2 * self.block_ff_dim) / 3; + let m = self.block_multiple_of.max(1); + d.div_ceil(m) * m + } else { + self.block_ff_dim + } + } + + fn is_attention(&self, layer: usize) -> bool { + self.layer_types + .get(layer) + .is_some_and(|s| s == "full_attention") + } + + /// Parse `config.json` bytes. + pub fn from_json_bytes(bytes: &[u8]) -> Result { + let cfg: Self = serde_json::from_slice(bytes).map_err(|e| e.to_string())?; + cfg.validate()?; + Ok(cfg) + } + + fn validate(&self) -> Result<(), String> { + if self.layer_types.len() != self.num_hidden_layers { + return Err(format!( + "layer_types has {} entries but num_hidden_layers is {}", + self.layer_types.len(), + self.num_hidden_layers + )); + } + if self.num_key_value_heads == 0 + || !self + .num_attention_heads + .is_multiple_of(self.num_key_value_heads) + { + return Err(format!( + "num_attention_heads ({}) must be a positive multiple of num_key_value_heads ({})", + self.num_attention_heads, self.num_key_value_heads + )); + } + if self.conv_l_cache < 2 { + return Err(format!( + "conv_L_cache must be >= 2, got {}", + self.conv_l_cache + )); + } + Ok(()) + } +} + +/// One hybrid layer: exactly one of `conv` / `self_attn` is present, per +/// `layer_types[i]`. Field names mirror the HF checkpoint. +#[derive(Module, Debug)] +pub struct HybridLayer { + pub conv: Option>, + pub self_attn: Option>, + pub feed_forward: SwiGluMlp, + pub operator_norm: RmsNorm, + pub ffn_norm: RmsNorm, +} + +/// The LFM2 decoder stack (tied lm-head). +#[derive(Module, Debug)] +pub struct Lfm2 { + pub embed_tokens: Embedding, + pub layers: Vec>, + pub embedding_norm: RmsNorm, +} + +/// Per-layer decode cache: conv layers roll the last `K-1` gated inputs, +/// attention layers keep the running k/v. +pub enum HybridKv { + Conv(ConvState), + Attn(LayerKv), +} + +/// A weight-loaded LFM2 plus its config. +pub struct LoadedLfm2 { + pub model: Lfm2, + pub config: Lfm2Config, +} + +fn build(cfg: &Lfm2Config, device: &B::Device) -> Lfm2 { + let attn_cfg = GqaAttentionConfig { + hidden_size: cfg.hidden_size, + num_heads: cfg.num_attention_heads, + num_kv_heads: cfg.num_key_value_heads, + head_dim: cfg.head_dim(), + bias: false, // LFM2 projections are bias-free + qk_norm_eps: Some(cfg.norm_eps), // per-head q/k RMSNorm + }; + let conv_cfg = ShortConvConfig { + hidden_size: cfg.hidden_size, + kernel_len: cfg.conv_l_cache, + }; + let mlp_cfg = SwiGluMlpConfig { + hidden_size: cfg.hidden_size, + intermediate_size: cfg.ff_dim(), + }; + let norm = |dev: &B::Device| { + RmsNormConfig::new(cfg.hidden_size) + .with_epsilon(cfg.norm_eps) + .init(dev) + }; + let layers = (0..cfg.num_hidden_layers) + .map(|i| { + let attn = cfg.is_attention(i); + HybridLayer { + conv: (!attn).then(|| conv_cfg.init(device)), + self_attn: attn.then(|| attn_cfg.init(device)), + feed_forward: mlp_cfg.init(device), + operator_norm: norm(device), + ffn_norm: norm(device), + } + }) + .collect(); + Lfm2 { + embed_tokens: EmbeddingConfig::new(cfg.vocab_size, cfg.hidden_size).init(device), + layers, + embedding_norm: norm(device), + } +} + +/// Build the architecture from `dir/config.json` and load +/// `dir/model.safetensors`, checked. Key remap: strip `model.`, RmsNorm +/// `weight` → `gamma`, and LFM2's `out_proj`/`q_layernorm`/`k_layernorm` onto +/// the shared block's `o_proj`/`q_norm`/`k_norm`. +pub fn load_from_dir( + dir: &Path, + device: &B::Device, +) -> Result, ImportError> { + let cfg_path = required_file(dir, "config.json")?; + let weights = required_file(dir, "model.safetensors")?; + let cfg_bytes = std::fs::read(&cfg_path).map_err(|e| ImportError::Parse { + file: cfg_path.clone(), + reason: e.to_string(), + })?; + let config = Lfm2Config::from_json_bytes(&cfg_bytes).map_err(|reason| ImportError::Parse { + file: cfg_path, + reason, + })?; + + let mut model = build::(&config, device); + let target_float = Tensor::::zeros([1], device).dtype(); + let mut store = SafetensorsStore::from_file(weights.clone()) + .with_from_adapter(PyTorchToBurnAdapter.chain(CastFloatAdapter::new(target_float))) + .allow_partial(true) + .with_key_remapping(r"^model\.", "") + .with_key_remapping(r"(self_attn)\.out_proj\.", "$1.o_proj.") + .with_key_remapping(r"(self_attn)\.q_layernorm\.weight$", "$1.q_norm.gamma") + .with_key_remapping(r"(self_attn)\.k_layernorm\.weight$", "$1.k_norm.gamma") + .with_key_remapping(r"(feed_forward)\.w1\.", "$1.gate_proj.") + .with_key_remapping(r"(feed_forward)\.w2\.", "$1.down_proj.") + .with_key_remapping(r"(feed_forward)\.w3\.", "$1.up_proj.") + .with_key_remapping( + r"(operator_norm|ffn_norm|embedding_norm)\.weight$", + "$1.gamma", + ); + load_checked(&mut model, &mut store, &weights)?; + Ok(LoadedLfm2 { model, config }) +} + +impl LoadedLfm2 { + /// A fresh per-layer cache matching `layer_types`. + #[must_use] + pub fn new_cache(&self) -> Vec> { + (0..self.config.num_hidden_layers) + .map(|i| { + if self.config.is_attention(i) { + HybridKv::Attn(None) + } else { + HybridKv::Conv(None) + } + }) + .collect() + } + + /// Forward `new_ids` (the whole prompt when `past == 0`, else one decode + /// token), updating `cache`; returns logits for the last position `[1, vocab]`. + pub fn forward( + &self, + new_ids: &[u32], + past: usize, + cache: &mut [HybridKv], + device: &B::Device, + ) -> Tensor { + let t = new_ids.len(); + assert!(t >= 1, "LFM2 forward: need at least one token"); + assert!( + cache.len() == self.config.num_hidden_layers, + "LFM2 forward: cache has {} layers, model has {}", + cache.len(), + self.config.num_hidden_layers + ); + let cfg = &self.config; + + let ids32: Vec = new_ids.iter().map(|&i| i as i32).collect(); + let input = + Tensor::::from_data(TensorData::new(ids32, [t]), device).reshape([1, t]); + let mut x = self.model.embed_tokens.forward(input); + + let (cos, sin) = rope_tables::(t, past, cfg.head_dim(), cfg.rope_theta, device); + let mask = (t > 1).then(|| causal_mask::(t, past, device)); + let kk = cfg.conv_l_cache; + + for (layer, kv) in self.model.layers.iter().zip(cache.iter_mut()) { + let h = layer.operator_norm.forward(x.clone()); + let h = match (&layer.conv, &layer.self_attn, kv) { + (Some(conv), None, HybridKv::Conv(state)) => conv.forward(h, kk, state), + (None, Some(attn), HybridKv::Attn(kv_state)) => attn.forward( + h, + cfg.num_attention_heads, + cfg.num_key_value_heads, + cfg.head_dim(), + &cos, + &sin, + mask.as_ref(), + kv_state, + ), + // Layer kind and cache kind disagree — a caller bug. + _ => unreachable!("LFM2 forward: layer/cache kind mismatch"), + }; + x = x.add(h); + let h2 = layer.ffn_norm.forward(x.clone()); + x = x.add(layer.feed_forward.forward(h2)); + } + let x = self.model.embedding_norm.forward(x); + let last = x.narrow(1, t - 1, 1).reshape([1, cfg.hidden_size]); + let w = self.model.embed_tokens.weight.val(); // tied lm-head + last.matmul(w.swap_dims(0, 1)) + } + + /// Greedy decode: prefill once, then one token per step, stopping at any + /// config EOS id or `max_tokens`. Argmax runs on-device. + pub fn greedy_generate( + &self, + prompt_ids: &[u32], + max_tokens: usize, + device: &B::Device, + ) -> Result, String> { + assert!(!prompt_ids.is_empty(), "greedy_generate: empty prompt"); + assert!(max_tokens >= 1, "greedy_generate: max_tokens must be >= 1"); + let mut cache = self.new_cache(); + let mut logits = self.forward(prompt_ids, 0, &mut cache, device); + let mut out: Vec = Vec::with_capacity(max_tokens); + for past in (prompt_ids.len()..).take(max_tokens) { + let next = argmax_id(logits)?; + if self.config.eos_token_id.contains(next) { + break; + } + out.push(next); + logits = self.forward(&[next], past, &mut cache, device); + } + debug_assert!(out.len() <= max_tokens); + Ok(out) + } + + /// Parity probe: top-k next-token ids for a single prefill. + pub fn first_token( + &self, + prompt_ids: &[u32], + k: usize, + device: &B::Device, + ) -> Result, String> { + assert!(!prompt_ids.is_empty(), "first_token: empty prompt"); + assert!(k >= 1, "first_token: k must be >= 1"); + let mut cache = self.new_cache(); + let logits = self.forward(prompt_ids, 0, &mut cache, device); + let v = logits + .into_data() + .convert::() + .to_vec::() + .map_err(|e| format!("logits readback: {e:?}"))?; + Ok(top_k_ids(&v, k)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::backend::Cpu; + + type Dev = burn::tensor::Device; + + /// A 3-layer toy hybrid: conv, attention, conv. + fn toy_config() -> Lfm2Config { + Lfm2Config { + vocab_size: 48, + hidden_size: 16, + num_hidden_layers: 3, + num_attention_heads: 4, + num_key_value_heads: 2, + norm_eps: 1e-5, + rope_theta: 1e4, + conv_l_cache: 3, + block_ff_dim: 48, + block_multiple_of: 8, + block_auto_adjust_ff_dim: true, + layer_types: vec!["conv".into(), "full_attention".into(), "conv".into()], + eos_token_id: EosIds::One(7), + } + } + + #[test] + fn ff_dim_auto_adjust_matches_reference_formula() { + let cfg = toy_config(); + // 2*48/3 = 32, already a multiple of 8. + assert_eq!(cfg.ff_dim(), 32); + // The 1.2B's real numbers: 2/3 of 12288 = 8192, multiple_of 8192 → 8192. + let mut big = toy_config(); + big.block_ff_dim = 12288; + big.block_multiple_of = 8192; + assert_eq!(big.ff_dim(), 8192); + } + + #[test] + fn config_rejects_layer_type_count_mismatch() { + let mut cfg = toy_config(); + cfg.layer_types.pop(); + assert!(cfg.validate().is_err()); + } + + #[test] + fn build_places_operators_by_layer_type() { + let device = Dev::default(); + let cfg = toy_config(); + let model = build::(&cfg, &device); + assert!(model.layers[0].conv.is_some() && model.layers[0].self_attn.is_none()); + assert!(model.layers[1].conv.is_none() && model.layers[1].self_attn.is_some()); + assert!(model.layers[2].conv.is_some() && model.layers[2].self_attn.is_none()); + } + + /// Whole-stack cache equivalence for the hybrid: prefill + cached decode + /// equals one full forward, across BOTH cache kinds at once. + #[test] + fn toy_hybrid_cached_decode_matches_full_forward() { + let device = Dev::default(); + let cfg = toy_config(); + let loaded = LoadedLfm2:: { + model: build(&cfg, &device), + config: cfg, + }; + + let prompt: Vec = vec![5, 11, 2, 30]; + let mut cache = loaded.new_cache(); + let _ = loaded.forward(&prompt, 0, &mut cache, &device); + let step = loaded + .forward(&[19], prompt.len(), &mut cache, &device) + .into_data() + .to_vec::() + .unwrap(); + + let mut full_cache = loaded.new_cache(); + let all: Vec = prompt.iter().copied().chain([19]).collect(); + let full = loaded + .forward(&all, 0, &mut full_cache, &device) + .into_data() + .to_vec::() + .unwrap(); + + for (i, (c, f)) in step.iter().zip(&full).enumerate() { + assert!((c - f).abs() < 1e-4, "logit {i}: cached {c} vs full {f}"); + } + } + + #[test] + fn greedy_generate_respects_max_tokens_bound() { + let device = Dev::default(); + let cfg = toy_config(); + let loaded = LoadedLfm2:: { + model: build(&cfg, &device), + config: cfg, + }; + let out = loaded.greedy_generate(&[1, 2], 3, &device).unwrap(); + assert!(out.len() <= 3); + } +} diff --git a/crates/mummu/src/models/mod.rs b/crates/mummu/src/models/mod.rs new file mode 100644 index 0000000..d1686f9 --- /dev/null +++ b/crates/mummu/src/models/mod.rs @@ -0,0 +1,6 @@ +//! The model zoo: from-scratch architectures on the shared `nn` blocks, all +//! generic over `B: Backend`, all config-driven (hyperparameters come from the +//! checkpoint's `config.json`, never hardcoded). + +pub mod lfm2; +pub mod qwen2; diff --git a/crates/mummu/src/models/qwen2.rs b/crates/mummu/src/models/qwen2.rs new file mode 100644 index 0000000..198046c --- /dev/null +++ b/crates/mummu/src/models/qwen2.rs @@ -0,0 +1,401 @@ +//! Qwen2 / Qwen2.5 decoder, from scratch on the shared `nn` blocks: +//! Embedding → N×{RmsNorm, GQA attention (RoPE, KV cache), RmsNorm, SwiGLU} +//! → RmsNorm → tied lm-head. Config-driven — the 0.5B and 1.5B tiers (and any +//! other single-file Qwen2 checkpoint) load through the same code. +//! +//! Ported from laurelane's parity-validated implementation (single-forward +//! next-token logits byte-identical to Candle's `qwen2` on identical f32 +//! weights, CPU and wgpu GPU). Mummu's own parity gate (P7) re-verifies here +//! before the port is marked trusted. + +use std::path::Path; + +use burn::module::Module; +use burn::nn::{Embedding, EmbeddingConfig, RmsNorm, RmsNormConfig}; +use burn::store::{ModuleAdapter, PyTorchToBurnAdapter, SafetensorsStore}; +use burn::tensor::{Int, Tensor, TensorData, backend::Backend}; + +use crate::decode::{argmax_id, top_k_ids}; +use crate::import::{CastFloatAdapter, ImportError, load_checked, required_file}; +use crate::nn::{ + GqaAttention, GqaAttentionConfig, LayerKv, SwiGluMlp, SwiGluMlpConfig, causal_mask, rope_tables, +}; + +/// Qwen2 architecture hyperparameters, read from the checkpoint's `config.json`. +#[derive(Debug, Clone, serde::Deserialize)] +pub struct Qwen2Config { + pub vocab_size: usize, + pub hidden_size: usize, + pub intermediate_size: usize, + pub num_hidden_layers: usize, + pub num_attention_heads: usize, + pub num_key_value_heads: usize, + #[serde(default)] + pub head_dim: usize, + pub rms_norm_eps: f64, + pub rope_theta: f32, + #[serde(default)] + pub tie_word_embeddings: bool, + /// EOS token id(s) — `<|im_end|>` first for the instruct checkpoints. + #[serde(default)] + pub eos_token_id: EosIds, +} + +/// `eos_token_id` is a bare int in some checkpoints, a list in others. +#[derive(Debug, Clone, Default, serde::Deserialize)] +#[serde(untagged)] +pub enum EosIds { + #[default] + None, + One(u32), + Many(Vec), +} + +impl EosIds { + /// Is `id` one of the EOS ids? + #[must_use] + pub fn contains(&self, id: u32) -> bool { + match self { + Self::None => false, + Self::One(e) => *e == id, + Self::Many(v) => v.contains(&id), + } + } +} + +impl Qwen2Config { + /// Parse `config.json` bytes; derives `head_dim` when absent. + pub fn from_json_bytes(bytes: &[u8]) -> Result { + let mut cfg: Self = serde_json::from_slice(bytes).map_err(|e| e.to_string())?; + if cfg.head_dim == 0 { + cfg.head_dim = cfg.hidden_size / cfg.num_attention_heads; + } + cfg.validate()?; + Ok(cfg) + } + + fn validate(&self) -> Result<(), String> { + if self.num_key_value_heads == 0 + || !self + .num_attention_heads + .is_multiple_of(self.num_key_value_heads) + { + return Err(format!( + "num_attention_heads ({}) must be a positive multiple of num_key_value_heads ({})", + self.num_attention_heads, self.num_key_value_heads + )); + } + if self.num_hidden_layers == 0 || self.vocab_size == 0 { + return Err("num_hidden_layers and vocab_size must be positive".into()); + } + Ok(()) + } +} + +/// One decoder layer. Field names mirror the HF checkpoint so the key remap +/// stays trivial. +#[derive(Module, Debug)] +pub struct DecoderLayer { + pub self_attn: GqaAttention, + pub mlp: SwiGluMlp, + pub input_layernorm: RmsNorm, + pub post_attention_layernorm: RmsNorm, +} + +/// The Qwen2 decoder stack (HF's `model.*` subtree; the lm-head is tied). +#[derive(Module, Debug)] +pub struct Qwen2 { + pub embed_tokens: Embedding, + pub layers: Vec>, + pub norm: RmsNorm, +} + +/// A weight-loaded Qwen2 plus its config — everything a forward needs. +pub struct LoadedQwen2 { + pub model: Qwen2, + pub config: Qwen2Config, +} + +fn build(cfg: &Qwen2Config, device: &B::Device) -> Qwen2 { + let attn_cfg = GqaAttentionConfig { + hidden_size: cfg.hidden_size, + num_heads: cfg.num_attention_heads, + num_kv_heads: cfg.num_key_value_heads, + head_dim: cfg.head_dim, + bias: true, // Qwen2 has q/k/v projection bias + qk_norm_eps: None, // and no per-head q/k norm + }; + let mlp_cfg = SwiGluMlpConfig { + hidden_size: cfg.hidden_size, + intermediate_size: cfg.intermediate_size, + }; + let norm = |dev: &B::Device| { + RmsNormConfig::new(cfg.hidden_size) + .with_epsilon(cfg.rms_norm_eps) + .init(dev) + }; + let layers = (0..cfg.num_hidden_layers) + .map(|_| DecoderLayer { + self_attn: attn_cfg.init(device), + mlp: mlp_cfg.init(device), + input_layernorm: norm(device), + post_attention_layernorm: norm(device), + }) + .collect(); + Qwen2 { + embed_tokens: EmbeddingConfig::new(cfg.vocab_size, cfg.hidden_size).init(device), + layers, + norm: norm(device), + } +} + +/// Build the architecture from `dir/config.json` and load +/// `dir/model.safetensors` into it, checked (no silent partial loads). +/// +/// The key remap maps HF names onto our HF-mirroring field paths: strip the +/// `model.` prefix and rename RmsNorm `weight` → Burn's `gamma` +/// (`PyTorchToBurnAdapter` renames Layer/Batch/Group norm params but NOT +/// RmsNorm; it does transpose the Linear weights). +pub fn load_from_dir( + dir: &Path, + device: &B::Device, +) -> Result, ImportError> { + let cfg_path = required_file(dir, "config.json")?; + let weights = required_file(dir, "model.safetensors")?; + let cfg_bytes = std::fs::read(&cfg_path).map_err(|e| ImportError::Parse { + file: cfg_path.clone(), + reason: e.to_string(), + })?; + let config = Qwen2Config::from_json_bytes(&cfg_bytes).map_err(|reason| ImportError::Parse { + file: cfg_path, + reason, + })?; + + let mut model = build::(&config, device); + // The backend's own float dtype (f32, or f16 on the GpuF16 alias). + let target_float = Tensor::::zeros([1], device).dtype(); + let mut store = SafetensorsStore::from_file(weights.clone()) + .with_from_adapter(PyTorchToBurnAdapter.chain(CastFloatAdapter::new(target_float))) + .allow_partial(true) + .with_key_remapping(r"^model\.", "") + .with_key_remapping(r"(input_layernorm)\.weight$", "$1.gamma") + .with_key_remapping(r"(post_attention_layernorm)\.weight$", "$1.gamma") + .with_key_remapping(r"^norm\.weight$", "norm.gamma"); + load_checked(&mut model, &mut store, &weights)?; + Ok(LoadedQwen2 { model, config }) +} + +impl LoadedQwen2 { + /// A fresh (empty) per-layer KV cache. + #[must_use] + pub fn new_cache(&self) -> Vec> { + (0..self.config.num_hidden_layers).map(|_| None).collect() + } + + /// Forward `new_ids` (the whole prompt when `past == 0`, else one decode + /// token), updating `cache`; returns logits for the **last** position, + /// `[1, vocab]`. + pub fn forward( + &self, + new_ids: &[u32], + past: usize, + cache: &mut [LayerKv], + device: &B::Device, + ) -> Tensor { + let t = new_ids.len(); + assert!(t >= 1, "Qwen2 forward: need at least one token"); + assert!( + cache.len() == self.config.num_hidden_layers, + "Qwen2 forward: cache has {} layers, model has {}", + cache.len(), + self.config.num_hidden_layers + ); + let cfg = &self.config; + + // i32 token ids: native for wgpu, upcast for ndarray — portable either way. + let ids32: Vec = new_ids.iter().map(|&i| i as i32).collect(); + let input = + Tensor::::from_data(TensorData::new(ids32, [t]), device).reshape([1, t]); + let mut x = self.model.embed_tokens.forward(input); // [1, t, hidden] + + let (cos, sin) = rope_tables::(t, past, cfg.head_dim, cfg.rope_theta, device); + // A single-token decode step needs no mask (the one query attends to + // all cached keys); only a multi-token prefill needs the triangle. + let mask = (t > 1).then(|| causal_mask::(t, past, device)); + + for (layer, kv) in self.model.layers.iter().zip(cache.iter_mut()) { + let h = layer.input_layernorm.forward(x.clone()); + let h = layer.self_attn.forward( + h, + cfg.num_attention_heads, + cfg.num_key_value_heads, + cfg.head_dim, + &cos, + &sin, + mask.as_ref(), + kv, + ); + x = x.add(h); + let h2 = layer.post_attention_layernorm.forward(x.clone()); + x = x.add(layer.mlp.forward(h2)); + } + let x = self.model.norm.forward(x); + + // Tied lm-head: logits = last_hidden @ embed_weight^T. + let last = x.narrow(1, t - 1, 1).reshape([1, cfg.hidden_size]); + let w = self.model.embed_tokens.weight.val(); // [vocab, hidden] + last.matmul(w.swap_dims(0, 1)) // [1, vocab] + } + + /// Greedy decode: prefill `prompt_ids` once, then one token per step + /// through the KV cache, stopping at any config EOS id or `max_tokens`. + /// The argmax runs on-device; only the winning index is synced back. + pub fn greedy_generate( + &self, + prompt_ids: &[u32], + max_tokens: usize, + device: &B::Device, + ) -> Result, String> { + assert!(!prompt_ids.is_empty(), "greedy_generate: empty prompt"); + assert!(max_tokens >= 1, "greedy_generate: max_tokens must be >= 1"); + let mut cache = self.new_cache(); + let mut logits = self.forward(prompt_ids, 0, &mut cache, device); + let mut out: Vec = Vec::with_capacity(max_tokens); + for past in (prompt_ids.len()..).take(max_tokens) { + let next = argmax_id(logits)?; + if self.config.eos_token_id.contains(next) { + break; + } + out.push(next); + logits = self.forward(&[next], past, &mut cache, device); + } + debug_assert!(out.len() <= max_tokens); + Ok(out) + } + + /// Parity probe: greedy next-token id + top-k ids for a single prefill. + pub fn first_token( + &self, + prompt_ids: &[u32], + k: usize, + device: &B::Device, + ) -> Result, String> { + assert!(!prompt_ids.is_empty(), "first_token: empty prompt"); + assert!(k >= 1, "first_token: k must be >= 1"); + let mut cache = self.new_cache(); + let logits = self.forward(prompt_ids, 0, &mut cache, device); + // `convert` so an f16 GPU tensor reads back as f32. + let v = logits + .into_data() + .convert::() + .to_vec::() + .map_err(|e| format!("logits readback: {e:?}"))?; + Ok(top_k_ids(&v, k)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::backend::Cpu; + + type Dev = burn::tensor::Device; + + /// A synthetic 2-layer toy config: everything runs without real weights. + fn toy_config() -> Qwen2Config { + Qwen2Config { + vocab_size: 64, + hidden_size: 16, + intermediate_size: 32, + num_hidden_layers: 2, + num_attention_heads: 4, + num_key_value_heads: 2, + head_dim: 4, + rms_norm_eps: 1e-6, + rope_theta: 1e4, + tie_word_embeddings: true, + eos_token_id: EosIds::One(2), + } + } + + #[test] + fn config_parses_hf_shape_and_derives_head_dim() { + let json = br#"{ + "vocab_size": 151936, "hidden_size": 1536, "intermediate_size": 8960, + "num_hidden_layers": 28, "num_attention_heads": 12, "num_key_value_heads": 2, + "rms_norm_eps": 1e-6, "rope_theta": 1000000.0, "tie_word_embeddings": true, + "eos_token_id": 151645 + }"#; + let cfg = Qwen2Config::from_json_bytes(json).unwrap(); + assert_eq!(cfg.head_dim, 128); // derived 1536/12 + assert!(cfg.eos_token_id.contains(151_645)); + assert!(!cfg.eos_token_id.contains(151_644)); + } + + #[test] + fn config_rejects_indivisible_heads() { + let json = br#"{ + "vocab_size": 100, "hidden_size": 16, "intermediate_size": 32, + "num_hidden_layers": 1, "num_attention_heads": 5, "num_key_value_heads": 2, + "rms_norm_eps": 1e-6, "rope_theta": 10000.0 + }"#; + assert!(Qwen2Config::from_json_bytes(json).is_err()); + } + + #[test] + fn eos_ids_parse_as_int_or_list() { + let one: EosIds = serde_json::from_str("7").unwrap(); + let many: EosIds = serde_json::from_str("[7, 9]").unwrap(); + assert!(one.contains(7) && !one.contains(9)); + assert!(many.contains(7) && many.contains(9) && !many.contains(8)); + } + + /// The toy model decodes through the cache identically to full re-forwards + /// — the whole-stack version of the nn-level equivalence tests. + #[test] + fn toy_model_cached_decode_matches_full_forward() { + let device = Dev::default(); + let cfg = toy_config(); + let loaded = LoadedQwen2:: { + model: build(&cfg, &device), + config: cfg, + }; + + let prompt: Vec = vec![3, 14, 15, 9, 26]; + + // Cached: prefill then one decode step for token at position 5. + let mut cache = loaded.new_cache(); + let _ = loaded.forward(&prompt, 0, &mut cache, &device); + let step = loaded + .forward(&[42], prompt.len(), &mut cache, &device) + .into_data() + .to_vec::() + .unwrap(); + + // Full: all six tokens in one forward, no cache reuse. + let mut full_cache = loaded.new_cache(); + let all: Vec = prompt.iter().copied().chain([42]).collect(); + let full = loaded + .forward(&all, 0, &mut full_cache, &device) + .into_data() + .to_vec::() + .unwrap(); + + assert_eq!(step.len(), full.len()); + for (i, (c, f)) in step.iter().zip(&full).enumerate() { + assert!((c - f).abs() < 1e-4, "logit {i}: cached {c} vs full {f}"); + } + } + + #[test] + fn greedy_generate_respects_max_tokens_bound() { + let device = Dev::default(); + let cfg = toy_config(); + let loaded = LoadedQwen2:: { + model: build(&cfg, &device), + config: cfg, + }; + let out = loaded.greedy_generate(&[1, 2, 3], 4, &device).unwrap(); + assert!(out.len() <= 4); + } +} diff --git a/crates/mummu/tests/parity_lfm2.rs b/crates/mummu/tests/parity_lfm2.rs new file mode 100644 index 0000000..d5c0d53 --- /dev/null +++ b/crates/mummu/tests/parity_lfm2.rs @@ -0,0 +1,107 @@ +//! LFM2.5 parity: greedy sequence vs a local Ollama `lfm2.5` reference (the +//! same reference laurelane validated against). Ignored by default; run with +//! +//! ```text +//! MUMMU_LFM2_DIR=path/to/lfm2.5-1.2b cargo test -p mummu --release --test parity_lfm2 -- --ignored --nocapture +//! ``` +//! +//! Needs a running Ollama whose `lfm2.5` tag is the SAME weights (the 1.2B +//! dense instruct). As of 2026-07-09 `lfm2.5:latest` resolves to the 8.5B +//! **MoE** at Q4_K_M with thinking — a different model, so this test fails +//! against it by design (fail loudly, never compare across weights). This is +//! the greedy leg of the P7 parity gate; the top-k-logits leg needs a +//! reference that exposes logits (Candle) and stays a P7 to-do. + +use std::path::PathBuf; +use std::process::Command; + +use mummu::backend::Gpu; +use mummu::models::lfm2; +use tokenizers::Tokenizer; + +const PROMPT: &str = "List the first five prime numbers."; +const MAX_TOKENS: usize = 24; + +fn lfm2_dir() -> Option { + let dir = PathBuf::from(std::env::var_os("MUMMU_LFM2_DIR")?); + dir.is_dir().then_some(dir) +} + +/// The LFM2.5 ChatML wrapping (BOS + user turn + assistant open). +fn chatml(user: &str) -> String { + format!("<|startoftext|><|im_start|>user\n{user}<|im_end|>\n<|im_start|>assistant\n") +} + +/// Greedy reference from Ollama in raw mode (identical prompt text, +/// temperature 0), via curl so the test needs no HTTP dependency. +fn ollama_greedy(raw_prompt: &str, max_tokens: usize) -> Result { + let body = serde_json::json!({ + "model": "lfm2.5", + "prompt": raw_prompt, + "raw": true, + "stream": false, + "options": { "temperature": 0.0, "num_predict": max_tokens } + }); + let out = Command::new("curl") + .args([ + "-s", + "-X", + "POST", + "http://localhost:11434/api/generate", + "-d", + ]) + .arg(body.to_string()) + .output() + .map_err(|e| format!("curl spawn: {e}"))?; + if !out.status.success() { + return Err(format!("curl exit {:?}", out.status.code())); + } + let v: serde_json::Value = + serde_json::from_slice(&out.stdout).map_err(|e| format!("ollama response parse: {e}"))?; + v.get("response") + .and_then(|r| r.as_str()) + .map(str::to_string) + .ok_or_else(|| format!("no response field in: {v}")) +} + +#[test] +#[ignore = "needs local LFM2.5 weights (MUMMU_LFM2_DIR) + a running Ollama with lfm2.5"] +fn lfm2_greedy_sequence_matches_ollama_reference() { + let Some(dir) = lfm2_dir() else { + panic!("set MUMMU_LFM2_DIR to a dir with config.json/tokenizer.json/model.safetensors"); + }; + let raw = chatml(PROMPT); + + // Reference first: skip cleanly (not red) when Ollama isn't up. + let reference = match ollama_greedy(&raw, MAX_TOKENS) { + Ok(r) => r, + Err(e) => panic!("Ollama reference unavailable: {e}"), + }; + + let tok = Tokenizer::from_file(dir.join("tokenizer.json")).expect("tokenizer.json loads"); + let prompt_ids = tok + .encode(raw.as_str(), false) + .expect("encodes") + .get_ids() + .to_vec(); + + let device = burn::tensor::Device::::default(); + let loaded = lfm2::load_from_dir::(&dir, &device).expect("weights load checked"); + let ids = loaded + .greedy_generate(&prompt_ids, MAX_TOKENS, &device) + .expect("greedy decode"); + let ours = tok.decode(&ids, true).expect("decode"); + + eprintln!("[parity/lfm2] ours ({} tokens): {ours:?}", ids.len()); + eprintln!("[parity/lfm2] ollama : {reference:?}"); + + // Exact-match the greedy prefix over the shorter of the two (EOS/num_predict + // may truncate one side earlier than the other). + let n = ours.trim().len().min(reference.trim().len()); + assert!(n >= 8, "outputs too short to compare: {n} chars"); + assert_eq!( + &ours.trim()[..n], + &reference.trim()[..n], + "greedy sequences diverge" + ); +} diff --git a/crates/mummu/tests/real_inference.rs b/crates/mummu/tests/real_inference.rs new file mode 100644 index 0000000..93b1ca0 --- /dev/null +++ b/crates/mummu/tests/real_inference.rs @@ -0,0 +1,104 @@ +//! Real-model smoke tests: load an actual Qwen2 checkpoint and decode on the +//! machine's default device. Ignored by default (multi-GB weights); run with +//! +//! ```text +//! MUMMU_QWEN2_DIR=path/to/qwen2.5-x.xb cargo test -p mummu --test real_inference -- --ignored --nocapture +//! ``` +//! +//! where the dir holds `config.json`, `tokenizer.json`, `model.safetensors`. + +use std::path::PathBuf; + +use mummu::backend::{Cpu, Gpu, use_gpu}; +use mummu::models::qwen2; +use tokenizers::Tokenizer; + +fn qwen2_dir() -> Option { + let dir = PathBuf::from(std::env::var_os("MUMMU_QWEN2_DIR")?); + dir.is_dir().then_some(dir) +} + +/// ChatML prompt for the Qwen2.5 instruct checkpoints. +fn chatml(system: &str, user: &str) -> String { + format!( + "<|im_start|>system\n{system}<|im_end|>\n<|im_start|>user\n{user}<|im_end|>\n<|im_start|>assistant\n" + ) +} + +fn encode(dir: &std::path::Path, text: &str) -> Vec { + let tok = Tokenizer::from_file(dir.join("tokenizer.json")).expect("tokenizer.json loads"); + tok.encode(text, true) + .expect("prompt encodes") + .get_ids() + .to_vec() +} + +fn decode(dir: &std::path::Path, ids: &[u32]) -> String { + let tok = Tokenizer::from_file(dir.join("tokenizer.json")).expect("tokenizer.json loads"); + tok.decode(ids, true).expect("ids decode") +} + +#[test] +#[ignore = "needs multi-GB local weights (MUMMU_QWEN2_DIR)"] +fn qwen2_greedy_decodes_coherent_text_on_default_device() { + let Some(dir) = qwen2_dir() else { + panic!("set MUMMU_QWEN2_DIR to a dir with config.json/tokenizer.json/model.safetensors"); + }; + let prompt = encode( + &dir, + &chatml( + "You are a concise assistant.", + "What is 2+2? Answer in one short sentence.", + ), + ); + + let (ids, label) = if use_gpu() { + let device = burn::tensor::Device::::default(); + let loaded = qwen2::load_from_dir::(&dir, &device).expect("weights load checked"); + ( + loaded + .greedy_generate(&prompt, 32, &device) + .expect("decode"), + "GPU", + ) + } else { + let device = burn::tensor::Device::::default(); + let loaded = qwen2::load_from_dir::(&dir, &device).expect("weights load checked"); + ( + loaded + .greedy_generate(&prompt, 32, &device) + .expect("decode"), + "CPU", + ) + }; + + assert!( + !ids.is_empty(), + "greedy decode produced no tokens before EOS" + ); + let text = decode(&dir, &ids); + eprintln!("[real_inference/{label}] {} tokens: {text:?}", ids.len()); + // Weakest useful coherence check that stays deterministic under greedy: + // the answer to 2+2 must mention 4. + assert!( + text.contains('4'), + "expected the answer to mention 4, got: {text:?}" + ); +} + +#[test] +#[ignore = "needs multi-GB local weights (MUMMU_QWEN2_DIR)"] +fn qwen2_first_token_probe_reports_top5() { + let Some(dir) = qwen2_dir() else { + panic!("set MUMMU_QWEN2_DIR to a dir with config.json/tokenizer.json/model.safetensors"); + }; + let prompt = encode(&dir, &chatml("You are a helpful assistant.", "Say hello.")); + let device = burn::tensor::Device::::default(); + let loaded = qwen2::load_from_dir::(&dir, &device).expect("weights load checked"); + let top5 = loaded.first_token(&prompt, 5, &device).expect("probe"); + assert_eq!(top5.len(), 5); + eprintln!( + "[real_inference] top-5 next-token ids: {top5:?} → {:?}", + decode(&dir, &top5[..1]) + ); +} From 72f616194c2a019b4706d6a4422a35fe399cb7f8 Mon Sep 17 00:00:00 2001 From: Justin Icenhour Date: Thu, 9 Jul 2026 11:02:25 -0500 Subject: [PATCH 5/7] =?UTF-8?q?build(deps):=20criterion=200.7=20=E2=86=92?= =?UTF-8?q?=200.8;=20release=20LTO=20fat=20=E2=86=92=20thin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cargo update + criterion major bump (dev-only; bench harness re-verified green). tokenizers 0.23 and wgpu 30 exist but stay pinned — the burn 0.21 / wgpu 29 / tokenizers 0.22 combination is the parity-validated set (see ROADMAP Stack). Thin LTO cuts the release verify-loop link from ~13 min to a fraction; inference is GPU-kernel-bound so fat LTO bought nothing measurable — revisit once bench/BASELINE.md exists. Co-Authored-By: Claude Opus 4.8 --- Cargo.lock | 29 +++++++++++++++++++++++++---- Cargo.toml | 9 +++++---- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e1c505..6a37b7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,6 +69,15 @@ dependencies = [ "equator", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -1169,10 +1178,11 @@ dependencies = [ [[package]] name = "criterion" -version = "0.7.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" dependencies = [ + "alloca", "anes", "cast", "ciborium", @@ -1181,6 +1191,7 @@ dependencies = [ "itertools 0.13.0", "num-traits", "oorandom", + "page_size", "plotters", "rayon", "regex", @@ -1192,9 +1203,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.6.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" dependencies = [ "cast", "itertools 0.13.0", @@ -3766,6 +3777,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "parking" version = "2.2.1" diff --git a/Cargo.toml b/Cargo.toml index 1a006a3..81bbd06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,9 +36,10 @@ serde_json = "1" thiserror = "2" once_cell = "1" -criterion = "0.7" +criterion = "0.8" [profile.release] -# Model inference is the hot path; fat LTO buys measurable decode throughput. -lto = "fat" -codegen-units = 1 +# Thin LTO: inference is GPU-kernel-bound, so fat LTO's extra minutes per link +# buy almost nothing here while tripling the verify-loop cost. Revisit against +# bench/BASELINE.md numbers if a CPU-bound path ever becomes hot. +lto = "thin" From 43123bafa11b7e87052f56fc502cb0375d5112ff Mon Sep 17 00:00:00 2001 From: Justin Icenhour Date: Thu, 9 Jul 2026 11:06:31 -0500 Subject: [PATCH 6/7] =?UTF-8?q?feat(p2):=20all-MiniLM=20sentence=20embedde?= =?UTF-8?q?r=20=E2=80=94=20post-LN=20BERT,=20masked-mean=20pool,=20L2=20no?= =?UTF-8?q?rmalize?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Third model in the zoo, generic over B: Backend; ids+mask in, unit-norm embedding out (tokenization stays caller-side, keeping the lib app-agnostic). Unit tests incl. padding-invisibility; real-weights semantic check on the actual checkpoint: paraphrase cosine 0.556 vs cross-topic -0.02/-0.00, unit norm holds. Co-Authored-By: Claude Opus 4.8 --- README.md | 7 +- ROADMAP.md | 4 +- crates/mummu/src/models/minilm.rs | 366 ++++++++++++++++++++++++++++++ crates/mummu/src/models/mod.rs | 1 + crates/mummu/tests/real_minilm.rs | 60 +++++ 5 files changed, 434 insertions(+), 4 deletions(-) create mode 100644 crates/mummu/src/models/minilm.rs create mode 100644 crates/mummu/tests/real_minilm.rs diff --git a/README.md b/README.md index 1bbce88..6d30e7a 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,10 @@ It exists because two local-first apps — **[laurelane](https://github.com/phys unit tests prove prefill+decode ≡ full-forward for both cache kinds. - **Checked safetensors import** — bf16→backend-float cast adapter, per-architecture key remaps, and a fail-loud load (never silently zero-init); `config.json`-driven hyperparameters. -- **Two decoders ported and running on real weights** — Qwen2/2.5 and the LFM2/2.5 hybrid; Qwen2.5-1.5B - loads and greedy-decodes correctly on the reference GPU (wgpu/Vulkan). The P7 parity harness will gate - them "trusted" against a reference before the roadmap ticks them done. +- **Three models ported and running on real weights** — Qwen2/2.5, the LFM2/2.5 hybrid, and the + all-MiniLM sentence embedder; Qwen2.5-1.5B and LFM2.5-1.2B load and greedy-decode correctly on the + reference GPU (wgpu/Vulkan). The P7 parity harness will gate them "trusted" against a reference before + the roadmap ticks them done. - **Model-cache disk accounting** — per-model disk usage + traversal-safe removal validation (`manage`). ## Design principles diff --git a/ROADMAP.md b/ROADMAP.md index 9e78943..1b31496 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -84,7 +84,9 @@ a benchmark holds/improves its budget; README perf claims link an artifact. (verified via `ollama show` 2026-07-09) — not the same weights, so no valid local reference exists; see the P7 reference item.* - [ ] **all-MiniLM** BERT sentence-embedder (6-layer post-LN bidirectional attention + GeLU FFN, - masked-mean-pool + L2-normalize). + masked-mean-pool + L2-normalize). *(2026-07-09) Ported (`models::minilm`, ids+mask in → L2-normalized + embedding out; tokenization stays caller-side); unit tests incl. padding-invisibility; real-weights + semantic test (`tests/real_minilm.rs`). Stays `[ ]` until the P7 cosine-vs-Candle parity re-runs here.* - [ ] A `Model` trait so new architectures (Hermes-class function-callers, Gemma, Qwen3, …) slot in. ### P3 — Model import suite (any source / any format → a running model) diff --git a/crates/mummu/src/models/minilm.rs b/crates/mummu/src/models/minilm.rs new file mode 100644 index 0000000..a56593f --- /dev/null +++ b/crates/mummu/src/models/minilm.rs @@ -0,0 +1,366 @@ +//! all-MiniLM-class sentence embedder: a 6-layer post-LayerNorm BERT with +//! absolute position embeddings, full bidirectional attention over an additive +//! padding mask, exact (erf) GeLU, masked-mean pooling + L2 normalize (the +//! sentence-transformers recipe — the checkpoint's `pooler.*` weights are +//! intentionally unused). +//! +//! Ported from laurelane's implementation (cosine ~1.0 vs the Candle BERT on +//! identical weights). BERT's bidirectional attention and LayerNorm differ +//! from the causal GQA blocks in `nn`, so this file is self-contained. +//! +//! The library takes token ids + attention mask and returns embeddings; the +//! caller owns tokenization (HF `tokenizers` in the integration tests). + +use std::path::Path; + +use burn::module::Module; +use burn::nn::{Embedding, EmbeddingConfig, LayerNorm, LayerNormConfig, Linear, LinearConfig}; +use burn::store::{ModuleAdapter, PyTorchToBurnAdapter, SafetensorsStore}; +use burn::tensor::{Int, Tensor, TensorData, activation, backend::Backend}; + +use crate::import::{CastFloatAdapter, ImportError, load_checked, required_file}; + +/// BERT hyperparameters, read from the checkpoint's `config.json`. +#[derive(Debug, Clone, serde::Deserialize)] +pub struct BertConfig { + pub vocab_size: usize, + pub hidden_size: usize, + pub num_hidden_layers: usize, + pub num_attention_heads: usize, + pub intermediate_size: usize, + pub max_position_embeddings: usize, + pub type_vocab_size: usize, + #[serde(default = "default_eps")] + pub layer_norm_eps: f64, +} + +fn default_eps() -> f64 { + 1e-12 +} + +impl BertConfig { + /// Parse `config.json` bytes. + pub fn from_json_bytes(bytes: &[u8]) -> Result { + let cfg: Self = serde_json::from_slice(bytes).map_err(|e| e.to_string())?; + if cfg.num_attention_heads == 0 || !cfg.hidden_size.is_multiple_of(cfg.num_attention_heads) + { + return Err(format!( + "hidden_size ({}) must be a positive multiple of num_attention_heads ({})", + cfg.hidden_size, cfg.num_attention_heads + )); + } + Ok(cfg) + } +} + +/// Word + position + token-type embeddings, post-LN. +#[derive(Module, Debug)] +pub struct Embeddings { + pub word_embeddings: Embedding, + pub position_embeddings: Embedding, + pub token_type_embeddings: Embedding, + pub layer_norm: LayerNorm, +} + +/// One post-LN BERT encoder layer (flat field names; HF's nested keys remap +/// onto these at load). +#[derive(Module, Debug)] +pub struct EncoderLayer { + pub query: Linear, + pub key: Linear, + pub value: Linear, + pub attn_output: Linear, + pub attn_layer_norm: LayerNorm, + pub intermediate: Linear, + pub output: Linear, + pub output_layer_norm: LayerNorm, +} + +/// The BERT encoder stack. +#[derive(Module, Debug)] +pub struct Bert { + pub embeddings: Embeddings, + pub layers: Vec>, +} + +/// A weight-loaded MiniLM/BERT plus its config. +pub struct LoadedMiniLm { + pub model: Bert, + pub config: BertConfig, +} + +fn build(cfg: &BertConfig, device: &B::Device) -> Bert { + let h = cfg.hidden_size; + let eps = cfg.layer_norm_eps; + let lin = |i: usize, o: usize| LinearConfig::new(i, o).init(device); + let ln = || LayerNormConfig::new(h).with_epsilon(eps).init(device); + let layers = (0..cfg.num_hidden_layers) + .map(|_| EncoderLayer { + query: lin(h, h), + key: lin(h, h), + value: lin(h, h), + attn_output: lin(h, h), + attn_layer_norm: ln(), + intermediate: lin(h, cfg.intermediate_size), + output: lin(cfg.intermediate_size, h), + output_layer_norm: ln(), + }) + .collect(); + Bert { + embeddings: Embeddings { + word_embeddings: EmbeddingConfig::new(cfg.vocab_size, h).init(device), + position_embeddings: EmbeddingConfig::new(cfg.max_position_embeddings, h).init(device), + token_type_embeddings: EmbeddingConfig::new(cfg.type_vocab_size, h).init(device), + layer_norm: ln(), + }, + layers, + } +} + +/// Build from `dir/config.json` and load `dir/model.safetensors`, checked. +/// NOTE: LayerNorm keys keep their `.weight`/`.bias` suffixes — the +/// `PyTorchToBurnAdapter` renames those to `gamma`/`beta` itself (unlike +/// RmsNorm in the decoder models, where the rename is manual — the asymmetry +/// is intentional). `pooler.*` stays unused (masked-mean pooling instead). +pub fn load_from_dir( + dir: &Path, + device: &B::Device, +) -> Result, ImportError> { + let cfg_path = required_file(dir, "config.json")?; + let weights = required_file(dir, "model.safetensors")?; + let cfg_bytes = std::fs::read(&cfg_path).map_err(|e| ImportError::Parse { + file: cfg_path.clone(), + reason: e.to_string(), + })?; + let config = BertConfig::from_json_bytes(&cfg_bytes).map_err(|reason| ImportError::Parse { + file: cfg_path, + reason, + })?; + + let mut model = build::(&config, device); + let target_float = Tensor::::zeros([1], device).dtype(); + let mut store = SafetensorsStore::from_file(weights.clone()) + .with_from_adapter(PyTorchToBurnAdapter.chain(CastFloatAdapter::new(target_float))) + .allow_partial(true) + .with_key_remapping(r"^bert\.", "") // some checkpoints prefix `bert.` + .with_key_remapping(r"^embeddings\.LayerNorm\.", "embeddings.layer_norm.") + .with_key_remapping( + r"^encoder\.layer\.(\d+)\.attention\.self\.(query|key|value)\.", + "layers.$1.$2.", + ) + .with_key_remapping( + r"^encoder\.layer\.(\d+)\.attention\.output\.dense\.", + "layers.$1.attn_output.", + ) + .with_key_remapping( + r"^encoder\.layer\.(\d+)\.attention\.output\.LayerNorm\.", + "layers.$1.attn_layer_norm.", + ) + .with_key_remapping( + r"^encoder\.layer\.(\d+)\.intermediate\.dense\.", + "layers.$1.intermediate.", + ) + .with_key_remapping( + r"^encoder\.layer\.(\d+)\.output\.dense\.", + "layers.$1.output.", + ) + .with_key_remapping( + r"^encoder\.layer\.(\d+)\.output\.LayerNorm\.", + "layers.$1.output_layer_norm.", + ); + load_checked(&mut model, &mut store, &weights)?; + Ok(LoadedMiniLm { model, config }) +} + +fn embeddings_forward( + e: &Embeddings, + ids: &Tensor, + device: &B::Device, +) -> Tensor { + let [b, n] = ids.dims(); + let w = e.word_embeddings.forward(ids.clone()); // [b, n, h] + let pos = Tensor::::arange(0..n as i64, device).reshape([1, n]); + let p = e.position_embeddings.forward(pos); // [1, n, h] + let tt = Tensor::::zeros([b, n], device); + let t = e.token_type_embeddings.forward(tt); // [b, n, h] + e.layer_norm.forward(w.add(p).add(t)) +} + +fn layer_forward( + l: &EncoderLayer, + x: Tensor, + add_mask: &Tensor, + num_heads: usize, +) -> Tensor { + let [b, n, h] = x.dims(); + debug_assert!(h.is_multiple_of(num_heads), "hidden not divisible by heads"); + let hd = h / num_heads; + + let q = l + .query + .forward(x.clone()) + .reshape([b, n, num_heads, hd]) + .swap_dims(1, 2); + let k = l + .key + .forward(x.clone()) + .reshape([b, n, num_heads, hd]) + .swap_dims(1, 2); + let v = l + .value + .forward(x.clone()) + .reshape([b, n, num_heads, hd]) + .swap_dims(1, 2); + + let scale = 1.0 / (hd as f32).sqrt(); + let scores = q + .matmul(k.swap_dims(2, 3)) + .mul_scalar(scale) + .add(add_mask.clone()); + let probs = activation::softmax(scores, 3); + let ctx = probs.matmul(v).swap_dims(1, 2).reshape([b, n, h]); + + // Self-attention output: dense → LayerNorm(+ residual). + let x = l.attn_layer_norm.forward(l.attn_output.forward(ctx).add(x)); + // Feed-forward: dense → exact GeLU → dense → LayerNorm(+ residual). + let inter = activation::gelu(l.intermediate.forward(x.clone())); + l.output_layer_norm.forward(l.output.forward(inter).add(x)) +} + +impl LoadedMiniLm { + /// Encode one tokenized string (`ids` + `mask`, 1.0 = real token) into a + /// masked-mean-pooled, **L2-normalized** sentence embedding of + /// `hidden_size` floats. + pub fn embed_ids( + &self, + ids: &[u32], + mask: &[f32], + device: &B::Device, + ) -> Result, String> { + let n = ids.len(); + assert!(n >= 1, "embed_ids: empty token sequence"); + assert!( + mask.len() == n, + "embed_ids: mask length {} != ids length {n}", + mask.len() + ); + debug_assert!( + n <= self.config.max_position_embeddings, + "embed_ids: sequence longer than max_position_embeddings" + ); + + let ids32: Vec = ids.iter().map(|&i| i as i32).collect(); + let id_t = + Tensor::::from_data(TensorData::new(ids32, [n]), device).reshape([1, n]); + let mask_t = + Tensor::::from_data(TensorData::new(mask.to_vec(), [n]), device).reshape([1, n]); + + // Additive padding mask [1, 1, 1, n]: 0 for real tokens, large-negative + // for padding, broadcast across heads and query positions. + let add_mask = mask_t + .clone() + .reshape([1, 1, 1, n]) + .neg() + .add_scalar(1.0) + .mul_scalar(-1e30); + + let mut x = embeddings_forward(&self.model.embeddings, &id_t, device); + for layer in &self.model.layers { + x = layer_forward(layer, x, &add_mask, self.config.num_attention_heads); + } + + // Masked mean pool → L2 normalize. + let m3 = mask_t.reshape([1, n, 1]); + let summed = x.mul(m3.clone()).sum_dim(1); // [1, 1, h] + let counts = m3.sum_dim(1); // [1, 1, 1] + let pooled = summed.div(counts).reshape([1, self.config.hidden_size]); + let norm = pooled.clone().powf_scalar(2.0).sum_dim(1).sqrt(); // [1, 1] + let normalized = pooled.div(norm); + + normalized + .into_data() + .convert::() + .to_vec::() + .map_err(|e| format!("embedding readback: {e:?}")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::backend::Cpu; + + type Dev = burn::tensor::Device; + + fn toy_config() -> BertConfig { + BertConfig { + vocab_size: 50, + hidden_size: 16, + num_hidden_layers: 2, + num_attention_heads: 4, + intermediate_size: 32, + max_position_embeddings: 64, + type_vocab_size: 2, + layer_norm_eps: 1e-12, + } + } + + #[test] + fn config_rejects_indivisible_heads() { + let json = br#"{ + "vocab_size": 50, "hidden_size": 15, "num_hidden_layers": 1, + "num_attention_heads": 4, "intermediate_size": 32, + "max_position_embeddings": 64, "type_vocab_size": 2 + }"#; + assert!(BertConfig::from_json_bytes(json).is_err()); + } + + #[test] + fn embeddings_are_unit_norm() { + let device = Dev::default(); + let cfg = toy_config(); + let loaded = LoadedMiniLm:: { + model: build(&cfg, &device), + config: cfg, + }; + let ids = [3u32, 7, 12, 4]; + let mask = [1.0f32; 4]; + let e = loaded.embed_ids(&ids, &mask, &device).unwrap(); + assert_eq!(e.len(), 16); + let norm: f32 = e.iter().map(|v| v * v).sum::().sqrt(); + assert!((norm - 1.0).abs() < 1e-4, "L2 norm should be 1, got {norm}"); + } + + /// Padding must not change the embedding: the padded positions are masked + /// out of attention AND the mean pool. + #[test] + fn padding_is_invisible_to_the_embedding() { + let device = Dev::default(); + let cfg = toy_config(); + let loaded = LoadedMiniLm:: { + model: build(&cfg, &device), + config: cfg, + }; + let bare = loaded + .embed_ids(&[3, 7, 12], &[1.0, 1.0, 1.0], &device) + .unwrap(); + let padded = loaded + .embed_ids(&[3, 7, 12, 0, 0], &[1.0, 1.0, 1.0, 0.0, 0.0], &device) + .unwrap(); + for (i, (a, b)) in bare.iter().zip(&padded).enumerate() { + assert!((a - b).abs() < 1e-4, "elem {i}: bare {a} vs padded {b}"); + } + } + + #[test] + #[should_panic(expected = "mask length")] + fn embed_rejects_mismatched_mask() { + let device = Dev::default(); + let cfg = toy_config(); + let loaded = LoadedMiniLm:: { + model: build(&cfg, &device), + config: cfg, + }; + let _ = loaded.embed_ids(&[1, 2, 3], &[1.0, 1.0], &device); + } +} diff --git a/crates/mummu/src/models/mod.rs b/crates/mummu/src/models/mod.rs index d1686f9..c6de96b 100644 --- a/crates/mummu/src/models/mod.rs +++ b/crates/mummu/src/models/mod.rs @@ -3,4 +3,5 @@ //! checkpoint's `config.json`, never hardcoded). pub mod lfm2; +pub mod minilm; pub mod qwen2; diff --git a/crates/mummu/tests/real_minilm.rs b/crates/mummu/tests/real_minilm.rs new file mode 100644 index 0000000..f394b47 --- /dev/null +++ b/crates/mummu/tests/real_minilm.rs @@ -0,0 +1,60 @@ +//! Real-weights MiniLM smoke: load the actual all-MiniLM checkpoint on the +//! CPU backend and check the embedding space behaves semantically. Ignored by +//! default; run with +//! +//! ```text +//! MUMMU_MINILM_DIR=path/to/minilm cargo test -p mummu --test real_minilm -- --ignored --nocapture +//! ``` + +use std::path::PathBuf; + +use mummu::backend::Cpu; +use mummu::models::minilm; +use tokenizers::Tokenizer; + +fn minilm_dir() -> Option { + let dir = PathBuf::from(std::env::var_os("MUMMU_MINILM_DIR")?); + dir.is_dir().then_some(dir) +} + +fn cosine(a: &[f32], b: &[f32]) -> f32 { + // Embeddings are L2-normalized, so the dot product IS the cosine. + a.iter().zip(b).map(|(x, y)| x * y).sum() +} + +#[test] +#[ignore = "needs local MiniLM weights (MUMMU_MINILM_DIR)"] +fn minilm_embeds_similar_sentences_closer_than_dissimilar() { + let Some(dir) = minilm_dir() else { + panic!("set MUMMU_MINILM_DIR to a dir with config.json/tokenizer.json/model.safetensors"); + }; + let tok = Tokenizer::from_file(dir.join("tokenizer.json")).expect("tokenizer.json loads"); + let device = burn::tensor::Device::::default(); + let loaded = minilm::load_from_dir::(&dir, &device).expect("weights load checked"); + + let embed = |text: &str| -> Vec { + let enc = tok.encode(text, true).expect("encodes"); + let ids = enc.get_ids().to_vec(); + let mask: Vec = enc.get_attention_mask().iter().map(|&m| m as f32).collect(); + loaded.embed_ids(&ids, &mask, &device).expect("embeds") + }; + + let cat1 = embed("The cat sat on the mat."); + let cat2 = embed("A feline rested on the rug."); + let finance = embed("Quarterly revenue grew twelve percent year over year."); + + let sim = cosine(&cat1, &cat2); + let dis1 = cosine(&cat1, &finance); + let dis2 = cosine(&cat2, &finance); + eprintln!("[real_minilm] cat~cat = {sim:.4}, cat~finance = {dis1:.4}/{dis2:.4}"); + + assert!(sim > 0.5, "paraphrases should be similar, got {sim}"); + assert!( + sim > dis1 + 0.2 && sim > dis2 + 0.2, + "paraphrase similarity ({sim}) should clearly beat cross-topic ({dis1}, {dis2})" + ); + + // Unit norm on real weights too. + let norm: f32 = cat1.iter().map(|v| v * v).sum::().sqrt(); + assert!((norm - 1.0).abs() < 1e-3, "L2 norm should be 1, got {norm}"); +} From 4fd7550c335baeafedc8813bed4ca074e0866c18 Mon Sep 17 00:00:00 2001 From: Justin Icenhour Date: Thu, 9 Jul 2026 11:07:46 -0500 Subject: [PATCH 7/7] chore: ignore nested target/ dirs (CubeCL autotune cache lands next to the test cwd) Co-Authored-By: Claude Opus 4.8 --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4002ac4..be65e98 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Rust -/target +target/ **/*.rs.bk *.pdb