amaye15 commited on
Commit
a551379
·
verified ·
1 Parent(s): 6a5fabe

Upload model files

Browse files
.gitattributes CHANGED
@@ -386,3 +386,18 @@ target/release/deps/libzmij-53e8da00082a7bf8.rlib filter=lfs diff=lfs merge=lfs
386
  target/release/deps/toto_rs-224c98ba8d931f9e filter=lfs diff=lfs merge=lfs -text
387
  target/release/deps/toto_rs-b2be7fc1c6abc0d1 filter=lfs diff=lfs merge=lfs -text
388
  target/release/toto-rs filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
  target/release/deps/toto_rs-224c98ba8d931f9e filter=lfs diff=lfs merge=lfs -text
387
  target/release/deps/toto_rs-b2be7fc1c6abc0d1 filter=lfs diff=lfs merge=lfs -text
388
  target/release/toto-rs filter=lfs diff=lfs merge=lfs -text
389
+ toto-rs/gguf/toto-1b-f16.gguf filter=lfs diff=lfs merge=lfs -text
390
+ toto-rs/gguf/toto-1b-f32.gguf filter=lfs diff=lfs merge=lfs -text
391
+ toto-rs/gguf/toto-1b-q8.gguf filter=lfs diff=lfs merge=lfs -text
392
+ toto-rs/gguf/toto-2.5b-f16.gguf filter=lfs diff=lfs merge=lfs -text
393
+ toto-rs/gguf/toto-2.5b-f32.gguf filter=lfs diff=lfs merge=lfs -text
394
+ toto-rs/gguf/toto-2.5b-q8.gguf filter=lfs diff=lfs merge=lfs -text
395
+ toto-rs/gguf/toto-22m-f16.gguf filter=lfs diff=lfs merge=lfs -text
396
+ toto-rs/gguf/toto-22m-f32.gguf filter=lfs diff=lfs merge=lfs -text
397
+ toto-rs/gguf/toto-22m-q8.gguf filter=lfs diff=lfs merge=lfs -text
398
+ toto-rs/gguf/toto-313m-f16.gguf filter=lfs diff=lfs merge=lfs -text
399
+ toto-rs/gguf/toto-313m-f32.gguf filter=lfs diff=lfs merge=lfs -text
400
+ toto-rs/gguf/toto-313m-q8.gguf filter=lfs diff=lfs merge=lfs -text
401
+ toto-rs/gguf/toto-4m-f16.gguf filter=lfs diff=lfs merge=lfs -text
402
+ toto-rs/gguf/toto-4m-f32.gguf filter=lfs diff=lfs merge=lfs -text
403
+ toto-rs/gguf/toto-4m-q8.gguf filter=lfs diff=lfs merge=lfs -text
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.12
pyproject.toml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "toto-v2-env"
3
+ version = "0.1.0"
4
+ requires-python = ">=3.10"
5
+ dependencies = ["toto-ts>=0.2.0"]
6
+
7
+ [tool.uv]
8
+ package = false
toto-rs/.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ /target
2
+ /models
3
+ /gguf
4
+ /.venv
5
+ /toto
6
+ *.gguf
toto-rs/Cargo.lock ADDED
@@ -0,0 +1,2860 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "accelerate-src"
7
+ version = "0.3.2"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "415ed64958754dbe991900f3940677e6a7eefb4d7367afd70d642677b0c7d19d"
10
+
11
+ [[package]]
12
+ name = "anstream"
13
+ version = "1.0.0"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
16
+ dependencies = [
17
+ "anstyle",
18
+ "anstyle-parse",
19
+ "anstyle-query",
20
+ "anstyle-wincon",
21
+ "colorchoice",
22
+ "is_terminal_polyfill",
23
+ "utf8parse",
24
+ ]
25
+
26
+ [[package]]
27
+ name = "anstyle"
28
+ version = "1.0.14"
29
+ source = "registry+https://github.com/rust-lang/crates.io-index"
30
+ checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
31
+
32
+ [[package]]
33
+ name = "anstyle-parse"
34
+ version = "1.0.0"
35
+ source = "registry+https://github.com/rust-lang/crates.io-index"
36
+ checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
37
+ dependencies = [
38
+ "utf8parse",
39
+ ]
40
+
41
+ [[package]]
42
+ name = "anstyle-query"
43
+ version = "1.1.5"
44
+ source = "registry+https://github.com/rust-lang/crates.io-index"
45
+ checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
46
+ dependencies = [
47
+ "windows-sys 0.61.2",
48
+ ]
49
+
50
+ [[package]]
51
+ name = "anstyle-wincon"
52
+ version = "3.0.11"
53
+ source = "registry+https://github.com/rust-lang/crates.io-index"
54
+ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
55
+ dependencies = [
56
+ "anstyle",
57
+ "once_cell_polyfill",
58
+ "windows-sys 0.61.2",
59
+ ]
60
+
61
+ [[package]]
62
+ name = "anyhow"
63
+ version = "1.0.102"
64
+ source = "registry+https://github.com/rust-lang/crates.io-index"
65
+ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
66
+
67
+ [[package]]
68
+ name = "arbitrary"
69
+ version = "1.4.2"
70
+ source = "registry+https://github.com/rust-lang/crates.io-index"
71
+ checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
72
+ dependencies = [
73
+ "derive_arbitrary",
74
+ ]
75
+
76
+ [[package]]
77
+ name = "autocfg"
78
+ version = "1.5.1"
79
+ source = "registry+https://github.com/rust-lang/crates.io-index"
80
+ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
81
+
82
+ [[package]]
83
+ name = "base64"
84
+ version = "0.21.7"
85
+ source = "registry+https://github.com/rust-lang/crates.io-index"
86
+ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
87
+
88
+ [[package]]
89
+ name = "base64"
90
+ version = "0.22.1"
91
+ source = "registry+https://github.com/rust-lang/crates.io-index"
92
+ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
93
+
94
+ [[package]]
95
+ name = "bitflags"
96
+ version = "1.3.2"
97
+ source = "registry+https://github.com/rust-lang/crates.io-index"
98
+ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
99
+
100
+ [[package]]
101
+ name = "bitflags"
102
+ version = "2.11.1"
103
+ source = "registry+https://github.com/rust-lang/crates.io-index"
104
+ checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
105
+
106
+ [[package]]
107
+ name = "block-buffer"
108
+ version = "0.10.4"
109
+ source = "registry+https://github.com/rust-lang/crates.io-index"
110
+ checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
111
+ dependencies = [
112
+ "generic-array",
113
+ ]
114
+
115
+ [[package]]
116
+ name = "bumpalo"
117
+ version = "3.20.3"
118
+ source = "registry+https://github.com/rust-lang/crates.io-index"
119
+ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
120
+
121
+ [[package]]
122
+ name = "bytemuck"
123
+ version = "1.25.0"
124
+ source = "registry+https://github.com/rust-lang/crates.io-index"
125
+ checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
126
+ dependencies = [
127
+ "bytemuck_derive",
128
+ ]
129
+
130
+ [[package]]
131
+ name = "bytemuck_derive"
132
+ version = "1.10.2"
133
+ source = "registry+https://github.com/rust-lang/crates.io-index"
134
+ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff"
135
+ dependencies = [
136
+ "proc-macro2",
137
+ "quote",
138
+ "syn",
139
+ ]
140
+
141
+ [[package]]
142
+ name = "byteorder"
143
+ version = "1.5.0"
144
+ source = "registry+https://github.com/rust-lang/crates.io-index"
145
+ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
146
+
147
+ [[package]]
148
+ name = "bytes"
149
+ version = "1.11.1"
150
+ source = "registry+https://github.com/rust-lang/crates.io-index"
151
+ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
152
+
153
+ [[package]]
154
+ name = "candle-core"
155
+ version = "0.8.4"
156
+ source = "registry+https://github.com/rust-lang/crates.io-index"
157
+ checksum = "06ccf5ee3532e66868516d9b315f73aec9f34ea1a37ae98514534d458915dbf1"
158
+ dependencies = [
159
+ "accelerate-src",
160
+ "byteorder",
161
+ "gemm 0.17.1",
162
+ "half",
163
+ "libc",
164
+ "memmap2",
165
+ "num-traits",
166
+ "num_cpus",
167
+ "rand",
168
+ "rand_distr",
169
+ "rayon",
170
+ "safetensors",
171
+ "thiserror",
172
+ "ug",
173
+ "yoke 0.7.5",
174
+ "zip",
175
+ ]
176
+
177
+ [[package]]
178
+ name = "candle-nn"
179
+ version = "0.8.4"
180
+ source = "registry+https://github.com/rust-lang/crates.io-index"
181
+ checksum = "be1160c3b63f47d40d91110a3e1e1e566ae38edddbbf492a60b40ffc3bc1ff38"
182
+ dependencies = [
183
+ "accelerate-src",
184
+ "candle-core",
185
+ "half",
186
+ "num-traits",
187
+ "rayon",
188
+ "safetensors",
189
+ "serde",
190
+ "thiserror",
191
+ ]
192
+
193
+ [[package]]
194
+ name = "cc"
195
+ version = "1.2.62"
196
+ source = "registry+https://github.com/rust-lang/crates.io-index"
197
+ checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
198
+ dependencies = [
199
+ "find-msvc-tools",
200
+ "shlex",
201
+ ]
202
+
203
+ [[package]]
204
+ name = "cfg-if"
205
+ version = "1.0.4"
206
+ source = "registry+https://github.com/rust-lang/crates.io-index"
207
+ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
208
+
209
+ [[package]]
210
+ name = "clap"
211
+ version = "4.6.1"
212
+ source = "registry+https://github.com/rust-lang/crates.io-index"
213
+ checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
214
+ dependencies = [
215
+ "clap_builder",
216
+ "clap_derive",
217
+ ]
218
+
219
+ [[package]]
220
+ name = "clap_builder"
221
+ version = "4.6.0"
222
+ source = "registry+https://github.com/rust-lang/crates.io-index"
223
+ checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
224
+ dependencies = [
225
+ "anstream",
226
+ "anstyle",
227
+ "clap_lex",
228
+ "strsim",
229
+ ]
230
+
231
+ [[package]]
232
+ name = "clap_derive"
233
+ version = "4.6.1"
234
+ source = "registry+https://github.com/rust-lang/crates.io-index"
235
+ checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
236
+ dependencies = [
237
+ "heck",
238
+ "proc-macro2",
239
+ "quote",
240
+ "syn",
241
+ ]
242
+
243
+ [[package]]
244
+ name = "clap_lex"
245
+ version = "1.1.0"
246
+ source = "registry+https://github.com/rust-lang/crates.io-index"
247
+ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
248
+
249
+ [[package]]
250
+ name = "colorchoice"
251
+ version = "1.0.5"
252
+ source = "registry+https://github.com/rust-lang/crates.io-index"
253
+ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
254
+
255
+ [[package]]
256
+ name = "console"
257
+ version = "0.15.11"
258
+ source = "registry+https://github.com/rust-lang/crates.io-index"
259
+ checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
260
+ dependencies = [
261
+ "encode_unicode",
262
+ "libc",
263
+ "once_cell",
264
+ "unicode-width",
265
+ "windows-sys 0.59.0",
266
+ ]
267
+
268
+ [[package]]
269
+ name = "core-foundation"
270
+ version = "0.9.4"
271
+ source = "registry+https://github.com/rust-lang/crates.io-index"
272
+ checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
273
+ dependencies = [
274
+ "core-foundation-sys",
275
+ "libc",
276
+ ]
277
+
278
+ [[package]]
279
+ name = "core-foundation"
280
+ version = "0.10.1"
281
+ source = "registry+https://github.com/rust-lang/crates.io-index"
282
+ checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
283
+ dependencies = [
284
+ "core-foundation-sys",
285
+ "libc",
286
+ ]
287
+
288
+ [[package]]
289
+ name = "core-foundation-sys"
290
+ version = "0.8.7"
291
+ source = "registry+https://github.com/rust-lang/crates.io-index"
292
+ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
293
+
294
+ [[package]]
295
+ name = "cpufeatures"
296
+ version = "0.2.17"
297
+ source = "registry+https://github.com/rust-lang/crates.io-index"
298
+ checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
299
+ dependencies = [
300
+ "libc",
301
+ ]
302
+
303
+ [[package]]
304
+ name = "crc32fast"
305
+ version = "1.5.0"
306
+ source = "registry+https://github.com/rust-lang/crates.io-index"
307
+ checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
308
+ dependencies = [
309
+ "cfg-if",
310
+ ]
311
+
312
+ [[package]]
313
+ name = "crossbeam-deque"
314
+ version = "0.8.6"
315
+ source = "registry+https://github.com/rust-lang/crates.io-index"
316
+ checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
317
+ dependencies = [
318
+ "crossbeam-epoch",
319
+ "crossbeam-utils",
320
+ ]
321
+
322
+ [[package]]
323
+ name = "crossbeam-epoch"
324
+ version = "0.9.18"
325
+ source = "registry+https://github.com/rust-lang/crates.io-index"
326
+ checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
327
+ dependencies = [
328
+ "crossbeam-utils",
329
+ ]
330
+
331
+ [[package]]
332
+ name = "crossbeam-utils"
333
+ version = "0.8.21"
334
+ source = "registry+https://github.com/rust-lang/crates.io-index"
335
+ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
336
+
337
+ [[package]]
338
+ name = "crunchy"
339
+ version = "0.2.4"
340
+ source = "registry+https://github.com/rust-lang/crates.io-index"
341
+ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
342
+
343
+ [[package]]
344
+ name = "crypto-common"
345
+ version = "0.1.7"
346
+ source = "registry+https://github.com/rust-lang/crates.io-index"
347
+ checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
348
+ dependencies = [
349
+ "generic-array",
350
+ "typenum",
351
+ ]
352
+
353
+ [[package]]
354
+ name = "derive_arbitrary"
355
+ version = "1.4.2"
356
+ source = "registry+https://github.com/rust-lang/crates.io-index"
357
+ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
358
+ dependencies = [
359
+ "proc-macro2",
360
+ "quote",
361
+ "syn",
362
+ ]
363
+
364
+ [[package]]
365
+ name = "digest"
366
+ version = "0.10.7"
367
+ source = "registry+https://github.com/rust-lang/crates.io-index"
368
+ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
369
+ dependencies = [
370
+ "block-buffer",
371
+ "crypto-common",
372
+ ]
373
+
374
+ [[package]]
375
+ name = "displaydoc"
376
+ version = "0.2.5"
377
+ source = "registry+https://github.com/rust-lang/crates.io-index"
378
+ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
379
+ dependencies = [
380
+ "proc-macro2",
381
+ "quote",
382
+ "syn",
383
+ ]
384
+
385
+ [[package]]
386
+ name = "dyn-stack"
387
+ version = "0.10.0"
388
+ source = "registry+https://github.com/rust-lang/crates.io-index"
389
+ checksum = "56e53799688f5632f364f8fb387488dd05db9fe45db7011be066fc20e7027f8b"
390
+ dependencies = [
391
+ "bytemuck",
392
+ "reborrow",
393
+ ]
394
+
395
+ [[package]]
396
+ name = "dyn-stack"
397
+ version = "0.13.2"
398
+ source = "registry+https://github.com/rust-lang/crates.io-index"
399
+ checksum = "1c4713e43e2886ba72b8271aa66c93d722116acf7a75555cce11dcde84388fe8"
400
+ dependencies = [
401
+ "bytemuck",
402
+ "dyn-stack-macros",
403
+ ]
404
+
405
+ [[package]]
406
+ name = "dyn-stack-macros"
407
+ version = "0.1.3"
408
+ source = "registry+https://github.com/rust-lang/crates.io-index"
409
+ checksum = "e1d926b4d407d372f141f93bb444696142c29d32962ccbd3531117cf3aa0bfa9"
410
+
411
+ [[package]]
412
+ name = "either"
413
+ version = "1.16.0"
414
+ source = "registry+https://github.com/rust-lang/crates.io-index"
415
+ checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
416
+
417
+ [[package]]
418
+ name = "encode_unicode"
419
+ version = "1.0.0"
420
+ source = "registry+https://github.com/rust-lang/crates.io-index"
421
+ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
422
+
423
+ [[package]]
424
+ name = "encoding_rs"
425
+ version = "0.8.35"
426
+ source = "registry+https://github.com/rust-lang/crates.io-index"
427
+ checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
428
+ dependencies = [
429
+ "cfg-if",
430
+ ]
431
+
432
+ [[package]]
433
+ name = "enum-as-inner"
434
+ version = "0.6.1"
435
+ source = "registry+https://github.com/rust-lang/crates.io-index"
436
+ checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc"
437
+ dependencies = [
438
+ "heck",
439
+ "proc-macro2",
440
+ "quote",
441
+ "syn",
442
+ ]
443
+
444
+ [[package]]
445
+ name = "equivalent"
446
+ version = "1.0.2"
447
+ source = "registry+https://github.com/rust-lang/crates.io-index"
448
+ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
449
+
450
+ [[package]]
451
+ name = "errno"
452
+ version = "0.3.14"
453
+ source = "registry+https://github.com/rust-lang/crates.io-index"
454
+ checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
455
+ dependencies = [
456
+ "libc",
457
+ "windows-sys 0.61.2",
458
+ ]
459
+
460
+ [[package]]
461
+ name = "fastrand"
462
+ version = "2.4.1"
463
+ source = "registry+https://github.com/rust-lang/crates.io-index"
464
+ checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
465
+
466
+ [[package]]
467
+ name = "find-msvc-tools"
468
+ version = "0.1.9"
469
+ source = "registry+https://github.com/rust-lang/crates.io-index"
470
+ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
471
+
472
+ [[package]]
473
+ name = "fnv"
474
+ version = "1.0.7"
475
+ source = "registry+https://github.com/rust-lang/crates.io-index"
476
+ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
477
+
478
+ [[package]]
479
+ name = "foldhash"
480
+ version = "0.1.5"
481
+ source = "registry+https://github.com/rust-lang/crates.io-index"
482
+ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
483
+
484
+ [[package]]
485
+ name = "foreign-types"
486
+ version = "0.3.2"
487
+ source = "registry+https://github.com/rust-lang/crates.io-index"
488
+ checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
489
+ dependencies = [
490
+ "foreign-types-shared",
491
+ ]
492
+
493
+ [[package]]
494
+ name = "foreign-types-shared"
495
+ version = "0.1.1"
496
+ source = "registry+https://github.com/rust-lang/crates.io-index"
497
+ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
498
+
499
+ [[package]]
500
+ name = "form_urlencoded"
501
+ version = "1.2.2"
502
+ source = "registry+https://github.com/rust-lang/crates.io-index"
503
+ checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
504
+ dependencies = [
505
+ "percent-encoding",
506
+ ]
507
+
508
+ [[package]]
509
+ name = "futures-channel"
510
+ version = "0.3.32"
511
+ source = "registry+https://github.com/rust-lang/crates.io-index"
512
+ checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
513
+ dependencies = [
514
+ "futures-core",
515
+ ]
516
+
517
+ [[package]]
518
+ name = "futures-core"
519
+ version = "0.3.32"
520
+ source = "registry+https://github.com/rust-lang/crates.io-index"
521
+ checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
522
+
523
+ [[package]]
524
+ name = "futures-io"
525
+ version = "0.3.32"
526
+ source = "registry+https://github.com/rust-lang/crates.io-index"
527
+ checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
528
+
529
+ [[package]]
530
+ name = "futures-macro"
531
+ version = "0.3.32"
532
+ source = "registry+https://github.com/rust-lang/crates.io-index"
533
+ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
534
+ dependencies = [
535
+ "proc-macro2",
536
+ "quote",
537
+ "syn",
538
+ ]
539
+
540
+ [[package]]
541
+ name = "futures-sink"
542
+ version = "0.3.32"
543
+ source = "registry+https://github.com/rust-lang/crates.io-index"
544
+ checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
545
+
546
+ [[package]]
547
+ name = "futures-task"
548
+ version = "0.3.32"
549
+ source = "registry+https://github.com/rust-lang/crates.io-index"
550
+ checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
551
+
552
+ [[package]]
553
+ name = "futures-util"
554
+ version = "0.3.32"
555
+ source = "registry+https://github.com/rust-lang/crates.io-index"
556
+ checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
557
+ dependencies = [
558
+ "futures-core",
559
+ "futures-io",
560
+ "futures-macro",
561
+ "futures-sink",
562
+ "futures-task",
563
+ "memchr",
564
+ "pin-project-lite",
565
+ "slab",
566
+ ]
567
+
568
+ [[package]]
569
+ name = "gemm"
570
+ version = "0.17.1"
571
+ source = "registry+https://github.com/rust-lang/crates.io-index"
572
+ checksum = "6ab24cc62135b40090e31a76a9b2766a501979f3070fa27f689c27ec04377d32"
573
+ dependencies = [
574
+ "dyn-stack 0.10.0",
575
+ "gemm-c32 0.17.1",
576
+ "gemm-c64 0.17.1",
577
+ "gemm-common 0.17.1",
578
+ "gemm-f16 0.17.1",
579
+ "gemm-f32 0.17.1",
580
+ "gemm-f64 0.17.1",
581
+ "num-complex",
582
+ "num-traits",
583
+ "paste",
584
+ "raw-cpuid 10.7.0",
585
+ "seq-macro",
586
+ ]
587
+
588
+ [[package]]
589
+ name = "gemm"
590
+ version = "0.18.2"
591
+ source = "registry+https://github.com/rust-lang/crates.io-index"
592
+ checksum = "ab96b703d31950f1aeddded248bc95543c9efc7ac9c4a21fda8703a83ee35451"
593
+ dependencies = [
594
+ "dyn-stack 0.13.2",
595
+ "gemm-c32 0.18.2",
596
+ "gemm-c64 0.18.2",
597
+ "gemm-common 0.18.2",
598
+ "gemm-f16 0.18.2",
599
+ "gemm-f32 0.18.2",
600
+ "gemm-f64 0.18.2",
601
+ "num-complex",
602
+ "num-traits",
603
+ "paste",
604
+ "raw-cpuid 11.6.0",
605
+ "seq-macro",
606
+ ]
607
+
608
+ [[package]]
609
+ name = "gemm-c32"
610
+ version = "0.17.1"
611
+ source = "registry+https://github.com/rust-lang/crates.io-index"
612
+ checksum = "b9c030d0b983d1e34a546b86e08f600c11696fde16199f971cd46c12e67512c0"
613
+ dependencies = [
614
+ "dyn-stack 0.10.0",
615
+ "gemm-common 0.17.1",
616
+ "num-complex",
617
+ "num-traits",
618
+ "paste",
619
+ "raw-cpuid 10.7.0",
620
+ "seq-macro",
621
+ ]
622
+
623
+ [[package]]
624
+ name = "gemm-c32"
625
+ version = "0.18.2"
626
+ source = "registry+https://github.com/rust-lang/crates.io-index"
627
+ checksum = "f6db9fd9f40421d00eea9dd0770045a5603b8d684654816637732463f4073847"
628
+ dependencies = [
629
+ "dyn-stack 0.13.2",
630
+ "gemm-common 0.18.2",
631
+ "num-complex",
632
+ "num-traits",
633
+ "paste",
634
+ "raw-cpuid 11.6.0",
635
+ "seq-macro",
636
+ ]
637
+
638
+ [[package]]
639
+ name = "gemm-c64"
640
+ version = "0.17.1"
641
+ source = "registry+https://github.com/rust-lang/crates.io-index"
642
+ checksum = "fbb5f2e79fefb9693d18e1066a557b4546cd334b226beadc68b11a8f9431852a"
643
+ dependencies = [
644
+ "dyn-stack 0.10.0",
645
+ "gemm-common 0.17.1",
646
+ "num-complex",
647
+ "num-traits",
648
+ "paste",
649
+ "raw-cpuid 10.7.0",
650
+ "seq-macro",
651
+ ]
652
+
653
+ [[package]]
654
+ name = "gemm-c64"
655
+ version = "0.18.2"
656
+ source = "registry+https://github.com/rust-lang/crates.io-index"
657
+ checksum = "dfcad8a3d35a43758330b635d02edad980c1e143dc2f21e6fd25f9e4eada8edf"
658
+ dependencies = [
659
+ "dyn-stack 0.13.2",
660
+ "gemm-common 0.18.2",
661
+ "num-complex",
662
+ "num-traits",
663
+ "paste",
664
+ "raw-cpuid 11.6.0",
665
+ "seq-macro",
666
+ ]
667
+
668
+ [[package]]
669
+ name = "gemm-common"
670
+ version = "0.17.1"
671
+ source = "registry+https://github.com/rust-lang/crates.io-index"
672
+ checksum = "a2e7ea062c987abcd8db95db917b4ffb4ecdfd0668471d8dc54734fdff2354e8"
673
+ dependencies = [
674
+ "bytemuck",
675
+ "dyn-stack 0.10.0",
676
+ "half",
677
+ "num-complex",
678
+ "num-traits",
679
+ "once_cell",
680
+ "paste",
681
+ "pulp 0.18.22",
682
+ "raw-cpuid 10.7.0",
683
+ "rayon",
684
+ "seq-macro",
685
+ "sysctl 0.5.5",
686
+ ]
687
+
688
+ [[package]]
689
+ name = "gemm-common"
690
+ version = "0.18.2"
691
+ source = "registry+https://github.com/rust-lang/crates.io-index"
692
+ checksum = "a352d4a69cbe938b9e2a9cb7a3a63b7e72f9349174a2752a558a8a563510d0f3"
693
+ dependencies = [
694
+ "bytemuck",
695
+ "dyn-stack 0.13.2",
696
+ "half",
697
+ "libm",
698
+ "num-complex",
699
+ "num-traits",
700
+ "once_cell",
701
+ "paste",
702
+ "pulp 0.21.5",
703
+ "raw-cpuid 11.6.0",
704
+ "rayon",
705
+ "seq-macro",
706
+ "sysctl 0.6.0",
707
+ ]
708
+
709
+ [[package]]
710
+ name = "gemm-f16"
711
+ version = "0.17.1"
712
+ source = "registry+https://github.com/rust-lang/crates.io-index"
713
+ checksum = "7ca4c06b9b11952071d317604acb332e924e817bd891bec8dfb494168c7cedd4"
714
+ dependencies = [
715
+ "dyn-stack 0.10.0",
716
+ "gemm-common 0.17.1",
717
+ "gemm-f32 0.17.1",
718
+ "half",
719
+ "num-complex",
720
+ "num-traits",
721
+ "paste",
722
+ "raw-cpuid 10.7.0",
723
+ "rayon",
724
+ "seq-macro",
725
+ ]
726
+
727
+ [[package]]
728
+ name = "gemm-f16"
729
+ version = "0.18.2"
730
+ source = "registry+https://github.com/rust-lang/crates.io-index"
731
+ checksum = "cff95ae3259432f3c3410eaa919033cd03791d81cebd18018393dc147952e109"
732
+ dependencies = [
733
+ "dyn-stack 0.13.2",
734
+ "gemm-common 0.18.2",
735
+ "gemm-f32 0.18.2",
736
+ "half",
737
+ "num-complex",
738
+ "num-traits",
739
+ "paste",
740
+ "raw-cpuid 11.6.0",
741
+ "rayon",
742
+ "seq-macro",
743
+ ]
744
+
745
+ [[package]]
746
+ name = "gemm-f32"
747
+ version = "0.17.1"
748
+ source = "registry+https://github.com/rust-lang/crates.io-index"
749
+ checksum = "e9a69f51aaefbd9cf12d18faf273d3e982d9d711f60775645ed5c8047b4ae113"
750
+ dependencies = [
751
+ "dyn-stack 0.10.0",
752
+ "gemm-common 0.17.1",
753
+ "num-complex",
754
+ "num-traits",
755
+ "paste",
756
+ "raw-cpuid 10.7.0",
757
+ "seq-macro",
758
+ ]
759
+
760
+ [[package]]
761
+ name = "gemm-f32"
762
+ version = "0.18.2"
763
+ source = "registry+https://github.com/rust-lang/crates.io-index"
764
+ checksum = "bc8d3d4385393304f407392f754cd2dc4b315d05063f62cf09f47b58de276864"
765
+ dependencies = [
766
+ "dyn-stack 0.13.2",
767
+ "gemm-common 0.18.2",
768
+ "num-complex",
769
+ "num-traits",
770
+ "paste",
771
+ "raw-cpuid 11.6.0",
772
+ "seq-macro",
773
+ ]
774
+
775
+ [[package]]
776
+ name = "gemm-f64"
777
+ version = "0.17.1"
778
+ source = "registry+https://github.com/rust-lang/crates.io-index"
779
+ checksum = "aa397a48544fadf0b81ec8741e5c0fba0043008113f71f2034def1935645d2b0"
780
+ dependencies = [
781
+ "dyn-stack 0.10.0",
782
+ "gemm-common 0.17.1",
783
+ "num-complex",
784
+ "num-traits",
785
+ "paste",
786
+ "raw-cpuid 10.7.0",
787
+ "seq-macro",
788
+ ]
789
+
790
+ [[package]]
791
+ name = "gemm-f64"
792
+ version = "0.18.2"
793
+ source = "registry+https://github.com/rust-lang/crates.io-index"
794
+ checksum = "35b2a4f76ce4b8b16eadc11ccf2e083252d8237c1b589558a49b0183545015bd"
795
+ dependencies = [
796
+ "dyn-stack 0.13.2",
797
+ "gemm-common 0.18.2",
798
+ "num-complex",
799
+ "num-traits",
800
+ "paste",
801
+ "raw-cpuid 11.6.0",
802
+ "seq-macro",
803
+ ]
804
+
805
+ [[package]]
806
+ name = "generic-array"
807
+ version = "0.14.7"
808
+ source = "registry+https://github.com/rust-lang/crates.io-index"
809
+ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
810
+ dependencies = [
811
+ "typenum",
812
+ "version_check",
813
+ ]
814
+
815
+ [[package]]
816
+ name = "getrandom"
817
+ version = "0.3.4"
818
+ source = "registry+https://github.com/rust-lang/crates.io-index"
819
+ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
820
+ dependencies = [
821
+ "cfg-if",
822
+ "libc",
823
+ "r-efi 5.3.0",
824
+ "wasip2",
825
+ ]
826
+
827
+ [[package]]
828
+ name = "getrandom"
829
+ version = "0.4.2"
830
+ source = "registry+https://github.com/rust-lang/crates.io-index"
831
+ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
832
+ dependencies = [
833
+ "cfg-if",
834
+ "libc",
835
+ "r-efi 6.0.0",
836
+ "wasip2",
837
+ "wasip3",
838
+ ]
839
+
840
+ [[package]]
841
+ name = "h2"
842
+ version = "0.3.27"
843
+ source = "registry+https://github.com/rust-lang/crates.io-index"
844
+ checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d"
845
+ dependencies = [
846
+ "bytes",
847
+ "fnv",
848
+ "futures-core",
849
+ "futures-sink",
850
+ "futures-util",
851
+ "http",
852
+ "indexmap",
853
+ "slab",
854
+ "tokio",
855
+ "tokio-util",
856
+ "tracing",
857
+ ]
858
+
859
+ [[package]]
860
+ name = "half"
861
+ version = "2.7.1"
862
+ source = "registry+https://github.com/rust-lang/crates.io-index"
863
+ checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
864
+ dependencies = [
865
+ "bytemuck",
866
+ "cfg-if",
867
+ "crunchy",
868
+ "num-traits",
869
+ "rand",
870
+ "rand_distr",
871
+ "zerocopy",
872
+ ]
873
+
874
+ [[package]]
875
+ name = "hashbrown"
876
+ version = "0.15.5"
877
+ source = "registry+https://github.com/rust-lang/crates.io-index"
878
+ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
879
+ dependencies = [
880
+ "foldhash",
881
+ ]
882
+
883
+ [[package]]
884
+ name = "hashbrown"
885
+ version = "0.17.1"
886
+ source = "registry+https://github.com/rust-lang/crates.io-index"
887
+ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
888
+
889
+ [[package]]
890
+ name = "heck"
891
+ version = "0.5.0"
892
+ source = "registry+https://github.com/rust-lang/crates.io-index"
893
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
894
+
895
+ [[package]]
896
+ name = "hermit-abi"
897
+ version = "0.5.2"
898
+ source = "registry+https://github.com/rust-lang/crates.io-index"
899
+ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
900
+
901
+ [[package]]
902
+ name = "http"
903
+ version = "0.2.12"
904
+ source = "registry+https://github.com/rust-lang/crates.io-index"
905
+ checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
906
+ dependencies = [
907
+ "bytes",
908
+ "fnv",
909
+ "itoa",
910
+ ]
911
+
912
+ [[package]]
913
+ name = "http-body"
914
+ version = "0.4.6"
915
+ source = "registry+https://github.com/rust-lang/crates.io-index"
916
+ checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
917
+ dependencies = [
918
+ "bytes",
919
+ "http",
920
+ "pin-project-lite",
921
+ ]
922
+
923
+ [[package]]
924
+ name = "httparse"
925
+ version = "1.10.1"
926
+ source = "registry+https://github.com/rust-lang/crates.io-index"
927
+ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
928
+
929
+ [[package]]
930
+ name = "httpdate"
931
+ version = "1.0.3"
932
+ source = "registry+https://github.com/rust-lang/crates.io-index"
933
+ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
934
+
935
+ [[package]]
936
+ name = "hyper"
937
+ version = "0.14.32"
938
+ source = "registry+https://github.com/rust-lang/crates.io-index"
939
+ checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
940
+ dependencies = [
941
+ "bytes",
942
+ "futures-channel",
943
+ "futures-core",
944
+ "futures-util",
945
+ "h2",
946
+ "http",
947
+ "http-body",
948
+ "httparse",
949
+ "httpdate",
950
+ "itoa",
951
+ "pin-project-lite",
952
+ "socket2 0.5.10",
953
+ "tokio",
954
+ "tower-service",
955
+ "tracing",
956
+ "want",
957
+ ]
958
+
959
+ [[package]]
960
+ name = "hyper-tls"
961
+ version = "0.5.0"
962
+ source = "registry+https://github.com/rust-lang/crates.io-index"
963
+ checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
964
+ dependencies = [
965
+ "bytes",
966
+ "hyper",
967
+ "native-tls",
968
+ "tokio",
969
+ "tokio-native-tls",
970
+ ]
971
+
972
+ [[package]]
973
+ name = "icu_collections"
974
+ version = "2.2.0"
975
+ source = "registry+https://github.com/rust-lang/crates.io-index"
976
+ checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
977
+ dependencies = [
978
+ "displaydoc",
979
+ "potential_utf",
980
+ "utf8_iter",
981
+ "yoke 0.8.2",
982
+ "zerofrom",
983
+ "zerovec",
984
+ ]
985
+
986
+ [[package]]
987
+ name = "icu_locale_core"
988
+ version = "2.2.0"
989
+ source = "registry+https://github.com/rust-lang/crates.io-index"
990
+ checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
991
+ dependencies = [
992
+ "displaydoc",
993
+ "litemap",
994
+ "tinystr",
995
+ "writeable",
996
+ "zerovec",
997
+ ]
998
+
999
+ [[package]]
1000
+ name = "icu_normalizer"
1001
+ version = "2.2.0"
1002
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1003
+ checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
1004
+ dependencies = [
1005
+ "icu_collections",
1006
+ "icu_normalizer_data",
1007
+ "icu_properties",
1008
+ "icu_provider",
1009
+ "smallvec",
1010
+ "zerovec",
1011
+ ]
1012
+
1013
+ [[package]]
1014
+ name = "icu_normalizer_data"
1015
+ version = "2.2.0"
1016
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1017
+ checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
1018
+
1019
+ [[package]]
1020
+ name = "icu_properties"
1021
+ version = "2.2.0"
1022
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1023
+ checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
1024
+ dependencies = [
1025
+ "icu_collections",
1026
+ "icu_locale_core",
1027
+ "icu_properties_data",
1028
+ "icu_provider",
1029
+ "zerotrie",
1030
+ "zerovec",
1031
+ ]
1032
+
1033
+ [[package]]
1034
+ name = "icu_properties_data"
1035
+ version = "2.2.0"
1036
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1037
+ checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
1038
+
1039
+ [[package]]
1040
+ name = "icu_provider"
1041
+ version = "2.2.0"
1042
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1043
+ checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
1044
+ dependencies = [
1045
+ "displaydoc",
1046
+ "icu_locale_core",
1047
+ "writeable",
1048
+ "yoke 0.8.2",
1049
+ "zerofrom",
1050
+ "zerotrie",
1051
+ "zerovec",
1052
+ ]
1053
+
1054
+ [[package]]
1055
+ name = "id-arena"
1056
+ version = "2.3.0"
1057
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1058
+ checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
1059
+
1060
+ [[package]]
1061
+ name = "idna"
1062
+ version = "1.1.0"
1063
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1064
+ checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
1065
+ dependencies = [
1066
+ "idna_adapter",
1067
+ "smallvec",
1068
+ "utf8_iter",
1069
+ ]
1070
+
1071
+ [[package]]
1072
+ name = "idna_adapter"
1073
+ version = "1.2.2"
1074
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1075
+ checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
1076
+ dependencies = [
1077
+ "icu_normalizer",
1078
+ "icu_properties",
1079
+ ]
1080
+
1081
+ [[package]]
1082
+ name = "indexmap"
1083
+ version = "2.14.0"
1084
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1085
+ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
1086
+ dependencies = [
1087
+ "equivalent",
1088
+ "hashbrown 0.17.1",
1089
+ "serde",
1090
+ "serde_core",
1091
+ ]
1092
+
1093
+ [[package]]
1094
+ name = "indicatif"
1095
+ version = "0.17.11"
1096
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1097
+ checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
1098
+ dependencies = [
1099
+ "console",
1100
+ "number_prefix",
1101
+ "portable-atomic",
1102
+ "unicode-width",
1103
+ "web-time",
1104
+ ]
1105
+
1106
+ [[package]]
1107
+ name = "ipnet"
1108
+ version = "2.12.0"
1109
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1110
+ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
1111
+
1112
+ [[package]]
1113
+ name = "is_terminal_polyfill"
1114
+ version = "1.70.2"
1115
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1116
+ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
1117
+
1118
+ [[package]]
1119
+ name = "itoa"
1120
+ version = "1.0.18"
1121
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1122
+ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
1123
+
1124
+ [[package]]
1125
+ name = "js-sys"
1126
+ version = "0.3.99"
1127
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1128
+ checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
1129
+ dependencies = [
1130
+ "cfg-if",
1131
+ "futures-util",
1132
+ "once_cell",
1133
+ "wasm-bindgen",
1134
+ ]
1135
+
1136
+ [[package]]
1137
+ name = "leb128fmt"
1138
+ version = "0.1.0"
1139
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1140
+ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
1141
+
1142
+ [[package]]
1143
+ name = "libc"
1144
+ version = "0.2.186"
1145
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1146
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
1147
+
1148
+ [[package]]
1149
+ name = "libloading"
1150
+ version = "0.8.9"
1151
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1152
+ checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
1153
+ dependencies = [
1154
+ "cfg-if",
1155
+ "windows-link",
1156
+ ]
1157
+
1158
+ [[package]]
1159
+ name = "libm"
1160
+ version = "0.2.16"
1161
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1162
+ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
1163
+
1164
+ [[package]]
1165
+ name = "linux-raw-sys"
1166
+ version = "0.12.1"
1167
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1168
+ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
1169
+
1170
+ [[package]]
1171
+ name = "litemap"
1172
+ version = "0.8.2"
1173
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1174
+ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
1175
+
1176
+ [[package]]
1177
+ name = "lock_api"
1178
+ version = "0.4.14"
1179
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1180
+ checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
1181
+ dependencies = [
1182
+ "scopeguard",
1183
+ ]
1184
+
1185
+ [[package]]
1186
+ name = "log"
1187
+ version = "0.4.29"
1188
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1189
+ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
1190
+
1191
+ [[package]]
1192
+ name = "memchr"
1193
+ version = "2.8.0"
1194
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1195
+ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
1196
+
1197
+ [[package]]
1198
+ name = "memmap2"
1199
+ version = "0.9.10"
1200
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1201
+ checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3"
1202
+ dependencies = [
1203
+ "libc",
1204
+ "stable_deref_trait",
1205
+ ]
1206
+
1207
+ [[package]]
1208
+ name = "mime"
1209
+ version = "0.3.17"
1210
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1211
+ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
1212
+
1213
+ [[package]]
1214
+ name = "mio"
1215
+ version = "1.2.0"
1216
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1217
+ checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
1218
+ dependencies = [
1219
+ "libc",
1220
+ "wasi",
1221
+ "windows-sys 0.61.2",
1222
+ ]
1223
+
1224
+ [[package]]
1225
+ name = "native-tls"
1226
+ version = "0.2.18"
1227
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1228
+ checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
1229
+ dependencies = [
1230
+ "libc",
1231
+ "log",
1232
+ "openssl",
1233
+ "openssl-probe",
1234
+ "openssl-sys",
1235
+ "schannel",
1236
+ "security-framework",
1237
+ "security-framework-sys",
1238
+ "tempfile",
1239
+ ]
1240
+
1241
+ [[package]]
1242
+ name = "num"
1243
+ version = "0.4.3"
1244
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1245
+ checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
1246
+ dependencies = [
1247
+ "num-bigint",
1248
+ "num-complex",
1249
+ "num-integer",
1250
+ "num-iter",
1251
+ "num-rational",
1252
+ "num-traits",
1253
+ ]
1254
+
1255
+ [[package]]
1256
+ name = "num-bigint"
1257
+ version = "0.4.6"
1258
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1259
+ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
1260
+ dependencies = [
1261
+ "num-integer",
1262
+ "num-traits",
1263
+ ]
1264
+
1265
+ [[package]]
1266
+ name = "num-complex"
1267
+ version = "0.4.6"
1268
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1269
+ checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
1270
+ dependencies = [
1271
+ "bytemuck",
1272
+ "num-traits",
1273
+ ]
1274
+
1275
+ [[package]]
1276
+ name = "num-integer"
1277
+ version = "0.1.46"
1278
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1279
+ checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
1280
+ dependencies = [
1281
+ "num-traits",
1282
+ ]
1283
+
1284
+ [[package]]
1285
+ name = "num-iter"
1286
+ version = "0.1.45"
1287
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1288
+ checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
1289
+ dependencies = [
1290
+ "autocfg",
1291
+ "num-integer",
1292
+ "num-traits",
1293
+ ]
1294
+
1295
+ [[package]]
1296
+ name = "num-rational"
1297
+ version = "0.4.2"
1298
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1299
+ checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
1300
+ dependencies = [
1301
+ "num-bigint",
1302
+ "num-integer",
1303
+ "num-traits",
1304
+ ]
1305
+
1306
+ [[package]]
1307
+ name = "num-traits"
1308
+ version = "0.2.19"
1309
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1310
+ checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
1311
+ dependencies = [
1312
+ "autocfg",
1313
+ "libm",
1314
+ ]
1315
+
1316
+ [[package]]
1317
+ name = "num_cpus"
1318
+ version = "1.17.0"
1319
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1320
+ checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
1321
+ dependencies = [
1322
+ "hermit-abi",
1323
+ "libc",
1324
+ ]
1325
+
1326
+ [[package]]
1327
+ name = "num_enum"
1328
+ version = "0.7.6"
1329
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1330
+ checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
1331
+ dependencies = [
1332
+ "num_enum_derive",
1333
+ "rustversion",
1334
+ ]
1335
+
1336
+ [[package]]
1337
+ name = "num_enum_derive"
1338
+ version = "0.7.6"
1339
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1340
+ checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
1341
+ dependencies = [
1342
+ "proc-macro-crate",
1343
+ "proc-macro2",
1344
+ "quote",
1345
+ "syn",
1346
+ ]
1347
+
1348
+ [[package]]
1349
+ name = "number_prefix"
1350
+ version = "0.4.0"
1351
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1352
+ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
1353
+
1354
+ [[package]]
1355
+ name = "once_cell"
1356
+ version = "1.21.4"
1357
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1358
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
1359
+
1360
+ [[package]]
1361
+ name = "once_cell_polyfill"
1362
+ version = "1.70.2"
1363
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1364
+ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
1365
+
1366
+ [[package]]
1367
+ name = "openssl"
1368
+ version = "0.10.80"
1369
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1370
+ checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967"
1371
+ dependencies = [
1372
+ "bitflags 2.11.1",
1373
+ "cfg-if",
1374
+ "foreign-types",
1375
+ "libc",
1376
+ "openssl-macros",
1377
+ "openssl-sys",
1378
+ ]
1379
+
1380
+ [[package]]
1381
+ name = "openssl-macros"
1382
+ version = "0.1.1"
1383
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1384
+ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
1385
+ dependencies = [
1386
+ "proc-macro2",
1387
+ "quote",
1388
+ "syn",
1389
+ ]
1390
+
1391
+ [[package]]
1392
+ name = "openssl-probe"
1393
+ version = "0.2.1"
1394
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1395
+ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
1396
+
1397
+ [[package]]
1398
+ name = "openssl-sys"
1399
+ version = "0.9.116"
1400
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1401
+ checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4"
1402
+ dependencies = [
1403
+ "cc",
1404
+ "libc",
1405
+ "pkg-config",
1406
+ "vcpkg",
1407
+ ]
1408
+
1409
+ [[package]]
1410
+ name = "parking_lot"
1411
+ version = "0.12.5"
1412
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1413
+ checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
1414
+ dependencies = [
1415
+ "lock_api",
1416
+ "parking_lot_core",
1417
+ ]
1418
+
1419
+ [[package]]
1420
+ name = "parking_lot_core"
1421
+ version = "0.9.12"
1422
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1423
+ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
1424
+ dependencies = [
1425
+ "cfg-if",
1426
+ "libc",
1427
+ "redox_syscall",
1428
+ "smallvec",
1429
+ "windows-link",
1430
+ ]
1431
+
1432
+ [[package]]
1433
+ name = "paste"
1434
+ version = "1.0.15"
1435
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1436
+ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
1437
+
1438
+ [[package]]
1439
+ name = "percent-encoding"
1440
+ version = "2.3.2"
1441
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1442
+ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
1443
+
1444
+ [[package]]
1445
+ name = "pin-project-lite"
1446
+ version = "0.2.17"
1447
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1448
+ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
1449
+
1450
+ [[package]]
1451
+ name = "pkg-config"
1452
+ version = "0.3.33"
1453
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1454
+ checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
1455
+
1456
+ [[package]]
1457
+ name = "portable-atomic"
1458
+ version = "1.13.1"
1459
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1460
+ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
1461
+
1462
+ [[package]]
1463
+ name = "potential_utf"
1464
+ version = "0.1.5"
1465
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1466
+ checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
1467
+ dependencies = [
1468
+ "zerovec",
1469
+ ]
1470
+
1471
+ [[package]]
1472
+ name = "ppv-lite86"
1473
+ version = "0.2.21"
1474
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1475
+ checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
1476
+ dependencies = [
1477
+ "zerocopy",
1478
+ ]
1479
+
1480
+ [[package]]
1481
+ name = "prettyplease"
1482
+ version = "0.2.37"
1483
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1484
+ checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
1485
+ dependencies = [
1486
+ "proc-macro2",
1487
+ "syn",
1488
+ ]
1489
+
1490
+ [[package]]
1491
+ name = "proc-macro-crate"
1492
+ version = "3.5.0"
1493
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1494
+ checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
1495
+ dependencies = [
1496
+ "toml_edit",
1497
+ ]
1498
+
1499
+ [[package]]
1500
+ name = "proc-macro2"
1501
+ version = "1.0.106"
1502
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1503
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
1504
+ dependencies = [
1505
+ "unicode-ident",
1506
+ ]
1507
+
1508
+ [[package]]
1509
+ name = "pulp"
1510
+ version = "0.18.22"
1511
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1512
+ checksum = "a0a01a0dc67cf4558d279f0c25b0962bd08fc6dec0137699eae304103e882fe6"
1513
+ dependencies = [
1514
+ "bytemuck",
1515
+ "libm",
1516
+ "num-complex",
1517
+ "reborrow",
1518
+ ]
1519
+
1520
+ [[package]]
1521
+ name = "pulp"
1522
+ version = "0.21.5"
1523
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1524
+ checksum = "96b86df24f0a7ddd5e4b95c94fc9ed8a98f1ca94d3b01bdce2824097e7835907"
1525
+ dependencies = [
1526
+ "bytemuck",
1527
+ "cfg-if",
1528
+ "libm",
1529
+ "num-complex",
1530
+ "reborrow",
1531
+ "version_check",
1532
+ ]
1533
+
1534
+ [[package]]
1535
+ name = "quote"
1536
+ version = "1.0.45"
1537
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1538
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
1539
+ dependencies = [
1540
+ "proc-macro2",
1541
+ ]
1542
+
1543
+ [[package]]
1544
+ name = "r-efi"
1545
+ version = "5.3.0"
1546
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1547
+ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
1548
+
1549
+ [[package]]
1550
+ name = "r-efi"
1551
+ version = "6.0.0"
1552
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1553
+ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
1554
+
1555
+ [[package]]
1556
+ name = "rand"
1557
+ version = "0.9.4"
1558
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1559
+ checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
1560
+ dependencies = [
1561
+ "rand_chacha",
1562
+ "rand_core",
1563
+ ]
1564
+
1565
+ [[package]]
1566
+ name = "rand_chacha"
1567
+ version = "0.9.0"
1568
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1569
+ checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
1570
+ dependencies = [
1571
+ "ppv-lite86",
1572
+ "rand_core",
1573
+ ]
1574
+
1575
+ [[package]]
1576
+ name = "rand_core"
1577
+ version = "0.9.5"
1578
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1579
+ checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
1580
+ dependencies = [
1581
+ "getrandom 0.3.4",
1582
+ ]
1583
+
1584
+ [[package]]
1585
+ name = "rand_distr"
1586
+ version = "0.5.1"
1587
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1588
+ checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463"
1589
+ dependencies = [
1590
+ "num-traits",
1591
+ "rand",
1592
+ ]
1593
+
1594
+ [[package]]
1595
+ name = "raw-cpuid"
1596
+ version = "10.7.0"
1597
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1598
+ checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332"
1599
+ dependencies = [
1600
+ "bitflags 1.3.2",
1601
+ ]
1602
+
1603
+ [[package]]
1604
+ name = "raw-cpuid"
1605
+ version = "11.6.0"
1606
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1607
+ checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
1608
+ dependencies = [
1609
+ "bitflags 2.11.1",
1610
+ ]
1611
+
1612
+ [[package]]
1613
+ name = "rayon"
1614
+ version = "1.12.0"
1615
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1616
+ checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
1617
+ dependencies = [
1618
+ "either",
1619
+ "rayon-core",
1620
+ ]
1621
+
1622
+ [[package]]
1623
+ name = "rayon-core"
1624
+ version = "1.13.0"
1625
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1626
+ checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
1627
+ dependencies = [
1628
+ "crossbeam-deque",
1629
+ "crossbeam-utils",
1630
+ ]
1631
+
1632
+ [[package]]
1633
+ name = "reborrow"
1634
+ version = "0.5.5"
1635
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1636
+ checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430"
1637
+
1638
+ [[package]]
1639
+ name = "redox_syscall"
1640
+ version = "0.5.18"
1641
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1642
+ checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
1643
+ dependencies = [
1644
+ "bitflags 2.11.1",
1645
+ ]
1646
+
1647
+ [[package]]
1648
+ name = "reqwest"
1649
+ version = "0.11.27"
1650
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1651
+ checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
1652
+ dependencies = [
1653
+ "base64 0.21.7",
1654
+ "bytes",
1655
+ "encoding_rs",
1656
+ "futures-core",
1657
+ "futures-util",
1658
+ "h2",
1659
+ "http",
1660
+ "http-body",
1661
+ "hyper",
1662
+ "hyper-tls",
1663
+ "ipnet",
1664
+ "js-sys",
1665
+ "log",
1666
+ "mime",
1667
+ "native-tls",
1668
+ "once_cell",
1669
+ "percent-encoding",
1670
+ "pin-project-lite",
1671
+ "rustls-pemfile",
1672
+ "serde",
1673
+ "serde_json",
1674
+ "serde_urlencoded",
1675
+ "sync_wrapper",
1676
+ "system-configuration",
1677
+ "tokio",
1678
+ "tokio-native-tls",
1679
+ "tokio-util",
1680
+ "tower-service",
1681
+ "url",
1682
+ "wasm-bindgen",
1683
+ "wasm-bindgen-futures",
1684
+ "wasm-streams",
1685
+ "web-sys",
1686
+ "winreg",
1687
+ ]
1688
+
1689
+ [[package]]
1690
+ name = "rustix"
1691
+ version = "1.1.4"
1692
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1693
+ checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
1694
+ dependencies = [
1695
+ "bitflags 2.11.1",
1696
+ "errno",
1697
+ "libc",
1698
+ "linux-raw-sys",
1699
+ "windows-sys 0.61.2",
1700
+ ]
1701
+
1702
+ [[package]]
1703
+ name = "rustls-pemfile"
1704
+ version = "1.0.4"
1705
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1706
+ checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
1707
+ dependencies = [
1708
+ "base64 0.21.7",
1709
+ ]
1710
+
1711
+ [[package]]
1712
+ name = "rustversion"
1713
+ version = "1.0.22"
1714
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1715
+ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
1716
+
1717
+ [[package]]
1718
+ name = "ryu"
1719
+ version = "1.0.23"
1720
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1721
+ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
1722
+
1723
+ [[package]]
1724
+ name = "safetensors"
1725
+ version = "0.4.5"
1726
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1727
+ checksum = "44560c11236a6130a46ce36c836a62936dc81ebf8c36a37947423571be0e55b6"
1728
+ dependencies = [
1729
+ "serde",
1730
+ "serde_json",
1731
+ ]
1732
+
1733
+ [[package]]
1734
+ name = "same-file"
1735
+ version = "1.0.6"
1736
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1737
+ checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
1738
+ dependencies = [
1739
+ "winapi-util",
1740
+ ]
1741
+
1742
+ [[package]]
1743
+ name = "schannel"
1744
+ version = "0.1.29"
1745
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1746
+ checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
1747
+ dependencies = [
1748
+ "windows-sys 0.61.2",
1749
+ ]
1750
+
1751
+ [[package]]
1752
+ name = "scopeguard"
1753
+ version = "1.2.0"
1754
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1755
+ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
1756
+
1757
+ [[package]]
1758
+ name = "security-framework"
1759
+ version = "3.7.0"
1760
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1761
+ checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
1762
+ dependencies = [
1763
+ "bitflags 2.11.1",
1764
+ "core-foundation 0.10.1",
1765
+ "core-foundation-sys",
1766
+ "libc",
1767
+ "security-framework-sys",
1768
+ ]
1769
+
1770
+ [[package]]
1771
+ name = "security-framework-sys"
1772
+ version = "2.17.0"
1773
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1774
+ checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
1775
+ dependencies = [
1776
+ "core-foundation-sys",
1777
+ "libc",
1778
+ ]
1779
+
1780
+ [[package]]
1781
+ name = "semver"
1782
+ version = "1.0.28"
1783
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1784
+ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
1785
+
1786
+ [[package]]
1787
+ name = "seq-macro"
1788
+ version = "0.3.6"
1789
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1790
+ checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"
1791
+
1792
+ [[package]]
1793
+ name = "serde"
1794
+ version = "1.0.228"
1795
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1796
+ checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
1797
+ dependencies = [
1798
+ "serde_core",
1799
+ "serde_derive",
1800
+ ]
1801
+
1802
+ [[package]]
1803
+ name = "serde_core"
1804
+ version = "1.0.228"
1805
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1806
+ checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
1807
+ dependencies = [
1808
+ "serde_derive",
1809
+ ]
1810
+
1811
+ [[package]]
1812
+ name = "serde_derive"
1813
+ version = "1.0.228"
1814
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1815
+ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
1816
+ dependencies = [
1817
+ "proc-macro2",
1818
+ "quote",
1819
+ "syn",
1820
+ ]
1821
+
1822
+ [[package]]
1823
+ name = "serde_json"
1824
+ version = "1.0.150"
1825
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1826
+ checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
1827
+ dependencies = [
1828
+ "itoa",
1829
+ "memchr",
1830
+ "serde",
1831
+ "serde_core",
1832
+ "zmij",
1833
+ ]
1834
+
1835
+ [[package]]
1836
+ name = "serde_urlencoded"
1837
+ version = "0.7.1"
1838
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1839
+ checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
1840
+ dependencies = [
1841
+ "form_urlencoded",
1842
+ "itoa",
1843
+ "ryu",
1844
+ "serde",
1845
+ ]
1846
+
1847
+ [[package]]
1848
+ name = "sha2"
1849
+ version = "0.10.9"
1850
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1851
+ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
1852
+ dependencies = [
1853
+ "cfg-if",
1854
+ "cpufeatures",
1855
+ "digest",
1856
+ ]
1857
+
1858
+ [[package]]
1859
+ name = "shlex"
1860
+ version = "1.3.0"
1861
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1862
+ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
1863
+
1864
+ [[package]]
1865
+ name = "signal-hook-registry"
1866
+ version = "1.4.8"
1867
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1868
+ checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
1869
+ dependencies = [
1870
+ "errno",
1871
+ "libc",
1872
+ ]
1873
+
1874
+ [[package]]
1875
+ name = "slab"
1876
+ version = "0.4.12"
1877
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1878
+ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
1879
+
1880
+ [[package]]
1881
+ name = "smallvec"
1882
+ version = "1.15.1"
1883
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1884
+ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
1885
+
1886
+ [[package]]
1887
+ name = "socket2"
1888
+ version = "0.5.10"
1889
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1890
+ checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
1891
+ dependencies = [
1892
+ "libc",
1893
+ "windows-sys 0.52.0",
1894
+ ]
1895
+
1896
+ [[package]]
1897
+ name = "socket2"
1898
+ version = "0.6.3"
1899
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1900
+ checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
1901
+ dependencies = [
1902
+ "libc",
1903
+ "windows-sys 0.61.2",
1904
+ ]
1905
+
1906
+ [[package]]
1907
+ name = "stable_deref_trait"
1908
+ version = "1.2.1"
1909
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1910
+ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
1911
+
1912
+ [[package]]
1913
+ name = "strsim"
1914
+ version = "0.11.1"
1915
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1916
+ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
1917
+
1918
+ [[package]]
1919
+ name = "syn"
1920
+ version = "2.0.117"
1921
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1922
+ checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
1923
+ dependencies = [
1924
+ "proc-macro2",
1925
+ "quote",
1926
+ "unicode-ident",
1927
+ ]
1928
+
1929
+ [[package]]
1930
+ name = "sync_wrapper"
1931
+ version = "0.1.2"
1932
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1933
+ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
1934
+
1935
+ [[package]]
1936
+ name = "synstructure"
1937
+ version = "0.13.2"
1938
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1939
+ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
1940
+ dependencies = [
1941
+ "proc-macro2",
1942
+ "quote",
1943
+ "syn",
1944
+ ]
1945
+
1946
+ [[package]]
1947
+ name = "sysctl"
1948
+ version = "0.5.5"
1949
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1950
+ checksum = "ec7dddc5f0fee506baf8b9fdb989e242f17e4b11c61dfbb0635b705217199eea"
1951
+ dependencies = [
1952
+ "bitflags 2.11.1",
1953
+ "byteorder",
1954
+ "enum-as-inner",
1955
+ "libc",
1956
+ "thiserror",
1957
+ "walkdir",
1958
+ ]
1959
+
1960
+ [[package]]
1961
+ name = "sysctl"
1962
+ version = "0.6.0"
1963
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1964
+ checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc"
1965
+ dependencies = [
1966
+ "bitflags 2.11.1",
1967
+ "byteorder",
1968
+ "enum-as-inner",
1969
+ "libc",
1970
+ "thiserror",
1971
+ "walkdir",
1972
+ ]
1973
+
1974
+ [[package]]
1975
+ name = "system-configuration"
1976
+ version = "0.5.1"
1977
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1978
+ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
1979
+ dependencies = [
1980
+ "bitflags 1.3.2",
1981
+ "core-foundation 0.9.4",
1982
+ "system-configuration-sys",
1983
+ ]
1984
+
1985
+ [[package]]
1986
+ name = "system-configuration-sys"
1987
+ version = "0.5.0"
1988
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1989
+ checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
1990
+ dependencies = [
1991
+ "core-foundation-sys",
1992
+ "libc",
1993
+ ]
1994
+
1995
+ [[package]]
1996
+ name = "tempfile"
1997
+ version = "3.27.0"
1998
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1999
+ checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
2000
+ dependencies = [
2001
+ "fastrand",
2002
+ "getrandom 0.4.2",
2003
+ "once_cell",
2004
+ "rustix",
2005
+ "windows-sys 0.61.2",
2006
+ ]
2007
+
2008
+ [[package]]
2009
+ name = "thiserror"
2010
+ version = "1.0.69"
2011
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2012
+ checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
2013
+ dependencies = [
2014
+ "thiserror-impl",
2015
+ ]
2016
+
2017
+ [[package]]
2018
+ name = "thiserror-impl"
2019
+ version = "1.0.69"
2020
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2021
+ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
2022
+ dependencies = [
2023
+ "proc-macro2",
2024
+ "quote",
2025
+ "syn",
2026
+ ]
2027
+
2028
+ [[package]]
2029
+ name = "tinystr"
2030
+ version = "0.8.3"
2031
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2032
+ checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
2033
+ dependencies = [
2034
+ "displaydoc",
2035
+ "zerovec",
2036
+ ]
2037
+
2038
+ [[package]]
2039
+ name = "tokio"
2040
+ version = "1.52.3"
2041
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2042
+ checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
2043
+ dependencies = [
2044
+ "bytes",
2045
+ "libc",
2046
+ "mio",
2047
+ "parking_lot",
2048
+ "pin-project-lite",
2049
+ "signal-hook-registry",
2050
+ "socket2 0.6.3",
2051
+ "tokio-macros",
2052
+ "windows-sys 0.61.2",
2053
+ ]
2054
+
2055
+ [[package]]
2056
+ name = "tokio-macros"
2057
+ version = "2.7.0"
2058
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2059
+ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
2060
+ dependencies = [
2061
+ "proc-macro2",
2062
+ "quote",
2063
+ "syn",
2064
+ ]
2065
+
2066
+ [[package]]
2067
+ name = "tokio-native-tls"
2068
+ version = "0.3.1"
2069
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2070
+ checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
2071
+ dependencies = [
2072
+ "native-tls",
2073
+ "tokio",
2074
+ ]
2075
+
2076
+ [[package]]
2077
+ name = "tokio-util"
2078
+ version = "0.7.18"
2079
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2080
+ checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
2081
+ dependencies = [
2082
+ "bytes",
2083
+ "futures-core",
2084
+ "futures-sink",
2085
+ "pin-project-lite",
2086
+ "tokio",
2087
+ ]
2088
+
2089
+ [[package]]
2090
+ name = "toml_datetime"
2091
+ version = "1.1.1+spec-1.1.0"
2092
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2093
+ checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
2094
+ dependencies = [
2095
+ "serde_core",
2096
+ ]
2097
+
2098
+ [[package]]
2099
+ name = "toml_edit"
2100
+ version = "0.25.11+spec-1.1.0"
2101
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2102
+ checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b"
2103
+ dependencies = [
2104
+ "indexmap",
2105
+ "toml_datetime",
2106
+ "toml_parser",
2107
+ "winnow",
2108
+ ]
2109
+
2110
+ [[package]]
2111
+ name = "toml_parser"
2112
+ version = "1.1.2+spec-1.1.0"
2113
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2114
+ checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
2115
+ dependencies = [
2116
+ "winnow",
2117
+ ]
2118
+
2119
+ [[package]]
2120
+ name = "toto-rs"
2121
+ version = "0.1.0"
2122
+ dependencies = [
2123
+ "anyhow",
2124
+ "base64 0.22.1",
2125
+ "byteorder",
2126
+ "candle-core",
2127
+ "candle-nn",
2128
+ "clap",
2129
+ "futures-util",
2130
+ "indicatif",
2131
+ "reqwest",
2132
+ "safetensors",
2133
+ "serde",
2134
+ "serde_json",
2135
+ "sha2",
2136
+ "tokio",
2137
+ "tokio-util",
2138
+ ]
2139
+
2140
+ [[package]]
2141
+ name = "tower-service"
2142
+ version = "0.3.3"
2143
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2144
+ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
2145
+
2146
+ [[package]]
2147
+ name = "tracing"
2148
+ version = "0.1.44"
2149
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2150
+ checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
2151
+ dependencies = [
2152
+ "pin-project-lite",
2153
+ "tracing-attributes",
2154
+ "tracing-core",
2155
+ ]
2156
+
2157
+ [[package]]
2158
+ name = "tracing-attributes"
2159
+ version = "0.1.31"
2160
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2161
+ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
2162
+ dependencies = [
2163
+ "proc-macro2",
2164
+ "quote",
2165
+ "syn",
2166
+ ]
2167
+
2168
+ [[package]]
2169
+ name = "tracing-core"
2170
+ version = "0.1.36"
2171
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2172
+ checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
2173
+ dependencies = [
2174
+ "once_cell",
2175
+ ]
2176
+
2177
+ [[package]]
2178
+ name = "try-lock"
2179
+ version = "0.2.5"
2180
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2181
+ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
2182
+
2183
+ [[package]]
2184
+ name = "typenum"
2185
+ version = "1.20.1"
2186
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2187
+ checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
2188
+
2189
+ [[package]]
2190
+ name = "ug"
2191
+ version = "0.1.0"
2192
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2193
+ checksum = "03719c61a91b51541f076dfdba45caacf750b230cefaa4b32d6f5411c3f7f437"
2194
+ dependencies = [
2195
+ "gemm 0.18.2",
2196
+ "half",
2197
+ "libloading",
2198
+ "memmap2",
2199
+ "num",
2200
+ "num-traits",
2201
+ "num_cpus",
2202
+ "rayon",
2203
+ "safetensors",
2204
+ "serde",
2205
+ "thiserror",
2206
+ "tracing",
2207
+ "yoke 0.7.5",
2208
+ ]
2209
+
2210
+ [[package]]
2211
+ name = "unicode-ident"
2212
+ version = "1.0.24"
2213
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2214
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
2215
+
2216
+ [[package]]
2217
+ name = "unicode-width"
2218
+ version = "0.2.2"
2219
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2220
+ checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
2221
+
2222
+ [[package]]
2223
+ name = "unicode-xid"
2224
+ version = "0.2.6"
2225
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2226
+ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
2227
+
2228
+ [[package]]
2229
+ name = "url"
2230
+ version = "2.5.8"
2231
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2232
+ checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
2233
+ dependencies = [
2234
+ "form_urlencoded",
2235
+ "idna",
2236
+ "percent-encoding",
2237
+ "serde",
2238
+ ]
2239
+
2240
+ [[package]]
2241
+ name = "utf8_iter"
2242
+ version = "1.0.4"
2243
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2244
+ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
2245
+
2246
+ [[package]]
2247
+ name = "utf8parse"
2248
+ version = "0.2.2"
2249
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2250
+ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
2251
+
2252
+ [[package]]
2253
+ name = "vcpkg"
2254
+ version = "0.2.15"
2255
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2256
+ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
2257
+
2258
+ [[package]]
2259
+ name = "version_check"
2260
+ version = "0.9.5"
2261
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2262
+ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
2263
+
2264
+ [[package]]
2265
+ name = "walkdir"
2266
+ version = "2.5.0"
2267
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2268
+ checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
2269
+ dependencies = [
2270
+ "same-file",
2271
+ "winapi-util",
2272
+ ]
2273
+
2274
+ [[package]]
2275
+ name = "want"
2276
+ version = "0.3.1"
2277
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2278
+ checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
2279
+ dependencies = [
2280
+ "try-lock",
2281
+ ]
2282
+
2283
+ [[package]]
2284
+ name = "wasi"
2285
+ version = "0.11.1+wasi-snapshot-preview1"
2286
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2287
+ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
2288
+
2289
+ [[package]]
2290
+ name = "wasip2"
2291
+ version = "1.0.3+wasi-0.2.9"
2292
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2293
+ checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
2294
+ dependencies = [
2295
+ "wit-bindgen 0.57.1",
2296
+ ]
2297
+
2298
+ [[package]]
2299
+ name = "wasip3"
2300
+ version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
2301
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2302
+ checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
2303
+ dependencies = [
2304
+ "wit-bindgen 0.51.0",
2305
+ ]
2306
+
2307
+ [[package]]
2308
+ name = "wasm-bindgen"
2309
+ version = "0.2.122"
2310
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2311
+ checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
2312
+ dependencies = [
2313
+ "cfg-if",
2314
+ "once_cell",
2315
+ "rustversion",
2316
+ "wasm-bindgen-macro",
2317
+ "wasm-bindgen-shared",
2318
+ ]
2319
+
2320
+ [[package]]
2321
+ name = "wasm-bindgen-futures"
2322
+ version = "0.4.72"
2323
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2324
+ checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f"
2325
+ dependencies = [
2326
+ "js-sys",
2327
+ "wasm-bindgen",
2328
+ ]
2329
+
2330
+ [[package]]
2331
+ name = "wasm-bindgen-macro"
2332
+ version = "0.2.122"
2333
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2334
+ checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
2335
+ dependencies = [
2336
+ "quote",
2337
+ "wasm-bindgen-macro-support",
2338
+ ]
2339
+
2340
+ [[package]]
2341
+ name = "wasm-bindgen-macro-support"
2342
+ version = "0.2.122"
2343
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2344
+ checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
2345
+ dependencies = [
2346
+ "bumpalo",
2347
+ "proc-macro2",
2348
+ "quote",
2349
+ "syn",
2350
+ "wasm-bindgen-shared",
2351
+ ]
2352
+
2353
+ [[package]]
2354
+ name = "wasm-bindgen-shared"
2355
+ version = "0.2.122"
2356
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2357
+ checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
2358
+ dependencies = [
2359
+ "unicode-ident",
2360
+ ]
2361
+
2362
+ [[package]]
2363
+ name = "wasm-encoder"
2364
+ version = "0.244.0"
2365
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2366
+ checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
2367
+ dependencies = [
2368
+ "leb128fmt",
2369
+ "wasmparser",
2370
+ ]
2371
+
2372
+ [[package]]
2373
+ name = "wasm-metadata"
2374
+ version = "0.244.0"
2375
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2376
+ checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
2377
+ dependencies = [
2378
+ "anyhow",
2379
+ "indexmap",
2380
+ "wasm-encoder",
2381
+ "wasmparser",
2382
+ ]
2383
+
2384
+ [[package]]
2385
+ name = "wasm-streams"
2386
+ version = "0.4.2"
2387
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2388
+ checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
2389
+ dependencies = [
2390
+ "futures-util",
2391
+ "js-sys",
2392
+ "wasm-bindgen",
2393
+ "wasm-bindgen-futures",
2394
+ "web-sys",
2395
+ ]
2396
+
2397
+ [[package]]
2398
+ name = "wasmparser"
2399
+ version = "0.244.0"
2400
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2401
+ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
2402
+ dependencies = [
2403
+ "bitflags 2.11.1",
2404
+ "hashbrown 0.15.5",
2405
+ "indexmap",
2406
+ "semver",
2407
+ ]
2408
+
2409
+ [[package]]
2410
+ name = "web-sys"
2411
+ version = "0.3.99"
2412
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2413
+ checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436"
2414
+ dependencies = [
2415
+ "js-sys",
2416
+ "wasm-bindgen",
2417
+ ]
2418
+
2419
+ [[package]]
2420
+ name = "web-time"
2421
+ version = "1.1.0"
2422
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2423
+ checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
2424
+ dependencies = [
2425
+ "js-sys",
2426
+ "wasm-bindgen",
2427
+ ]
2428
+
2429
+ [[package]]
2430
+ name = "winapi-util"
2431
+ version = "0.1.11"
2432
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2433
+ checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
2434
+ dependencies = [
2435
+ "windows-sys 0.61.2",
2436
+ ]
2437
+
2438
+ [[package]]
2439
+ name = "windows-link"
2440
+ version = "0.2.1"
2441
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2442
+ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
2443
+
2444
+ [[package]]
2445
+ name = "windows-sys"
2446
+ version = "0.48.0"
2447
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2448
+ checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
2449
+ dependencies = [
2450
+ "windows-targets 0.48.5",
2451
+ ]
2452
+
2453
+ [[package]]
2454
+ name = "windows-sys"
2455
+ version = "0.52.0"
2456
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2457
+ checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
2458
+ dependencies = [
2459
+ "windows-targets 0.52.6",
2460
+ ]
2461
+
2462
+ [[package]]
2463
+ name = "windows-sys"
2464
+ version = "0.59.0"
2465
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2466
+ checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
2467
+ dependencies = [
2468
+ "windows-targets 0.52.6",
2469
+ ]
2470
+
2471
+ [[package]]
2472
+ name = "windows-sys"
2473
+ version = "0.61.2"
2474
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2475
+ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
2476
+ dependencies = [
2477
+ "windows-link",
2478
+ ]
2479
+
2480
+ [[package]]
2481
+ name = "windows-targets"
2482
+ version = "0.48.5"
2483
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2484
+ checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
2485
+ dependencies = [
2486
+ "windows_aarch64_gnullvm 0.48.5",
2487
+ "windows_aarch64_msvc 0.48.5",
2488
+ "windows_i686_gnu 0.48.5",
2489
+ "windows_i686_msvc 0.48.5",
2490
+ "windows_x86_64_gnu 0.48.5",
2491
+ "windows_x86_64_gnullvm 0.48.5",
2492
+ "windows_x86_64_msvc 0.48.5",
2493
+ ]
2494
+
2495
+ [[package]]
2496
+ name = "windows-targets"
2497
+ version = "0.52.6"
2498
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2499
+ checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
2500
+ dependencies = [
2501
+ "windows_aarch64_gnullvm 0.52.6",
2502
+ "windows_aarch64_msvc 0.52.6",
2503
+ "windows_i686_gnu 0.52.6",
2504
+ "windows_i686_gnullvm",
2505
+ "windows_i686_msvc 0.52.6",
2506
+ "windows_x86_64_gnu 0.52.6",
2507
+ "windows_x86_64_gnullvm 0.52.6",
2508
+ "windows_x86_64_msvc 0.52.6",
2509
+ ]
2510
+
2511
+ [[package]]
2512
+ name = "windows_aarch64_gnullvm"
2513
+ version = "0.48.5"
2514
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2515
+ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
2516
+
2517
+ [[package]]
2518
+ name = "windows_aarch64_gnullvm"
2519
+ version = "0.52.6"
2520
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2521
+ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
2522
+
2523
+ [[package]]
2524
+ name = "windows_aarch64_msvc"
2525
+ version = "0.48.5"
2526
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2527
+ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
2528
+
2529
+ [[package]]
2530
+ name = "windows_aarch64_msvc"
2531
+ version = "0.52.6"
2532
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2533
+ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
2534
+
2535
+ [[package]]
2536
+ name = "windows_i686_gnu"
2537
+ version = "0.48.5"
2538
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2539
+ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
2540
+
2541
+ [[package]]
2542
+ name = "windows_i686_gnu"
2543
+ version = "0.52.6"
2544
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2545
+ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
2546
+
2547
+ [[package]]
2548
+ name = "windows_i686_gnullvm"
2549
+ version = "0.52.6"
2550
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2551
+ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
2552
+
2553
+ [[package]]
2554
+ name = "windows_i686_msvc"
2555
+ version = "0.48.5"
2556
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2557
+ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
2558
+
2559
+ [[package]]
2560
+ name = "windows_i686_msvc"
2561
+ version = "0.52.6"
2562
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2563
+ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
2564
+
2565
+ [[package]]
2566
+ name = "windows_x86_64_gnu"
2567
+ version = "0.48.5"
2568
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2569
+ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
2570
+
2571
+ [[package]]
2572
+ name = "windows_x86_64_gnu"
2573
+ version = "0.52.6"
2574
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2575
+ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
2576
+
2577
+ [[package]]
2578
+ name = "windows_x86_64_gnullvm"
2579
+ version = "0.48.5"
2580
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2581
+ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
2582
+
2583
+ [[package]]
2584
+ name = "windows_x86_64_gnullvm"
2585
+ version = "0.52.6"
2586
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2587
+ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
2588
+
2589
+ [[package]]
2590
+ name = "windows_x86_64_msvc"
2591
+ version = "0.48.5"
2592
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2593
+ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
2594
+
2595
+ [[package]]
2596
+ name = "windows_x86_64_msvc"
2597
+ version = "0.52.6"
2598
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2599
+ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
2600
+
2601
+ [[package]]
2602
+ name = "winnow"
2603
+ version = "1.0.3"
2604
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2605
+ checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
2606
+ dependencies = [
2607
+ "memchr",
2608
+ ]
2609
+
2610
+ [[package]]
2611
+ name = "winreg"
2612
+ version = "0.50.0"
2613
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2614
+ checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
2615
+ dependencies = [
2616
+ "cfg-if",
2617
+ "windows-sys 0.48.0",
2618
+ ]
2619
+
2620
+ [[package]]
2621
+ name = "wit-bindgen"
2622
+ version = "0.51.0"
2623
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2624
+ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
2625
+ dependencies = [
2626
+ "wit-bindgen-rust-macro",
2627
+ ]
2628
+
2629
+ [[package]]
2630
+ name = "wit-bindgen"
2631
+ version = "0.57.1"
2632
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2633
+ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
2634
+
2635
+ [[package]]
2636
+ name = "wit-bindgen-core"
2637
+ version = "0.51.0"
2638
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2639
+ checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
2640
+ dependencies = [
2641
+ "anyhow",
2642
+ "heck",
2643
+ "wit-parser",
2644
+ ]
2645
+
2646
+ [[package]]
2647
+ name = "wit-bindgen-rust"
2648
+ version = "0.51.0"
2649
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2650
+ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
2651
+ dependencies = [
2652
+ "anyhow",
2653
+ "heck",
2654
+ "indexmap",
2655
+ "prettyplease",
2656
+ "syn",
2657
+ "wasm-metadata",
2658
+ "wit-bindgen-core",
2659
+ "wit-component",
2660
+ ]
2661
+
2662
+ [[package]]
2663
+ name = "wit-bindgen-rust-macro"
2664
+ version = "0.51.0"
2665
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2666
+ checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
2667
+ dependencies = [
2668
+ "anyhow",
2669
+ "prettyplease",
2670
+ "proc-macro2",
2671
+ "quote",
2672
+ "syn",
2673
+ "wit-bindgen-core",
2674
+ "wit-bindgen-rust",
2675
+ ]
2676
+
2677
+ [[package]]
2678
+ name = "wit-component"
2679
+ version = "0.244.0"
2680
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2681
+ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
2682
+ dependencies = [
2683
+ "anyhow",
2684
+ "bitflags 2.11.1",
2685
+ "indexmap",
2686
+ "log",
2687
+ "serde",
2688
+ "serde_derive",
2689
+ "serde_json",
2690
+ "wasm-encoder",
2691
+ "wasm-metadata",
2692
+ "wasmparser",
2693
+ "wit-parser",
2694
+ ]
2695
+
2696
+ [[package]]
2697
+ name = "wit-parser"
2698
+ version = "0.244.0"
2699
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2700
+ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
2701
+ dependencies = [
2702
+ "anyhow",
2703
+ "id-arena",
2704
+ "indexmap",
2705
+ "log",
2706
+ "semver",
2707
+ "serde",
2708
+ "serde_derive",
2709
+ "serde_json",
2710
+ "unicode-xid",
2711
+ "wasmparser",
2712
+ ]
2713
+
2714
+ [[package]]
2715
+ name = "writeable"
2716
+ version = "0.6.3"
2717
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2718
+ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
2719
+
2720
+ [[package]]
2721
+ name = "yoke"
2722
+ version = "0.7.5"
2723
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2724
+ checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
2725
+ dependencies = [
2726
+ "serde",
2727
+ "stable_deref_trait",
2728
+ "yoke-derive 0.7.5",
2729
+ "zerofrom",
2730
+ ]
2731
+
2732
+ [[package]]
2733
+ name = "yoke"
2734
+ version = "0.8.2"
2735
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2736
+ checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
2737
+ dependencies = [
2738
+ "stable_deref_trait",
2739
+ "yoke-derive 0.8.2",
2740
+ "zerofrom",
2741
+ ]
2742
+
2743
+ [[package]]
2744
+ name = "yoke-derive"
2745
+ version = "0.7.5"
2746
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2747
+ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
2748
+ dependencies = [
2749
+ "proc-macro2",
2750
+ "quote",
2751
+ "syn",
2752
+ "synstructure",
2753
+ ]
2754
+
2755
+ [[package]]
2756
+ name = "yoke-derive"
2757
+ version = "0.8.2"
2758
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2759
+ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
2760
+ dependencies = [
2761
+ "proc-macro2",
2762
+ "quote",
2763
+ "syn",
2764
+ "synstructure",
2765
+ ]
2766
+
2767
+ [[package]]
2768
+ name = "zerocopy"
2769
+ version = "0.8.48"
2770
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2771
+ checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
2772
+ dependencies = [
2773
+ "zerocopy-derive",
2774
+ ]
2775
+
2776
+ [[package]]
2777
+ name = "zerocopy-derive"
2778
+ version = "0.8.48"
2779
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2780
+ checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
2781
+ dependencies = [
2782
+ "proc-macro2",
2783
+ "quote",
2784
+ "syn",
2785
+ ]
2786
+
2787
+ [[package]]
2788
+ name = "zerofrom"
2789
+ version = "0.1.8"
2790
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2791
+ checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
2792
+ dependencies = [
2793
+ "zerofrom-derive",
2794
+ ]
2795
+
2796
+ [[package]]
2797
+ name = "zerofrom-derive"
2798
+ version = "0.1.7"
2799
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2800
+ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
2801
+ dependencies = [
2802
+ "proc-macro2",
2803
+ "quote",
2804
+ "syn",
2805
+ "synstructure",
2806
+ ]
2807
+
2808
+ [[package]]
2809
+ name = "zerotrie"
2810
+ version = "0.2.4"
2811
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2812
+ checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
2813
+ dependencies = [
2814
+ "displaydoc",
2815
+ "yoke 0.8.2",
2816
+ "zerofrom",
2817
+ ]
2818
+
2819
+ [[package]]
2820
+ name = "zerovec"
2821
+ version = "0.11.6"
2822
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2823
+ checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
2824
+ dependencies = [
2825
+ "yoke 0.8.2",
2826
+ "zerofrom",
2827
+ "zerovec-derive",
2828
+ ]
2829
+
2830
+ [[package]]
2831
+ name = "zerovec-derive"
2832
+ version = "0.11.3"
2833
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2834
+ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
2835
+ dependencies = [
2836
+ "proc-macro2",
2837
+ "quote",
2838
+ "syn",
2839
+ ]
2840
+
2841
+ [[package]]
2842
+ name = "zip"
2843
+ version = "1.1.4"
2844
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2845
+ checksum = "9cc23c04387f4da0374be4533ad1208cbb091d5c11d070dfef13676ad6497164"
2846
+ dependencies = [
2847
+ "arbitrary",
2848
+ "crc32fast",
2849
+ "crossbeam-utils",
2850
+ "displaydoc",
2851
+ "indexmap",
2852
+ "num_enum",
2853
+ "thiserror",
2854
+ ]
2855
+
2856
+ [[package]]
2857
+ name = "zmij"
2858
+ version = "1.0.21"
2859
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2860
+ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
toto-rs/Cargo.toml ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [package]
2
+ name = "toto-rs"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+
6
+ [[bin]]
7
+ name = "toto-rs"
8
+ path = "src/main.rs"
9
+
10
+ [dependencies]
11
+ reqwest = { version = "0.11", features = ["stream", "json"] }
12
+ futures-util = "0.3"
13
+ tokio = { version = "1", features = ["full"] }
14
+ safetensors = "0.4"
15
+ serde = { version = "1", features = ["derive"] }
16
+ serde_json = "1"
17
+ clap = { version = "4", features = ["derive", "env"] }
18
+ anyhow = "1"
19
+ byteorder = "1"
20
+ indicatif = "0.17"
21
+ sha2 = "0.10"
22
+ base64 = "0.22"
23
+ tokio-util = { version = "0.7", features = ["io"] }
24
+ candle-core = { version = "0.8", features = ["accelerate"] }
25
+ candle-nn = { version = "0.8", features = ["accelerate"] }
toto-rs/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Andrew Mayes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
toto-rs/README.md ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # toto-rs
2
+
3
+ Pure-Rust CLI to download, convert, and run [Datadog Toto-2.0](https://huggingface.co/collections/Datadog/toto-20-6768ce5e1b4c1d0e7c5b9fed) time-series forecasting models. No Python runtime needed for inference.
4
+
5
+ ## What it does
6
+
7
+ - Downloads Toto-2.0 weights from HuggingFace
8
+ - Converts safetensors → GGUF (F32, F16, or Q8_0)
9
+ - Runs inference natively via [candle](https://github.com/huggingface/candle), reading directly from the GGUF file
10
+
11
+ ## Build
12
+
13
+ ```
14
+ cargo build --release
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ### Convert a model
20
+
21
+ ```
22
+ ./target/release/toto-rs convert \
23
+ --model Datadog/Toto-2.0-2.5B \
24
+ --output gguf/toto-2.5b-f16.gguf \
25
+ --dtype f16
26
+ ```
27
+
28
+ Available models: `Datadog/Toto-2.0-{4m,22m,313m,1B,2.5B}`
29
+ Available dtypes: `f32`, `f16`, `q8`
30
+
31
+ To convert all sizes in all three dtypes at once:
32
+
33
+ ```
34
+ ./scripts/convert_all.sh
35
+ ```
36
+
37
+ For gated models pass a HuggingFace token:
38
+
39
+ ```
40
+ HF_TOKEN=hf_xxx ./scripts/convert_all.sh
41
+ ```
42
+
43
+ ### Run inference
44
+
45
+ Input is a CSV file with a header row and one column per variate:
46
+
47
+ ```
48
+ ./target/release/toto-rs infer \
49
+ --gguf gguf/toto-2.5b-f16.gguf \
50
+ --config models/toto-2.5b/config.json \
51
+ --input data.csv \
52
+ --prediction-length 64
53
+ ```
54
+
55
+ Output is a CSV of 9-quantile forecasts (q0.1 … q0.9) for each variate, one row per forecast step. Use `--f64` for double-precision arithmetic (matches PyTorch numerics, ~2× memory).
56
+
57
+ ## Benchmark
58
+
59
+ Measured on Apple M-series (CPU), 512-step context → 64-step forecast, 3 runs averaged.
60
+
61
+ | Model | dtype | Size (GB) | Time (s) | MAE vs F32 |
62
+ |-----------|-------|-----------:|---------:|-----------:|
63
+ | toto-4m | f32 | 0.02 | 0.01 | — |
64
+ | | f16 | 0.01 | 0.02 | 0.176 |
65
+ | | q8 | 0.00 | 0.01 | 3.242 |
66
+ | toto-22m | f32 | 0.09 | 0.04 | — |
67
+ | | f16 | 0.04 | 0.04 | 0.071 |
68
+ | | q8 | 0.02 | 0.04 | 5.030 |
69
+ | toto-313m | f32 | 1.25 | 0.32 | — |
70
+ | | f16 | 0.63 | 0.36 | 0.074 |
71
+ | | q8 | 0.33 | 0.39 | 0.316 |
72
+ | toto-1b | f32 | 4.16 | 1.20 | — |
73
+ | | f16 | 2.08 | 1.19 | 0.074 |
74
+ | | q8 | 1.11 | 0.87 | 0.334 |
75
+ | toto-2.5b | f32 | 9.82 | 5.40 | — |
76
+ | | f16 | 4.91 | 3.04 | 0.066 |
77
+ | | q8 | 2.61 | 3.49 | 0.243 |
78
+
79
+ **F16 is the recommended dtype**: ~2× compression vs F32 with only ~0.07 MAE on models ≥313m. Q8 is useful when memory is the bottleneck and the model is ≥313m (small models are numerically sensitive).
80
+
81
+ To reproduce:
82
+
83
+ ```
84
+ pip install -r requirements.txt
85
+ python benchmark.py
86
+ ```
toto-rs/benchmark.py ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Benchmark all Toto GGUF files: file size, inference speed, and quantization error.
4
+
5
+ Usage:
6
+ python benchmark.py # all models & dtypes
7
+ python benchmark.py --model toto-4m # one model only
8
+ python benchmark.py --ctx 256 # custom context length
9
+ """
10
+ import argparse
11
+ import math
12
+ import os
13
+ import subprocess
14
+ import sys
15
+ import time
16
+
17
+ import numpy as np
18
+
19
+ GGUF_DIR = "./gguf"
20
+ MODELS_DIR = "./models"
21
+ BIN = "./target/release/toto-rs"
22
+
23
+ MODELS = [
24
+ ("toto-4m", "Datadog/Toto-2.0-4m"),
25
+ ("toto-22m", "Datadog/Toto-2.0-22m"),
26
+ ("toto-313m", "Datadog/Toto-2.0-313m"),
27
+ ("toto-1b", "Datadog/Toto-2.0-1B"),
28
+ ("toto-2.5b", "Datadog/Toto-2.0-2.5B"),
29
+ ]
30
+ DTYPES = ["f32", "f16", "q8"]
31
+
32
+ PRED_LEN = 64
33
+ N_TIMED_RUNS = 3
34
+
35
+
36
+ def make_input(ctx_len: int) -> str:
37
+ """Build a CSV string with a single sine-wave variate."""
38
+ lines = ["value"]
39
+ for t in range(ctx_len):
40
+ v = 100.0 * math.sin(2 * math.pi * t / 32) + 500.0
41
+ lines.append(f"{v:.6f}")
42
+ return "\n".join(lines) + "\n"
43
+
44
+
45
+ def gguf_path(tag: str, dtype: str) -> str:
46
+ return os.path.join(GGUF_DIR, f"{tag}-{dtype}.gguf")
47
+
48
+
49
+ def config_path(tag: str) -> str:
50
+ return os.path.join(MODELS_DIR, tag, "config.json")
51
+
52
+
53
+ def run_inference(gguf: str, config: str, csv_input: str, ctx_len: int) -> np.ndarray | None:
54
+ """Run toto-rs infer and return quantile array [9][pred_len], or None on error."""
55
+ try:
56
+ result = subprocess.run(
57
+ [
58
+ BIN, "infer",
59
+ "--gguf", gguf,
60
+ "--config", config,
61
+ "--input", "-",
62
+ "--prediction-length", str(PRED_LEN),
63
+ "--context-length", str(ctx_len),
64
+ ],
65
+ input=csv_input,
66
+ capture_output=True,
67
+ text=True,
68
+ check=True,
69
+ )
70
+ except subprocess.CalledProcessError as e:
71
+ print(f" ERROR: {e.stderr.strip().splitlines()[-1] if e.stderr else e}", file=sys.stderr)
72
+ return None
73
+
74
+ lines = result.stdout.strip().splitlines()
75
+ if len(lines) < PRED_LEN + 1:
76
+ return None
77
+ data = np.array([
78
+ [float(lines[r + 1].split(",")[q]) for r in range(PRED_LEN)]
79
+ for q in range(9)
80
+ ])
81
+ return data
82
+
83
+
84
+ def time_inference(gguf: str, config: str, csv_input: str, ctx_len: int, n_runs: int) -> float | None:
85
+ """Return mean wall-clock seconds for n_runs inference calls."""
86
+ times = []
87
+ for _ in range(n_runs):
88
+ t0 = time.perf_counter()
89
+ ok = run_inference(gguf, config, csv_input, ctx_len)
90
+ if ok is None:
91
+ return None
92
+ times.append(time.perf_counter() - t0)
93
+ return float(np.mean(times))
94
+
95
+
96
+ def file_size_gb(path: str) -> float:
97
+ return os.path.getsize(path) / 1e9
98
+
99
+
100
+ def fmt(val, missing="—"):
101
+ return missing if val is None else val
102
+
103
+
104
+ def main():
105
+ parser = argparse.ArgumentParser()
106
+ parser.add_argument("--model", help="restrict to one tag, e.g. toto-4m")
107
+ parser.add_argument("--ctx", type=int, default=512, help="context length (default 512)")
108
+ args = parser.parse_args()
109
+
110
+ ctx_len = args.ctx
111
+ csv_input = make_input(ctx_len)
112
+
113
+ models = [(t, hf) for t, hf in MODELS if args.model is None or t == args.model]
114
+ if not models:
115
+ sys.exit(f"No model matching --model {args.model!r}")
116
+
117
+ # Column widths
118
+ w_tag = 10
119
+ w_dtype = 6
120
+ w_size = 8
121
+ w_time = 9
122
+ w_mae = 10
123
+
124
+ header = (
125
+ f"{'Model':<{w_tag}} {'dtype':<{w_dtype}} {'Size(GB)':>{w_size}} "
126
+ f"{'Time(s)':>{w_time}} {'MAE_vs_F32':>{w_mae}}"
127
+ )
128
+ sep = "-" * len(header)
129
+ print(f"\nContext={ctx_len} steps, prediction={PRED_LEN} steps, {N_TIMED_RUNS} timed runs\n")
130
+ print(header)
131
+ print(sep)
132
+
133
+ for tag, _hf in models:
134
+ cfg = config_path(tag)
135
+ if not os.path.exists(cfg):
136
+ print(f"{tag:<{w_tag}} [no config.json — run convert first]")
137
+ continue
138
+
139
+ # F32 reference for relative error
140
+ f32_gguf = gguf_path(tag, "f32")
141
+ f32_ref: np.ndarray | None = None
142
+ if os.path.exists(f32_gguf):
143
+ f32_ref = run_inference(f32_gguf, cfg, csv_input, ctx_len)
144
+
145
+ first_row = True
146
+ for dtype in DTYPES:
147
+ path = gguf_path(tag, dtype)
148
+ model_col = tag if first_row else ""
149
+ first_row = False
150
+
151
+ if not os.path.exists(path):
152
+ print(
153
+ f"{model_col:<{w_tag}} {dtype:<{w_dtype}} {'missing':>{w_size}} "
154
+ f"{'—':>{w_time}} {'—':>{w_mae}}"
155
+ )
156
+ continue
157
+
158
+ size = file_size_gb(path)
159
+ elapsed = time_inference(path, cfg, csv_input, ctx_len, N_TIMED_RUNS)
160
+
161
+ mae_str = "—"
162
+ if f32_ref is not None and dtype != "f32":
163
+ preds = run_inference(path, cfg, csv_input, ctx_len)
164
+ if preds is not None:
165
+ mae = float(np.abs(preds - f32_ref).mean())
166
+ mae_str = f"{mae:.5f}"
167
+
168
+ elapsed_str = f"{elapsed:.2f}" if elapsed is not None else "—"
169
+ print(
170
+ f"{model_col:<{w_tag}} {dtype:<{w_dtype}} {size:>{w_size}.2f} "
171
+ f"{elapsed_str:>{w_time}} {mae_str:>{w_mae}}"
172
+ )
173
+
174
+ if not first_row:
175
+ print(sep)
176
+
177
+
178
+ if __name__ == "__main__":
179
+ main()
toto-rs/gguf/toto-1b-f16.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a8610f41c5e763a284880ee891569e0da0f5d67f477b90488327b2ff6dd5f445
3
+ size 2082086944
toto-rs/gguf/toto-1b-f32.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ca731b1f760fc9fa385cf194536a7c2ad6025cc8817e46bc84d5832e52680035
3
+ size 4164152992
toto-rs/gguf/toto-1b-q8.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f5b6a0539779ea30c291e65b1c47d8a25897ed8b328ad5d935a6d69678bfb5dd
3
+ size 1106119520
toto-rs/gguf/toto-2.5b-f16.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e25a276f593341924e599ebe04d5237153eff25ac2b630c40671d07e1305abcc
3
+ size 4908591136
toto-rs/gguf/toto-2.5b-f32.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3b45c20ab3ddb30c82f883437e40f37dd94ce852f87996982ba509097a62d3da
3
+ size 9817154720
toto-rs/gguf/toto-2.5b-q8.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dc0779a8f85a3edff7a24dfd4081b6fa9f821e558cce1df57fd32cd348a713b0
3
+ size 2607703328
toto-rs/gguf/toto-22m-f16.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5e222378a32d2099f0c27f49335c5e1f6d7062f8f94e6b2fc40e1d18a81bc519
3
+ size 43835552
toto-rs/gguf/toto-22m-f32.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:161bd2bab49feb239711f7a8b474988dfcc65a3ff14157b736582c66fb7362a5
3
+ size 87666720
toto-rs/gguf/toto-22m-q8.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a11e5d8798e76938b6f510aa5557828ce90ced831f84786c01bc80cc8de87046
3
+ size 23289888
toto-rs/gguf/toto-313m-f16.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:39041f941a2dd42c5df24c608b3a3125e210bb34c9347025e34e283f36bad0a2
3
+ size 625383488
toto-rs/gguf/toto-313m-f32.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b9fbe115438aebbc3b4d300f8c727bc8742d60b21918dc8c4a1bb676defad217
3
+ size 1250752704
toto-rs/gguf/toto-313m-q8.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dc5048ed8dc1365608985aebb53b86b1f76396d8131d8cba4cafea6730c50efc
3
+ size 332242368
toto-rs/gguf/toto-4m-f16.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a3863b379552b82c7194a66b002c4989e610428642fdbbc36da3ac152246e1a5
3
+ size 8292192
toto-rs/gguf/toto-4m-f32.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a1e2d64dcd48c170b63c83362919f8dc4a1b12ee5e25ca520edb5dc88854612e
3
+ size 16581088
toto-rs/gguf/toto-4m-q8.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ee2a072101c5bdffae69573227e3f5cd5e0acf021e0934e30d059ce7d4832160
3
+ size 4407072
toto-rs/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ numpy
toto-rs/scripts/compare_python.py ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Compare toto-rs Rust inference vs. Python Toto-2.0 reference.
3
+
4
+ NOTE: The Toto Python package API has diverged from the released weights.
5
+ This script runs the Rust binary for all three dtypes and compares f16/q8
6
+ against f32 (intra-Rust comparison) to measure quantization degradation.
7
+ A full Python vs Rust comparison requires the original toto training codebase.
8
+
9
+ Usage:
10
+ python scripts/compare_python.py [--model 22m] [--dtype f32|f16|q8]
11
+ """
12
+ import argparse
13
+ import math
14
+ import subprocess
15
+ import sys
16
+ import os
17
+ import tempfile
18
+ import numpy as np
19
+
20
+ REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
21
+ RUST_BIN = os.path.join(REPO_ROOT, "target", "release", "toto-rs")
22
+
23
+
24
+ def make_context(n=128):
25
+ t = np.linspace(0, 4 * np.pi, n)
26
+ return (np.sin(t) + 0.05 * t + 1.0).astype(np.float32)
27
+
28
+
29
+ def run_rust(model: str, dtype: str, context: np.ndarray, prediction_length: int = 32) -> np.ndarray:
30
+ gguf = os.path.join(REPO_ROOT, "gguf", f"toto-{model}-{dtype}.gguf")
31
+ config = os.path.join(REPO_ROOT, "models", f"toto-{model}", "config.json")
32
+
33
+ # Write context to a temp CSV
34
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
35
+ tmp = f.name
36
+ f.write("value\n")
37
+ for v in context:
38
+ f.write(f"{v:.8f}\n")
39
+
40
+ try:
41
+ result = subprocess.run(
42
+ [RUST_BIN, "infer", "--gguf", gguf, "--config", config,
43
+ "--input", tmp, "--prediction-length", str(prediction_length)],
44
+ capture_output=True, text=True, check=True,
45
+ )
46
+ finally:
47
+ os.unlink(tmp)
48
+
49
+ lines = [l for l in result.stdout.strip().splitlines() if l]
50
+ # Skip header row, read median quantile column (q0.5)
51
+ header = lines[0].split(",")
52
+ q5_idx = next((i for i, h in enumerate(header) if "q0.5" in h), 4)
53
+ vals = []
54
+ for line in lines[1:]:
55
+ fields = line.split(",")
56
+ vals.append(float(fields[q5_idx]))
57
+ return np.array(vals, dtype=np.float32)
58
+
59
+
60
+ def main():
61
+ parser = argparse.ArgumentParser()
62
+ parser.add_argument("--model", default="22m",
63
+ choices=["4m", "22m", "313m", "1b", "2.5b"])
64
+ parser.add_argument("--dtype", default="f32", choices=["f32", "f16", "q8"])
65
+ parser.add_argument("--prediction-length", type=int, default=32)
66
+ args = parser.parse_args()
67
+
68
+ context = make_context(128)
69
+ print(f"Model: toto-{args.model}, dtype: {args.dtype}, prediction_length: {args.prediction_length}")
70
+
71
+ print(f"Running Rust f32 reference …")
72
+ f32_out = run_rust(args.model, "f32", context, args.prediction_length)
73
+ print(f" f32 shape: {f32_out.shape}, first 5: {f32_out[:5]}")
74
+
75
+ if args.dtype == "f32":
76
+ print(f"\nMax |Δ|: 0.000000 (f32 vs f32)")
77
+ print(f"Mean |Δ|: 0.000000")
78
+ return
79
+
80
+ print(f"Running Rust {args.dtype} …")
81
+ rs_out = run_rust(args.model, args.dtype, context, args.prediction_length)
82
+
83
+ diff = np.abs(f32_out - rs_out)
84
+ print(f"\nComparison (f32 vs {args.dtype}) over {len(diff)} steps:")
85
+ print(f" Max |Δ|: {diff.max():.6f}")
86
+ print(f" Mean |Δ|: {diff.mean():.6f}")
87
+
88
+
89
+ if __name__ == "__main__":
90
+ main()
toto-rs/scripts/convert_all.sh ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # Convert all Toto-2.0 model sizes to F32, F16, and Q8_0 GGUF.
3
+ #
4
+ # Usage:
5
+ # ./scripts/convert_all.sh # convert everything
6
+ # ./scripts/convert_all.sh toto-4m # only convert the 4m model
7
+ # HF_TOKEN=hf_xxx ./scripts/convert_all.sh
8
+ #
9
+ # Outputs go to: gguf/<tag>-<dtype>.gguf
10
+ # Model weights are cached in: models/<tag>/
11
+ set -euo pipefail
12
+
13
+ BIN="./target/release/toto-rs"
14
+ GGUF_DIR="./gguf"
15
+ MODELS_DIR="./models"
16
+
17
+ # model_id:tag pairs
18
+ MODELS=(
19
+ "Datadog/Toto-2.0-4m:toto-4m"
20
+ "Datadog/Toto-2.0-22m:toto-22m"
21
+ "Datadog/Toto-2.0-313m:toto-313m"
22
+ "Datadog/Toto-2.0-1B:toto-1b"
23
+ "Datadog/Toto-2.0-2.5B:toto-2.5b"
24
+ )
25
+ DTYPES=(f32 f16 q8)
26
+
27
+ FILTER="${1:-}" # optional: restrict to one tag
28
+
29
+ mkdir -p "$GGUF_DIR"
30
+
31
+ for entry in "${MODELS[@]}"; do
32
+ model_id="${entry%%:*}"
33
+ tag="${entry##*:}"
34
+
35
+ if [[ -n "$FILTER" && "$tag" != "$FILTER" ]]; then
36
+ continue
37
+ fi
38
+
39
+ model_dir="${MODELS_DIR}/${tag}"
40
+ mkdir -p "$model_dir"
41
+
42
+ for dtype in "${DTYPES[@]}"; do
43
+ output="${GGUF_DIR}/${tag}-${dtype}.gguf"
44
+
45
+ if [[ -f "$output" ]]; then
46
+ echo "[skip] $output already exists"
47
+ continue
48
+ fi
49
+
50
+ echo ""
51
+ echo "=== $model_id → $dtype → $output ==="
52
+ "$BIN" convert \
53
+ --model "$model_id" \
54
+ --model-dir "$model_dir" \
55
+ --output "$output" \
56
+ --dtype "$dtype" \
57
+ ${HF_TOKEN:+--token "$HF_TOKEN"}
58
+ done
59
+ done
60
+
61
+ echo ""
62
+ echo "All done. GGUF files:"
63
+ ls -lh "$GGUF_DIR"/*.gguf 2>/dev/null || echo " (none found)"
toto-rs/scripts/upload.sh ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # Upload source + GGUF files to HuggingFace Hub via the built-in upload subcommand.
3
+ #
4
+ # Usage:
5
+ # HF_TOKEN=hf_... ./scripts/upload.sh [--repo owner/repo-name]
6
+ #
7
+ set -euo pipefail
8
+ cd "$(dirname "$0")/.."
9
+
10
+ BIN=./target/release/toto-rs
11
+
12
+ if [[ ! -f "$BIN" ]]; then
13
+ echo "Binary not found — building release …"
14
+ cargo build --release
15
+ fi
16
+
17
+ exec "$BIN" upload \
18
+ --repo "amaye15/toto-gguf" \
19
+ ${HF_TOKEN:+--token "$HF_TOKEN"} \
20
+ "$@"
toto-rs/src/config.rs ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use serde::Deserialize;
2
+
3
+ /// Mirrors the relevant fields from Datadog/Toto-2.0 config.json.
4
+ /// Accepts the Python model field names (d_model, num_layers, …).
5
+ /// Unknown fields are ignored so we stay forward-compatible.
6
+ #[derive(Debug, Deserialize)]
7
+ pub struct TotoConfig {
8
+ /// Number of transformer layers.
9
+ #[serde(alias = "num_layers", default = "default_u32::<48>")]
10
+ pub num_hidden_layers: u32,
11
+
12
+ /// Hidden / embedding dimension.
13
+ #[serde(alias = "d_model", default = "default_u32::<2048>")]
14
+ pub hidden_size: u32,
15
+
16
+ /// Number of query attention heads.
17
+ #[serde(alias = "num_heads", default = "default_u32::<32>")]
18
+ pub num_attention_heads: u32,
19
+
20
+ /// Number of KV groups (num_groups in Python = GQA groups).
21
+ #[serde(alias = "num_groups", default = "default_u32::<32>")]
22
+ pub num_key_value_heads: u32,
23
+
24
+ /// Per-head QK dimension.
25
+ #[serde(alias = "qk_dim", default = "default_u32::<64>")]
26
+ pub head_dim: u32,
27
+
28
+ /// Patch size in timesteps.
29
+ #[serde(default = "default_u32::<32>")]
30
+ pub patch_size: u32,
31
+
32
+ /// Number of output quantile levels.
33
+ #[serde(default = "default_u32::<9>")]
34
+ pub num_quantiles: u32,
35
+ }
36
+
37
+ impl TotoConfig {
38
+ pub fn from_json(s: &str) -> anyhow::Result<Self> {
39
+ Ok(serde_json::from_str(s)?)
40
+ }
41
+ }
42
+
43
+ fn default_u32<const N: u32>() -> u32 { N }
toto-rs/src/convert.rs ADDED
@@ -0,0 +1,397 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::fs::File;
2
+ use std::io::BufWriter;
3
+ use std::path::Path;
4
+
5
+ use anyhow::Context;
6
+ use indicatif::{ProgressBar, ProgressStyle};
7
+ use safetensors::SafeTensors;
8
+
9
+ use safetensors::Dtype as StDtype;
10
+
11
+ use crate::config::TotoConfig;
12
+ use crate::download::ModelFiles;
13
+ use crate::gguf::{GGMLType, GGUFMetaValue, GGUFWriter};
14
+ use crate::tensor_map::map_tensor_name;
15
+
16
+ /// Options that control how tensors are stored in the output GGUF.
17
+ pub struct ConvertOptions {
18
+ /// Target dtype for all tensors in the output file.
19
+ pub output_dtype: GGMLType,
20
+ }
21
+
22
+ /// Read `files`, convert every tensor, and write a GGUF file to `output_path`.
23
+ pub fn convert(
24
+ model_id: &str,
25
+ files: &ModelFiles,
26
+ config: &TotoConfig,
27
+ opts: &ConvertOptions,
28
+ output_path: &Path,
29
+ ) -> anyhow::Result<()> {
30
+ let mut writer = GGUFWriter::new();
31
+
32
+ // Embed Toto architecture metadata.
33
+ write_metadata(&mut writer, model_id, config);
34
+
35
+ // Discover all tensor names across shards before writing (we need a total
36
+ // for the progress bar and to detect duplicates).
37
+ let shard_bytes = load_shard_bytes(&files.safetensors_shards)?;
38
+ let shard_views: Vec<SafeTensors> = shard_bytes
39
+ .iter()
40
+ .map(|b| SafeTensors::deserialize(b).context("deserialize shard"))
41
+ .collect::<anyhow::Result<_>>()?;
42
+
43
+ let total_tensors: usize = shard_views.iter().map(|s| s.len()).sum();
44
+ println!(
45
+ "Found {} tensors across {} shard(s).",
46
+ total_tensors,
47
+ shard_views.len()
48
+ );
49
+
50
+ let pb = ProgressBar::new(total_tensors as u64);
51
+ pb.set_style(
52
+ ProgressStyle::with_template(
53
+ "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} {msg}",
54
+ )
55
+ .unwrap()
56
+ .progress_chars("=>-"),
57
+ );
58
+
59
+ let mut mapped = 0usize;
60
+ let mut skipped: Vec<String> = Vec::new();
61
+ let mut fallback_count = 0usize;
62
+
63
+ for shard in &shard_views {
64
+ for (hf_name, tensor_view) in shard.tensors() {
65
+ pb.set_message(hf_name.to_string());
66
+
67
+ let gguf_name = match map_tensor_name(&hf_name) {
68
+ Some(n) => n,
69
+ None => {
70
+ skipped.push(hf_name.to_string());
71
+ pb.inc(1);
72
+ continue;
73
+ }
74
+ };
75
+
76
+ let src_dtype = ggml_type_from_st(tensor_view.dtype())
77
+ .with_context(|| format!("tensor {hf_name}: unsupported dtype {:?}", tensor_view.dtype()))?;
78
+
79
+ let raw_data = tensor_view.data();
80
+ let py_shape = tensor_view.shape(); // Python row-major: [d0, d1, ..., d_inner]
81
+ let n_elems: usize = py_shape.iter().product();
82
+ let innermost = py_shape.last().copied().unwrap_or(1);
83
+ let outermost = py_shape.first().copied().unwrap_or(1);
84
+
85
+ // Q8_0: candle requires the innermost dimension (last Python dim, first GGUF dim) to
86
+ // be divisible by 32. When that fails for a 2D weight with a block-aligned outermost
87
+ // dim, we transpose the data and flip the GGUF shape so candle sees the weight with
88
+ // the divisible dimension as innermost. The inference loader corrects the orientation.
89
+ let (dst_dtype, gguf_shape, tensor_data) =
90
+ if opts.output_dtype == GGMLType::Q8_0 && innermost % 32 != 0 {
91
+ if py_shape.len() == 2 && outermost % 32 == 0 && n_elems % 32 == 0 {
92
+ // Transpose: (d_out × d_in) → (d_in × d_out) so d_out becomes innermost.
93
+ let f32_vals = decode_to_f32(raw_data, src_dtype)
94
+ .with_context(|| format!("tensor {hf_name}: decode failed"))?;
95
+ let transposed = transpose_f32(&f32_vals, outermost, innermost);
96
+ let qdata = quantize_q8_0(&transposed)
97
+ .with_context(|| format!("tensor {hf_name}: quantize failed"))?;
98
+ // GGUF shape = Python shape order (NOT reversed). Candle reverses it back,
99
+ // seeing (d_in, d_out) with last dim d_out divisible by 32.
100
+ let gs = py_shape.iter().map(|&d| d as u64).collect();
101
+ (GGMLType::Q8_0, gs, qdata)
102
+ } else {
103
+ // Can't transpose (1D scalar/bias or no suitable fallback dim) → F32.
104
+ fallback_count += 1;
105
+ let data = cast_data(raw_data, src_dtype, GGMLType::F32)
106
+ .with_context(|| format!("tensor {hf_name}: cast failed"))?;
107
+ let gs = py_shape.iter().rev().map(|&d| d as u64).collect();
108
+ (GGMLType::F32, gs, data)
109
+ }
110
+ } else if opts.output_dtype == GGMLType::Q8_0 && n_elems % 32 != 0 {
111
+ // Scalar / tiny tensor — fall back to F32.
112
+ fallback_count += 1;
113
+ let data = cast_data(raw_data, src_dtype, GGMLType::F32)
114
+ .with_context(|| format!("tensor {hf_name}: cast failed"))?;
115
+ let gs = py_shape.iter().rev().map(|&d| d as u64).collect();
116
+ (GGMLType::F32, gs, data)
117
+ } else {
118
+ let dst = opts.output_dtype;
119
+ let data = cast_data(raw_data, src_dtype, dst)
120
+ .with_context(|| format!("tensor {hf_name}: cast failed"))?;
121
+ let gs = py_shape.iter().rev().map(|&d| d as u64).collect();
122
+ (dst, gs, data)
123
+ };
124
+
125
+ writer.add_tensor(gguf_name, gguf_shape, dst_dtype, tensor_data);
126
+ mapped += 1;
127
+ pb.inc(1);
128
+ }
129
+ }
130
+
131
+ pb.finish_with_message("tensors processed");
132
+
133
+ if !skipped.is_empty() {
134
+ eprintln!(
135
+ "\nWarning: {} tensor(s) had unrecognised names and were skipped:",
136
+ skipped.len()
137
+ );
138
+ for name in &skipped {
139
+ eprintln!(" {name}");
140
+ }
141
+ eprintln!("Update tensor_map.rs to include these if needed.");
142
+ }
143
+
144
+ if fallback_count > 0 {
145
+ eprintln!(
146
+ "\nNote: {fallback_count} tensor(s) fell back to F32 (scalars/biases too \
147
+ small for Q8_0 blocks)."
148
+ );
149
+ }
150
+ println!("Writing {mapped} tensors to {} …", output_path.display());
151
+ let out_file = File::create(output_path)
152
+ .with_context(|| format!("create output file {}", output_path.display()))?;
153
+ let mut buf_writer = BufWriter::new(out_file);
154
+ writer.write_to(&mut buf_writer)?;
155
+ println!("Done.");
156
+
157
+ Ok(())
158
+ }
159
+
160
+ fn ggml_type_from_st(dtype: StDtype) -> anyhow::Result<GGMLType> {
161
+ match dtype {
162
+ StDtype::F32 => Ok(GGMLType::F32),
163
+ StDtype::F16 => Ok(GGMLType::F16),
164
+ StDtype::BF16 => Ok(GGMLType::BF16),
165
+ other => anyhow::bail!("unsupported safetensors dtype: {other:?}"),
166
+ }
167
+ }
168
+
169
+ /// Build the GGUF metadata section from the parsed config.
170
+ fn write_metadata(writer: &mut GGUFWriter, model_id: &str, config: &TotoConfig) {
171
+ writer.add_metadata("general.architecture", GGUFMetaValue::String("toto2".into()));
172
+ writer.add_metadata("general.name", GGUFMetaValue::String(model_id.into()));
173
+ writer.add_metadata(
174
+ "toto2.block_count",
175
+ GGUFMetaValue::Uint32(config.num_hidden_layers),
176
+ );
177
+ writer.add_metadata(
178
+ "toto2.embedding_length",
179
+ GGUFMetaValue::Uint32(config.hidden_size),
180
+ );
181
+ writer.add_metadata(
182
+ "toto2.attention.head_count",
183
+ GGUFMetaValue::Uint32(config.num_attention_heads),
184
+ );
185
+ writer.add_metadata(
186
+ "toto2.attention.head_count_kv",
187
+ GGUFMetaValue::Uint32(config.num_key_value_heads),
188
+ );
189
+ writer.add_metadata(
190
+ "toto2.attention.head_dim",
191
+ GGUFMetaValue::Uint32(config.head_dim),
192
+ );
193
+ writer.add_metadata(
194
+ "toto2.patch_size",
195
+ GGUFMetaValue::Uint32(config.patch_size),
196
+ );
197
+ writer.add_metadata(
198
+ "toto2.quantile_count",
199
+ GGUFMetaValue::Uint32(config.num_quantiles),
200
+ );
201
+ }
202
+
203
+ /// Memory-map and read each shard file into a `Vec<u8>`.
204
+ fn load_shard_bytes(shards: &[std::path::PathBuf]) -> anyhow::Result<Vec<Vec<u8>>> {
205
+ shards
206
+ .iter()
207
+ .map(|p| {
208
+ std::fs::read(p).with_context(|| format!("read shard {}", p.display()))
209
+ })
210
+ .collect()
211
+ }
212
+
213
+ /// Cast tensor bytes from `src_dtype` to `dst_dtype`.
214
+ /// If both are the same non-quantized dtype, returns a copy of the input bytes.
215
+ fn cast_data(data: &[u8], src: GGMLType, dst: GGMLType) -> anyhow::Result<Vec<u8>> {
216
+ if src == dst {
217
+ return Ok(data.to_vec());
218
+ }
219
+
220
+ // For Q8_0 destination: decode to f32 first, then quantize.
221
+ if dst == GGMLType::Q8_0 {
222
+ let f32_values = decode_to_f32(data, src)?;
223
+ return quantize_q8_0(&f32_values);
224
+ }
225
+
226
+ match (src, dst) {
227
+ (GGMLType::F32, GGMLType::F16) => {
228
+ let f32_values = parse_f32_le(data)?;
229
+ let mut out = Vec::with_capacity(f32_values.len() * 2);
230
+ for v in f32_values {
231
+ let bits = f32_to_f16_bits(v);
232
+ out.extend_from_slice(&bits.to_le_bytes());
233
+ }
234
+ Ok(out)
235
+ }
236
+ (GGMLType::BF16, GGMLType::F32) => {
237
+ let mut out = Vec::with_capacity(data.len() * 2);
238
+ for chunk in data.chunks_exact(2) {
239
+ let bf16_bits = u16::from_le_bytes([chunk[0], chunk[1]]);
240
+ let f32_bits = (bf16_bits as u32) << 16;
241
+ out.extend_from_slice(&f32_bits.to_le_bytes());
242
+ }
243
+ Ok(out)
244
+ }
245
+ (GGMLType::BF16, GGMLType::F16) => {
246
+ // BF16 → F32 → F16
247
+ let mut out = Vec::with_capacity(data.len());
248
+ for chunk in data.chunks_exact(2) {
249
+ let bf16_bits = u16::from_le_bytes([chunk[0], chunk[1]]);
250
+ let f32_bits = (bf16_bits as u32) << 16;
251
+ let f32_val = f32::from_bits(f32_bits);
252
+ let f16_bits = f32_to_f16_bits(f32_val);
253
+ out.extend_from_slice(&f16_bits.to_le_bytes());
254
+ }
255
+ Ok(out)
256
+ }
257
+ (GGMLType::F16, GGMLType::F32) => {
258
+ let mut out = Vec::with_capacity(data.len() * 2);
259
+ for chunk in data.chunks_exact(2) {
260
+ let f16_bits = u16::from_le_bytes([chunk[0], chunk[1]]);
261
+ let f32_val = f16_to_f32(f16_bits);
262
+ out.extend_from_slice(&f32_val.to_bits().to_le_bytes());
263
+ }
264
+ Ok(out)
265
+ }
266
+ _ => anyhow::bail!("unsupported cast: {src:?} → {dst:?}"),
267
+ }
268
+ }
269
+
270
+ /// Decode any supported float dtype to a Vec<f32>.
271
+ fn decode_to_f32(data: &[u8], src: GGMLType) -> anyhow::Result<Vec<f32>> {
272
+ match src {
273
+ GGMLType::F32 => parse_f32_le(data),
274
+ GGMLType::F16 => {
275
+ data.chunks_exact(2)
276
+ .map(|c| Ok(f16_to_f32(u16::from_le_bytes([c[0], c[1]]))))
277
+ .collect()
278
+ }
279
+ GGMLType::BF16 => {
280
+ data.chunks_exact(2)
281
+ .map(|c| {
282
+ let bf16_bits = u16::from_le_bytes([c[0], c[1]]);
283
+ Ok(f32::from_bits((bf16_bits as u32) << 16))
284
+ })
285
+ .collect()
286
+ }
287
+ GGMLType::Q8_0 => anyhow::bail!("Q8_0 → Q8_0 re-quantization not supported as source"),
288
+ }
289
+ }
290
+
291
+ /// Transpose a flat (n_rows × n_cols) row-major F32 array to (n_cols × n_rows).
292
+ fn transpose_f32(data: &[f32], n_rows: usize, n_cols: usize) -> Vec<f32> {
293
+ let mut out = vec![0.0f32; n_rows * n_cols];
294
+ for r in 0..n_rows {
295
+ for c in 0..n_cols {
296
+ out[c * n_rows + r] = data[r * n_cols + c];
297
+ }
298
+ }
299
+ out
300
+ }
301
+
302
+ /// Quantize a slice of f32 values to GGML Q8_0 block format.
303
+ ///
304
+ /// Each block: `[f16 scale (2 bytes)][32 × i8 (32 bytes)]` = 34 bytes.
305
+ /// Requires `values.len()` to be a multiple of 32.
306
+ fn quantize_q8_0(values: &[f32]) -> anyhow::Result<Vec<u8>> {
307
+ const BLOCK: usize = 32;
308
+ if values.len() % BLOCK != 0 {
309
+ anyhow::bail!(
310
+ "Q8_0 requires element count divisible by {BLOCK}, got {}",
311
+ values.len()
312
+ );
313
+ }
314
+ let n_blocks = values.len() / BLOCK;
315
+ let mut out = vec![0u8; n_blocks * 34];
316
+
317
+ for b in 0..n_blocks {
318
+ let blk = &values[b * BLOCK..(b + 1) * BLOCK];
319
+ let amax = blk.iter().copied().map(f32::abs).fold(0.0f32, f32::max);
320
+ let d = if amax == 0.0 { 0.0f32 } else { amax / 127.0 };
321
+ let d_inv = if d == 0.0 { 0.0f32 } else { 1.0 / d };
322
+
323
+ let base = b * 34;
324
+ let d_f16 = f32_to_f16_bits(d);
325
+ out[base..base + 2].copy_from_slice(&d_f16.to_le_bytes());
326
+ for i in 0..BLOCK {
327
+ let q = (blk[i] * d_inv).round().clamp(-127.0, 127.0) as i8;
328
+ out[base + 2 + i] = q as u8;
329
+ }
330
+ }
331
+ Ok(out)
332
+ }
333
+
334
+ /// Parse a little-endian byte slice into a Vec<f32> (len must be divisible by 4).
335
+ fn parse_f32_le(data: &[u8]) -> anyhow::Result<Vec<f32>> {
336
+ if data.len() % 4 != 0 {
337
+ anyhow::bail!("f32 data length not divisible by 4");
338
+ }
339
+ Ok(data.chunks_exact(4)
340
+ .map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
341
+ .collect())
342
+ }
343
+
344
+ /// Convert f32 to IEEE 754 binary16 bit pattern.
345
+ fn f32_to_f16_bits(v: f32) -> u16 {
346
+ // Fast software path — no hardware f16 needed.
347
+ let bits = v.to_bits();
348
+ let sign = ((bits >> 16) & 0x8000) as u16;
349
+ let exp = ((bits >> 23) & 0xFF) as i32;
350
+ let mantissa = bits & 0x007F_FFFF;
351
+
352
+ if exp == 0xFF {
353
+ // NaN or Inf
354
+ return sign | 0x7C00 | if mantissa != 0 { 0x0200 } else { 0 };
355
+ }
356
+
357
+ let new_exp = exp - 127 + 15;
358
+ if new_exp >= 31 {
359
+ return sign | 0x7C00; // Inf
360
+ }
361
+ if new_exp <= 0 {
362
+ if new_exp < -10 {
363
+ return sign; // Underflow to zero
364
+ }
365
+ // Subnormal
366
+ let m = (mantissa | 0x0080_0000) >> (1 - new_exp);
367
+ return sign | (m >> 13) as u16;
368
+ }
369
+ sign | ((new_exp as u16) << 10) | (mantissa >> 13) as u16
370
+ }
371
+
372
+ /// Convert IEEE 754 binary16 bit pattern to f32.
373
+ fn f16_to_f32(bits: u16) -> f32 {
374
+ let sign = ((bits & 0x8000) as u32) << 16;
375
+ let exp = ((bits >> 10) & 0x1F) as i32;
376
+ let mantissa = (bits & 0x03FF) as u32;
377
+
378
+ let f32_bits = if exp == 0 {
379
+ if mantissa == 0 {
380
+ sign
381
+ } else {
382
+ // Subnormal → normalise
383
+ let mut m = mantissa;
384
+ let mut e = 0i32;
385
+ while m & 0x0400 == 0 {
386
+ m <<= 1;
387
+ e += 1;
388
+ }
389
+ sign | ((127 - 15 - e + 1) as u32) << 23 | (m & 0x03FF) << 13
390
+ }
391
+ } else if exp == 31 {
392
+ sign | 0x7F80_0000 | (mantissa << 13)
393
+ } else {
394
+ sign | ((exp + 127 - 15) as u32) << 23 | (mantissa << 13)
395
+ };
396
+ f32::from_bits(f32_bits)
397
+ }
toto-rs/src/download.rs ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::path::{Path, PathBuf};
2
+
3
+ use anyhow::Context;
4
+ use futures_util::StreamExt;
5
+ use serde::Deserialize;
6
+ use tokio::io::AsyncWriteExt;
7
+
8
+ const HF_BASE: &str = "https://huggingface.co";
9
+
10
+ /// Paths to all local files needed for conversion.
11
+ pub struct ModelFiles {
12
+ pub config_json: PathBuf,
13
+ /// Ordered list of safetensors shard paths, already downloaded locally.
14
+ pub safetensors_shards: Vec<PathBuf>,
15
+ }
16
+
17
+ /// Download (or locate from cache) all model files for `repo_id` into `model_dir`.
18
+ pub async fn download_model(
19
+ repo_id: &str,
20
+ hf_token: Option<&str>,
21
+ model_dir: &Path,
22
+ ) -> anyhow::Result<ModelFiles> {
23
+ let client = build_client(hf_token)?;
24
+ std::fs::create_dir_all(model_dir).context("create model dir")?;
25
+
26
+ println!("Fetching config.json …");
27
+ let config_json = fetch_file(&client, repo_id, "config.json", model_dir).await?;
28
+
29
+ // Detect sharded model by fetching the index file.
30
+ let shards =
31
+ match fetch_file(&client, repo_id, "model.safetensors.index.json", model_dir).await {
32
+ Ok(index_path) => {
33
+ println!("Found sharded model — reading index …");
34
+ resolve_shards(&client, repo_id, &index_path, model_dir).await?
35
+ }
36
+ Err(_) => {
37
+ println!("Fetching model.safetensors …");
38
+ let shard =
39
+ fetch_file(&client, repo_id, "model.safetensors", model_dir).await?;
40
+ vec![shard]
41
+ }
42
+ };
43
+
44
+ Ok(ModelFiles {
45
+ config_json,
46
+ safetensors_shards: shards,
47
+ })
48
+ }
49
+
50
+ fn build_client(hf_token: Option<&str>) -> anyhow::Result<reqwest::Client> {
51
+ let mut headers = reqwest::header::HeaderMap::new();
52
+ headers.insert(
53
+ reqwest::header::USER_AGENT,
54
+ "toto-rs/0.1".parse().unwrap(),
55
+ );
56
+ if let Some(token) = hf_token {
57
+ headers.insert(
58
+ reqwest::header::AUTHORIZATION,
59
+ format!("Bearer {token}").parse().context("invalid HF token")?,
60
+ );
61
+ }
62
+ Ok(reqwest::Client::builder()
63
+ .default_headers(headers)
64
+ .redirect(reqwest::redirect::Policy::limited(10))
65
+ .build()?)
66
+ }
67
+
68
+ /// Download `filename` from `repo_id` into `dest_dir`, resuming if a partial
69
+ /// `.tmp` file already exists. Returns the local path of the completed file.
70
+ async fn fetch_file(
71
+ client: &reqwest::Client,
72
+ repo_id: &str,
73
+ filename: &str,
74
+ dest_dir: &Path,
75
+ ) -> anyhow::Result<PathBuf> {
76
+ let dest = dest_dir.join(filename.replace('/', "_"));
77
+ if dest.exists() {
78
+ println!(" (cached) {filename}");
79
+ return Ok(dest);
80
+ }
81
+
82
+ let dest_tmp = dest.with_extension("tmp");
83
+ let already = if dest_tmp.exists() {
84
+ dest_tmp.metadata()?.len()
85
+ } else {
86
+ 0
87
+ };
88
+
89
+ let url = format!("{HF_BASE}/{repo_id}/resolve/main/{filename}");
90
+
91
+ let mut req = client.get(&url);
92
+ if already > 0 {
93
+ req = req.header(reqwest::header::RANGE, format!("bytes={already}-"));
94
+ println!(" Resuming {filename} from {} MB …", already / 1_000_000);
95
+ }
96
+
97
+ let response = req
98
+ .send()
99
+ .await
100
+ .with_context(|| format!("GET {url}"))?;
101
+
102
+ let status = response.status();
103
+ // 206 = partial content (resume accepted), 200 = full content
104
+ if !status.is_success() {
105
+ anyhow::bail!("HTTP {status} fetching {filename} from {repo_id}");
106
+ }
107
+
108
+ // If server ignored the Range header and sent 200, truncate the tmp file.
109
+ let (file, resume_offset) = if status == reqwest::StatusCode::PARTIAL_CONTENT {
110
+ let f = tokio::fs::OpenOptions::new()
111
+ .append(true)
112
+ .open(&dest_tmp)
113
+ .await
114
+ .with_context(|| format!("open tmp {}", dest_tmp.display()))?;
115
+ (f, already)
116
+ } else {
117
+ let f = tokio::fs::File::create(&dest_tmp)
118
+ .await
119
+ .with_context(|| format!("create tmp {}", dest_tmp.display()))?;
120
+ (f, 0)
121
+ };
122
+
123
+ let total = response
124
+ .content_length()
125
+ .map(|n| n + resume_offset)
126
+ .unwrap_or(0);
127
+
128
+ let pb = indicatif::ProgressBar::new(total);
129
+ pb.set_style(
130
+ indicatif::ProgressStyle::with_template(
131
+ " {msg} [{bar:40}] {bytes}/{total_bytes} ({bytes_per_sec}, eta {eta})",
132
+ )
133
+ .unwrap()
134
+ .progress_chars("=>-"),
135
+ );
136
+ pb.set_message(filename.to_string());
137
+ pb.set_position(resume_offset);
138
+
139
+ {
140
+ let mut file = file;
141
+ let mut stream = response.bytes_stream();
142
+ while let Some(chunk) = stream.next().await {
143
+ let chunk = chunk.with_context(|| format!("stream chunk of {filename}"))?;
144
+ pb.inc(chunk.len() as u64);
145
+ file.write_all(&chunk)
146
+ .await
147
+ .with_context(|| format!("write chunk to {}", dest_tmp.display()))?;
148
+ }
149
+ }
150
+
151
+ pb.finish_and_clear();
152
+ std::fs::rename(&dest_tmp, &dest)
153
+ .with_context(|| format!("rename tmp → {}", dest.display()))?;
154
+
155
+ Ok(dest)
156
+ }
157
+
158
+ /// Parse the shard index JSON and download every unique shard.
159
+ async fn resolve_shards(
160
+ client: &reqwest::Client,
161
+ repo_id: &str,
162
+ index_path: &Path,
163
+ cache_dir: &Path,
164
+ ) -> anyhow::Result<Vec<PathBuf>> {
165
+ #[derive(Deserialize)]
166
+ struct Index {
167
+ weight_map: std::collections::HashMap<String, String>,
168
+ }
169
+
170
+ let raw = std::fs::read_to_string(index_path).context("read index json")?;
171
+ let index: Index = serde_json::from_str(&raw).context("parse index json")?;
172
+
173
+ let mut shard_names: Vec<String> = index.weight_map.into_values().collect();
174
+ shard_names.sort();
175
+ shard_names.dedup();
176
+
177
+ let mut paths = Vec::with_capacity(shard_names.len());
178
+ for name in &shard_names {
179
+ println!(" Fetching {name} …");
180
+ let p = fetch_file(client, repo_id, name, cache_dir).await?;
181
+ paths.push(p);
182
+ }
183
+ Ok(paths)
184
+ }
toto-rs/src/gguf/mod.rs ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ pub mod types;
2
+ pub mod writer;
3
+
4
+ pub use types::{GGMLType, GGUFMetaValue};
5
+ pub use writer::GGUFWriter;
toto-rs/src/gguf/types.rs ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /// GGML tensor data types used in GGUF files.
2
+ /// Values match the ggml_type enum in ggml.h.
3
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
4
+ #[repr(u32)]
5
+ pub enum GGMLType {
6
+ F32 = 0,
7
+ F16 = 1,
8
+ /// Q8_0: blocks of 32 × i8 with a shared f16 scale (34 bytes/block).
9
+ Q8_0 = 8,
10
+ BF16 = 30,
11
+ }
12
+
13
+ impl GGMLType {
14
+ /// (block_elems, bytes_per_block) for block-quantized types; None for float types.
15
+ /// Q8_0: 32 × i8 values + 1 × f16 scale = 34 bytes/block.
16
+ #[allow(dead_code)]
17
+ pub fn block_shape(self) -> Option<(usize, usize)> {
18
+ match self {
19
+ GGMLType::Q8_0 => Some((32, 34)),
20
+ _ => None,
21
+ }
22
+ }
23
+ }
24
+
25
+ /// GGUF metadata value types (gguf_metadata_value_type).
26
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
27
+ #[repr(u32)]
28
+ #[allow(dead_code)]
29
+ pub enum GGUFValueType {
30
+ Uint8 = 0,
31
+ Int8 = 1,
32
+ Uint16 = 2,
33
+ Int16 = 3,
34
+ Uint32 = 4,
35
+ Int32 = 5,
36
+ Float32 = 6,
37
+ Bool = 7,
38
+ String = 8,
39
+ Array = 9,
40
+ Uint64 = 10,
41
+ Int64 = 11,
42
+ Float64 = 12,
43
+ }
44
+
45
+ /// A typed metadata value for a GGUF key-value pair.
46
+ #[derive(Debug, Clone)]
47
+ #[allow(dead_code)]
48
+ pub enum GGUFMetaValue {
49
+ Uint8(u8),
50
+ Int8(i8),
51
+ Uint16(u16),
52
+ Int16(i16),
53
+ Uint32(u32),
54
+ Int32(i32),
55
+ Float32(f32),
56
+ Bool(bool),
57
+ String(String),
58
+ Uint64(u64),
59
+ Int64(i64),
60
+ Float64(f64),
61
+ ArrayUint32(Vec<u32>),
62
+ ArrayString(Vec<String>),
63
+ }
64
+
65
+ impl GGUFMetaValue {
66
+ pub fn value_type(&self) -> GGUFValueType {
67
+ match self {
68
+ GGUFMetaValue::Uint8(_) => GGUFValueType::Uint8,
69
+ GGUFMetaValue::Int8(_) => GGUFValueType::Int8,
70
+ GGUFMetaValue::Uint16(_) => GGUFValueType::Uint16,
71
+ GGUFMetaValue::Int16(_) => GGUFValueType::Int16,
72
+ GGUFMetaValue::Uint32(_) => GGUFValueType::Uint32,
73
+ GGUFMetaValue::Int32(_) => GGUFValueType::Int32,
74
+ GGUFMetaValue::Float32(_) => GGUFValueType::Float32,
75
+ GGUFMetaValue::Bool(_) => GGUFValueType::Bool,
76
+ GGUFMetaValue::String(_) => GGUFValueType::String,
77
+ GGUFMetaValue::Uint64(_) => GGUFValueType::Uint64,
78
+ GGUFMetaValue::Int64(_) => GGUFValueType::Int64,
79
+ GGUFMetaValue::Float64(_) => GGUFValueType::Float64,
80
+ GGUFMetaValue::ArrayUint32(_) | GGUFMetaValue::ArrayString(_) => GGUFValueType::Array,
81
+ }
82
+ }
83
+ }
toto-rs/src/gguf/writer.rs ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::io::{self, Seek, Write};
2
+
3
+ use byteorder::{LittleEndian, WriteBytesExt};
4
+
5
+ use super::types::{GGMLType, GGUFMetaValue, GGUFValueType};
6
+
7
+ const GGUF_MAGIC: &[u8; 4] = b"GGUF";
8
+ const GGUF_VERSION: u32 = 3;
9
+ const ALIGNMENT: u64 = 32;
10
+
11
+ /// Describes one tensor's position in the GGUF data section.
12
+ #[derive(Debug)]
13
+ struct TensorInfo {
14
+ name: String,
15
+ shape: Vec<u64>,
16
+ dtype: GGMLType,
17
+ /// Byte offset from the start of the tensor data block.
18
+ offset: u64,
19
+ /// Raw tensor bytes (row-major, little-endian).
20
+ data: Vec<u8>,
21
+ }
22
+
23
+ /// Streaming GGUF v3 writer.
24
+ ///
25
+ /// Call [`add_metadata`] for every key-value pair, then [`add_tensor`] for
26
+ /// every tensor, then [`write_to`] to flush the complete file.
27
+ pub struct GGUFWriter {
28
+ metadata: Vec<(String, GGUFMetaValue)>,
29
+ tensors: Vec<TensorInfo>,
30
+ /// Running data-block offset for the next tensor.
31
+ data_offset: u64,
32
+ }
33
+
34
+ impl GGUFWriter {
35
+ pub fn new() -> Self {
36
+ Self {
37
+ metadata: Vec::new(),
38
+ tensors: Vec::new(),
39
+ data_offset: 0,
40
+ }
41
+ }
42
+
43
+ pub fn add_metadata(&mut self, key: impl Into<String>, value: GGUFMetaValue) {
44
+ self.metadata.push((key.into(), value));
45
+ }
46
+
47
+ /// Buffer a tensor. `data` must already be in the target dtype byte layout.
48
+ pub fn add_tensor(
49
+ &mut self,
50
+ name: impl Into<String>,
51
+ shape: Vec<u64>,
52
+ dtype: GGMLType,
53
+ data: Vec<u8>,
54
+ ) {
55
+ let offset = self.data_offset;
56
+ let padded_len = round_up(data.len() as u64, ALIGNMENT);
57
+ self.data_offset += padded_len;
58
+ self.tensors.push(TensorInfo {
59
+ name: name.into(),
60
+ shape,
61
+ dtype,
62
+ offset,
63
+ data,
64
+ });
65
+ }
66
+
67
+ /// Serialize the complete GGUF file to `writer`.
68
+ pub fn write_to<W: Write + Seek>(&self, writer: &mut W) -> anyhow::Result<()> {
69
+ // --- header ---
70
+ writer.write_all(GGUF_MAGIC)?;
71
+ writer.write_u32::<LittleEndian>(GGUF_VERSION)?;
72
+ writer.write_u64::<LittleEndian>(self.tensors.len() as u64)?;
73
+ writer.write_u64::<LittleEndian>(self.metadata.len() as u64)?;
74
+
75
+ // --- metadata key-value pairs ---
76
+ for (key, value) in &self.metadata {
77
+ write_string(writer, key)?;
78
+ writer.write_u32::<LittleEndian>(value.value_type() as u32)?;
79
+ write_value(writer, value)?;
80
+ }
81
+
82
+ // --- tensor info ---
83
+ for t in &self.tensors {
84
+ write_string(writer, &t.name)?;
85
+ writer.write_u32::<LittleEndian>(t.shape.len() as u32)?;
86
+ for &dim in &t.shape {
87
+ writer.write_u64::<LittleEndian>(dim)?;
88
+ }
89
+ writer.write_u32::<LittleEndian>(t.dtype as u32)?;
90
+ writer.write_u64::<LittleEndian>(t.offset)?;
91
+ }
92
+
93
+ // --- align to ALIGNMENT before tensor data ---
94
+ let pos = writer.stream_position()?;
95
+ let aligned = round_up(pos, ALIGNMENT);
96
+ if aligned > pos {
97
+ let pad = vec![0u8; (aligned - pos) as usize];
98
+ writer.write_all(&pad)?;
99
+ }
100
+
101
+ // --- tensor data (each padded to ALIGNMENT) ---
102
+ for t in &self.tensors {
103
+ writer.write_all(&t.data)?;
104
+ let remainder = t.data.len() as u64 % ALIGNMENT;
105
+ if remainder != 0 {
106
+ let pad = vec![0u8; (ALIGNMENT - remainder) as usize];
107
+ writer.write_all(&pad)?;
108
+ }
109
+ }
110
+
111
+ Ok(())
112
+ }
113
+ }
114
+
115
+ fn round_up(value: u64, align: u64) -> u64 {
116
+ (value + align - 1) / align * align
117
+ }
118
+
119
+ fn write_string<W: Write>(writer: &mut W, s: &str) -> io::Result<()> {
120
+ writer.write_u64::<LittleEndian>(s.len() as u64)?;
121
+ writer.write_all(s.as_bytes())
122
+ }
123
+
124
+ fn write_value<W: Write>(writer: &mut W, value: &GGUFMetaValue) -> anyhow::Result<()> {
125
+ match value {
126
+ GGUFMetaValue::Uint8(v) => writer.write_u8(*v)?,
127
+ GGUFMetaValue::Int8(v) => writer.write_i8(*v)?,
128
+ GGUFMetaValue::Uint16(v) => writer.write_u16::<LittleEndian>(*v)?,
129
+ GGUFMetaValue::Int16(v) => writer.write_i16::<LittleEndian>(*v)?,
130
+ GGUFMetaValue::Uint32(v) => writer.write_u32::<LittleEndian>(*v)?,
131
+ GGUFMetaValue::Int32(v) => writer.write_i32::<LittleEndian>(*v)?,
132
+ GGUFMetaValue::Float32(v) => writer.write_f32::<LittleEndian>(*v)?,
133
+ GGUFMetaValue::Bool(v) => writer.write_u8(*v as u8)?,
134
+ GGUFMetaValue::String(v) => write_string(writer, v)?,
135
+ GGUFMetaValue::Uint64(v) => writer.write_u64::<LittleEndian>(*v)?,
136
+ GGUFMetaValue::Int64(v) => writer.write_i64::<LittleEndian>(*v)?,
137
+ GGUFMetaValue::Float64(v) => writer.write_f64::<LittleEndian>(*v)?,
138
+ GGUFMetaValue::ArrayUint32(arr) => {
139
+ writer.write_u32::<LittleEndian>(GGUFValueType::Uint32 as u32)?;
140
+ writer.write_u64::<LittleEndian>(arr.len() as u64)?;
141
+ for v in arr {
142
+ writer.write_u32::<LittleEndian>(*v)?;
143
+ }
144
+ }
145
+ GGUFMetaValue::ArrayString(arr) => {
146
+ writer.write_u32::<LittleEndian>(GGUFValueType::String as u32)?;
147
+ writer.write_u64::<LittleEndian>(arr.len() as u64)?;
148
+ for s in arr {
149
+ write_string(writer, s)?;
150
+ }
151
+ }
152
+ }
153
+ Ok(())
154
+ }
toto-rs/src/infer/mod.rs ADDED
@@ -0,0 +1,644 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License.
2
+ //
3
+ // This product includes software developed at Datadog (https://www.datadoghq.com/)
4
+ // Copyright 2026 Datadog, Inc.
5
+
6
+ //! Toto-2 inference engine.
7
+ //!
8
+ //! Unit-scaling (μP) inference notes:
9
+ //! * `uu.Linear` → `F.linear(x, w, b) / sqrt(fan_in)`
10
+ //! * `uu.LinearReadout` → `F.linear(x, w, b) / fan_in`
11
+ //! * `U.silu` → `F.silu(x) * 1.766782948` (unit-scaled)
12
+ //! * τ-rule: `residual_split` is identity; `residual_add(h,skip,τ)` = `h*(τ/d) + skip*(1/d)` where `d=√(1+τ²)`
13
+ //! * `PerDimScale(q, w)` = `q * softplus(w) / log(2)` — the 0.52103 unit-scaling factors cancel
14
+
15
+ mod rope;
16
+
17
+ use std::io::{BufReader, Read, Seek};
18
+ use std::path::Path;
19
+
20
+ use anyhow::{bail, Context, Result};
21
+ use candle_core::quantized::gguf_file;
22
+ use candle_core::{DType, Device, Tensor, D};
23
+ use candle_nn::ops;
24
+
25
+ use rope::RopeCache;
26
+
27
+ const SILU_SCALE: f64 = 1.766782948312328;
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Config
31
+ // ---------------------------------------------------------------------------
32
+
33
+ #[derive(Clone, Debug)]
34
+ pub struct InferConfig {
35
+ pub d_model: usize,
36
+ pub num_layers: usize,
37
+ pub num_heads: usize,
38
+ pub num_groups: usize,
39
+ pub qk_dim: usize,
40
+ pub v_dim: usize,
41
+ pub patch_size: usize,
42
+ pub norm_eps: f64,
43
+ pub layer_group_size: usize,
44
+ pub num_variate_layers_per_group: usize,
45
+ pub variate_layer_first: bool,
46
+ pub use_xpos: bool,
47
+ pub residual_mult: f64,
48
+ pub residual_attn_ratio: f64,
49
+ /// Run forward pass in F64 to match PyTorch numerical precision.
50
+ /// Weights are cast to F64 at load time (~2× memory). Default: false.
51
+ pub compute_f64: bool,
52
+ }
53
+
54
+ impl InferConfig {
55
+ fn is_variate_layer(&self, idx: usize) -> bool {
56
+ if self.variate_layer_first {
57
+ idx % self.layer_group_size < self.num_variate_layers_per_group
58
+ } else {
59
+ idx % self.layer_group_size
60
+ >= self.layer_group_size - self.num_variate_layers_per_group
61
+ }
62
+ }
63
+ fn q_size(&self) -> usize { self.qk_dim * self.num_heads }
64
+ fn k_size(&self) -> usize { self.qk_dim * self.num_groups }
65
+ fn v_size(&self) -> usize { self.v_dim * self.num_groups }
66
+ }
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Weights
70
+ // ---------------------------------------------------------------------------
71
+
72
+ struct ResidualMlpWeights {
73
+ l1_w: Tensor, l1_b: Tensor,
74
+ l2_w: Tensor, l2_b: Tensor,
75
+ skip_w: Tensor, skip_b: Tensor,
76
+ tau: f64,
77
+ is_output: bool, // true → use LinearReadout scale (1/fan_in) for l2 and skip
78
+ }
79
+
80
+ struct BlockWeights {
81
+ attn_qkv_w: Tensor,
82
+ attn_qkv_b: Option<Tensor>,
83
+ attn_out_w: Tensor,
84
+ attn_out_b: Option<Tensor>,
85
+ attn_pds: Option<Tensor>, // [qk_dim] raw param
86
+ attn_tau: f64,
87
+ ffn_up_w: Tensor,
88
+ ffn_down_w: Tensor,
89
+ mlp_tau: f64,
90
+ }
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // Model
94
+ // ---------------------------------------------------------------------------
95
+
96
+ pub struct TotoModel {
97
+ device: Device,
98
+ pub config: InferConfig,
99
+ rope: RopeCache,
100
+ patch_proj: ResidualMlpWeights,
101
+ blocks: Vec<BlockWeights>,
102
+ output_head: ResidualMlpWeights,
103
+ }
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // GGUF loading helpers (free functions to avoid multiple &mut borrows)
107
+ // ---------------------------------------------------------------------------
108
+
109
+ fn load_tensor(
110
+ content: &gguf_file::Content,
111
+ reader: &mut (impl Read + Seek),
112
+ name: &str,
113
+ device: &Device,
114
+ dtype: DType,
115
+ ) -> Result<Tensor> {
116
+ let qt = content
117
+ .tensor(reader, name, device)
118
+ .with_context(|| format!("load tensor '{name}'"))?;
119
+ Ok(qt.dequantize(device)?.to_dtype(dtype)?)
120
+ }
121
+
122
+ fn try_load_tensor(
123
+ content: &gguf_file::Content,
124
+ reader: &mut (impl Read + Seek),
125
+ name: &str,
126
+ device: &Device,
127
+ dtype: DType,
128
+ ) -> Result<Option<Tensor>> {
129
+ match content.tensor(reader, name, device) {
130
+ Ok(qt) => Ok(Some(qt.dequantize(device)?.to_dtype(dtype)?)),
131
+ Err(_) => Ok(None),
132
+ }
133
+ }
134
+
135
+ /// Compute attn_tau and mlp_tau for each layer using the transformer residual scaling rule.
136
+ ///
137
+ /// This reproduces `uu.transformer_residual_scaling_rule` + the per-layer buffer assignment
138
+ /// from `SelfAttentionTransformerLayer.__init__` in the Python model.
139
+ fn compute_taus(num_layers: usize, residual_mult: f64, residual_attn_ratio: f64) -> (Vec<f64>, Vec<f64>) {
140
+ let total_depth = 2 * num_layers;
141
+ let alpha_mlp = residual_mult * (2.0 / (1.0 + residual_attn_ratio.powi(2))).sqrt();
142
+ let alpha_attn = residual_attn_ratio * alpha_mlp;
143
+
144
+ let tau = |index: usize| -> f64 {
145
+ let n_attn = (index + 1) / 2;
146
+ let n_mlp = index / 2;
147
+ let num = if index % 2 == 0 { alpha_attn } else { alpha_mlp };
148
+ let den = (total_depth as f64 / 2.0
149
+ + n_attn as f64 * alpha_attn.powi(2)
150
+ + n_mlp as f64 * alpha_mlp.powi(2))
151
+ .sqrt();
152
+ num / den
153
+ };
154
+
155
+ (0..num_layers).map(|i| (tau(2 * i), tau(2 * i + 1))).unzip()
156
+ }
157
+
158
+ impl TotoModel {
159
+ pub fn load(gguf_path: &Path, config: InferConfig) -> Result<Self> {
160
+ let device = Device::Cpu;
161
+ let dtype = if config.compute_f64 { DType::F64 } else { DType::F32 };
162
+ let file = std::fs::File::open(gguf_path)
163
+ .with_context(|| format!("open {}", gguf_path.display()))?;
164
+ let mut reader = BufReader::new(file);
165
+ let content = gguf_file::Content::read(&mut reader)
166
+ .context("parse GGUF header")?;
167
+
168
+ macro_rules! ld {
169
+ ($name:expr) => { load_tensor(&content, &mut reader, $name, &device, dtype) };
170
+ }
171
+ macro_rules! tld {
172
+ ($name:expr) => { try_load_tensor(&content, &mut reader, $name, &device, dtype) };
173
+ }
174
+
175
+ // --- patch_proj ---
176
+ let patch_proj = ResidualMlpWeights {
177
+ l1_w: ld!("patch_proj.linear1.weight")?,
178
+ l1_b: ld!("patch_proj.linear1.bias")?,
179
+ l2_w: ld!("patch_proj.linear2.weight")?,
180
+ l2_b: ld!("patch_proj.linear2.bias")?,
181
+ skip_w: ld!("patch_proj.skip_proj.weight")?,
182
+ skip_b: ld!("patch_proj.skip_proj.bias")?,
183
+ tau: 1.0,
184
+ is_output: false,
185
+ };
186
+
187
+ // Tau values are deterministic given the config — compute rather than load
188
+ // (avoids candle's inability to dequantize 0-dim tensors)
189
+ let (attn_taus, mlp_taus) = compute_taus(
190
+ config.num_layers,
191
+ config.residual_mult,
192
+ config.residual_attn_ratio,
193
+ );
194
+
195
+ // --- transformer blocks ---
196
+ let mut blocks = Vec::with_capacity(config.num_layers);
197
+ for n in 0..config.num_layers {
198
+ blocks.push(BlockWeights {
199
+ attn_qkv_w: ld!(&format!("blk.{n}.attn_qkv.weight"))?,
200
+ attn_qkv_b: tld!(&format!("blk.{n}.attn_qkv.bias"))?,
201
+ attn_out_w: ld!(&format!("blk.{n}.attn_output.weight"))?,
202
+ attn_out_b: tld!(&format!("blk.{n}.attn_output.bias"))?,
203
+ attn_pds: tld!(&format!("blk.{n}.attn_pds.weight"))?,
204
+ attn_tau: attn_taus[n],
205
+ ffn_up_w: ld!(&format!("blk.{n}.ffn_up.weight"))?,
206
+ ffn_down_w: {
207
+ let w = ld!(&format!("blk.{n}.ffn_down.weight"))?;
208
+ // Q8_0 converter stores this weight transposed so candle's innermost
209
+ // dim (d_model) is block-aligned. Detect by dim(0) == d_ff and flip back.
210
+ if w.dim(0)? != config.d_model {
211
+ w.t()?.contiguous()?
212
+ } else {
213
+ w
214
+ }
215
+ },
216
+ mlp_tau: mlp_taus[n],
217
+ });
218
+ }
219
+
220
+ // --- output_head ---
221
+ let output_head = ResidualMlpWeights {
222
+ l1_w: ld!("output_head.linear1.weight")?,
223
+ l1_b: ld!("output_head.linear1.bias")?,
224
+ l2_w: ld!("output_head.linear2.weight")?,
225
+ l2_b: ld!("output_head.linear2.bias")?,
226
+ skip_w: ld!("output_head.skip_proj.weight")?,
227
+ skip_b: ld!("output_head.skip_proj.bias")?,
228
+ tau: 1.0,
229
+ is_output: true,
230
+ };
231
+
232
+ let rope = RopeCache::new(config.qk_dim, 8192);
233
+ Ok(Self { device, config, rope, patch_proj, blocks, output_head })
234
+ }
235
+
236
+ // -----------------------------------------------------------------------
237
+ // Public API
238
+ // -----------------------------------------------------------------------
239
+
240
+ /// Forecast from context.
241
+ ///
242
+ /// `target[n_var][ctx_len]` — observed values.
243
+ /// `mask[n_var][ctx_len]` — true for valid observations.
244
+ /// Returns `quantiles[9][n_var][prediction_length]`.
245
+ ///
246
+ /// Toto is a full-context model: the prediction region is appended to the
247
+ /// context as masked (all-zero) patches before the forward pass. The model
248
+ /// attends over the full sequence and outputs quantiles for the future region.
249
+ pub fn forecast(
250
+ &self,
251
+ target: &[Vec<f32>],
252
+ mask: &[Vec<bool>],
253
+ prediction_length: usize,
254
+ ) -> Result<Vec<Vec<Vec<f32>>>> {
255
+ let n_var = target.len();
256
+ let ctx_len = target[0].len();
257
+ let patch_size = self.config.patch_size;
258
+
259
+ if ctx_len % patch_size != 0 {
260
+ bail!("ctx_len ({ctx_len}) must be divisible by patch_size ({patch_size})");
261
+ }
262
+ let ctx_patches = ctx_len / patch_size;
263
+ // Number of forecast patches needed (ceiling division)
264
+ let fcst_patches = (prediction_length + patch_size - 1) / patch_size;
265
+ let total_patches = ctx_patches + fcst_patches;
266
+
267
+ // Causal patched std scaler on context only
268
+ let mut locs = Vec::with_capacity(n_var);
269
+ let mut scales = Vec::with_capacity(n_var);
270
+ for v in 0..n_var {
271
+ let (loc, scale) = causal_patched_std_scaler(&target[v], &mask[v], patch_size);
272
+ locs.push(loc);
273
+ scales.push(scale);
274
+ }
275
+
276
+ // Build full input: context patches (observed) + forecast patches (masked zeros)
277
+ // Shape: [1, n_var, total_patches, 2*patch_size]
278
+ let mut patch_data = vec![0.0f32; n_var * total_patches * 2 * patch_size];
279
+ for v in 0..n_var {
280
+ // Context patches
281
+ for p in 0..ctx_patches {
282
+ let base = v * total_patches * 2 * patch_size + p * 2 * patch_size;
283
+ for i in 0..patch_size {
284
+ let t = p * patch_size + i;
285
+ let obs = mask[v][t];
286
+ let val = if obs {
287
+ (((target[v][t] - locs[v][t]) / scales[v][t]) as f64).asinh() as f32
288
+ } else {
289
+ 0.0
290
+ };
291
+ patch_data[base + i] = val;
292
+ patch_data[base + patch_size + i] = if obs { 0.0 } else { 1.0 };
293
+ }
294
+ }
295
+ // Forecast patches: all zeros, not observed (mask channel = 1)
296
+ for p in ctx_patches..total_patches {
297
+ let base = v * total_patches * 2 * patch_size + p * 2 * patch_size;
298
+ for i in 0..patch_size {
299
+ patch_data[base + i] = 0.0;
300
+ patch_data[base + patch_size + i] = 1.0; // not observed
301
+ }
302
+ }
303
+ }
304
+
305
+ let dtype = if self.config.compute_f64 { DType::F64 } else { DType::F32 };
306
+ let x = Tensor::from_vec(
307
+ patch_data,
308
+ (1usize, n_var, total_patches, 2 * patch_size),
309
+ &self.device,
310
+ )?.to_dtype(dtype)?;
311
+
312
+ // patch_proj (InputResidualMLP)
313
+ let x = self.forward_residual_mlp(&x, &self.patch_proj)?;
314
+
315
+ // transformer
316
+ let x = self.forward_transformer(x, n_var, total_patches)?;
317
+
318
+ // output_head (OutputResidualMLP)
319
+ let x = self.forward_residual_mlp(&x, &self.output_head)?;
320
+ // x: [1, n_var, total_patches, patch_size * 9]
321
+
322
+ // Next-patch predictor: output at position i predicts patch i+1.
323
+ // Python: x_out[..., -(block+1):-1] where block=fcst_patches
324
+ // = positions [ctx_patches-1, ctx_patches+fcst_patches-1)
325
+ let x = x.narrow(2, ctx_patches - 1, fcst_patches)?;
326
+ // x: [1, n_var, fcst_patches, patch_size * 9]
327
+ let fcst_steps = fcst_patches * patch_size;
328
+
329
+ // Unflatten → [1, n_var, fcst_patches, patch_size, 9]
330
+ let out = x.reshape((1usize, n_var, fcst_patches, patch_size, 9))?;
331
+ // Permute → [9, 1, n_var, fcst_patches, patch_size]
332
+ let out = out.permute([4, 0, 1, 2, 3])?.contiguous()?;
333
+ // Flatten → [9, n_var, fcst_steps]
334
+ let out = out.reshape((9usize, n_var, fcst_steps))?;
335
+
336
+ // Trim to exactly prediction_length (if fcst_steps > prediction_length)
337
+ let out = if fcst_steps > prediction_length {
338
+ out.narrow(2, 0, prediction_length)?
339
+ } else {
340
+ out
341
+ };
342
+ // Cast back to F32 for output (harmless if already F32)
343
+ let out_data = out.to_dtype(DType::F32)?.to_vec3::<f32>()?; // [9][n_var][pred_len]
344
+
345
+ // Denormalise: sinh(quantile) * scale + loc (using final-patch stats)
346
+ let loc_final: Vec<f32> = (0..n_var).map(|v| locs[v][ctx_len - 1]).collect();
347
+ let scale_final: Vec<f32> = (0..n_var).map(|v| scales[v][ctx_len - 1]).collect();
348
+
349
+ let mut result = vec![vec![vec![0.0f32; prediction_length]; n_var]; 9];
350
+ for q in 0..9 {
351
+ for v in 0..n_var {
352
+ for t in 0..prediction_length {
353
+ let raw = out_data[q][v][t] as f64;
354
+ result[q][v][t] = (raw.sinh() as f32) * scale_final[v] + loc_final[v];
355
+ }
356
+ }
357
+ }
358
+ Ok(result)
359
+ }
360
+
361
+ // -----------------------------------------------------------------------
362
+ // ResidualMLP forward
363
+ // -----------------------------------------------------------------------
364
+
365
+ fn forward_residual_mlp(&self, x: &Tensor, w: &ResidualMlpWeights) -> Result<Tensor> {
366
+ // residual_split at inference = identity; both branches see x
367
+ let h = uu_linear(x, &w.l1_w, Some(&w.l1_b))?;
368
+ let h = uu_silu(&h)?;
369
+
370
+ let h = if w.is_output {
371
+ uu_linear_readout(&h, &w.l2_w, Some(&w.l2_b))?
372
+ } else {
373
+ uu_linear(&h, &w.l2_w, Some(&w.l2_b))?
374
+ };
375
+
376
+ let skip = if w.is_output {
377
+ uu_linear_readout(x, &w.skip_w, Some(&w.skip_b))?
378
+ } else {
379
+ uu_linear(x, &w.skip_w, Some(&w.skip_b))?
380
+ };
381
+
382
+ residual_add(&h, &skip, w.tau)
383
+ }
384
+
385
+ // -----------------------------------------------------------------------
386
+ // Transformer
387
+ // -----------------------------------------------------------------------
388
+
389
+ fn forward_transformer(&self, mut x: Tensor, n_var: usize, num_patches: usize) -> Result<Tensor> {
390
+ for (idx, blk) in self.blocks.iter().enumerate() {
391
+ x = if self.config.is_variate_layer(idx) {
392
+ self.forward_variate_layer(x, blk, n_var, num_patches)?
393
+ } else {
394
+ self.forward_time_layer(x, blk, n_var, num_patches)?
395
+ };
396
+ }
397
+ rms_norm(&x, self.config.norm_eps)
398
+ }
399
+
400
+ fn forward_time_layer(
401
+ &self,
402
+ x: Tensor,
403
+ blk: &BlockWeights,
404
+ n_var: usize,
405
+ num_patches: usize,
406
+ ) -> Result<Tensor> {
407
+ let cfg = &self.config;
408
+ // [1, n_var, num_patches, d_model] → [n_var, num_patches, d_model]
409
+ let state = x.reshape((n_var, num_patches, cfg.d_model))?;
410
+
411
+ let skip = state.clone();
412
+ let normed = rms_norm(&state, cfg.norm_eps)?;
413
+ let seq_ids: Vec<u32> = (0..num_patches as u32).collect();
414
+ let attn_out = self.forward_attention(&normed, blk, &seq_ids, /*is_variate=*/false)?;
415
+ let state = residual_add(&attn_out, &skip, blk.attn_tau)?;
416
+
417
+ let skip = state.clone();
418
+ let normed = rms_norm(&state, cfg.norm_eps)?;
419
+ let ffn_out = self.forward_ffn(&normed, blk)?;
420
+ let state = residual_add(&ffn_out, &skip, blk.mlp_tau)?;
421
+
422
+ // Restore [1, n_var, num_patches, d_model]
423
+ Ok(state.reshape((1usize, n_var, num_patches, cfg.d_model))?)
424
+ }
425
+
426
+ fn forward_variate_layer(
427
+ &self,
428
+ x: Tensor,
429
+ blk: &BlockWeights,
430
+ n_var: usize,
431
+ num_patches: usize,
432
+ ) -> Result<Tensor> {
433
+ let cfg = &self.config;
434
+ // [1, n_var, num_patches, d] → [num_patches, n_var, d]
435
+ let state = x.permute([0, 2, 1, 3])?.contiguous()?.reshape((num_patches, n_var, cfg.d_model))?;
436
+
437
+ let skip = state.clone();
438
+ let normed = rms_norm(&state, cfg.norm_eps)?;
439
+ let attn_out = self.forward_attention(&normed, blk, &[], /*is_variate=*/true)?;
440
+ let state = residual_add(&attn_out, &skip, blk.attn_tau)?;
441
+
442
+ let skip = state.clone();
443
+ let normed = rms_norm(&state, cfg.norm_eps)?;
444
+ let ffn_out = self.forward_ffn(&normed, blk)?;
445
+ let state = residual_add(&ffn_out, &skip, blk.mlp_tau)?;
446
+
447
+ // [num_patches, n_var, d] → [1, n_var, num_patches, d]
448
+ Ok(state
449
+ .reshape((1usize, num_patches, n_var, cfg.d_model))?
450
+ .permute([0, 2, 1, 3])?
451
+ .contiguous()?)
452
+ }
453
+
454
+ // -----------------------------------------------------------------------
455
+ // Self-attention
456
+ // -----------------------------------------------------------------------
457
+
458
+ fn forward_attention(
459
+ &self,
460
+ state: &Tensor,
461
+ blk: &BlockWeights,
462
+ seq_ids: &[u32],
463
+ is_variate: bool,
464
+ ) -> Result<Tensor> {
465
+ let cfg = &self.config;
466
+ let (batch, seq, _d) = state.dims3()?;
467
+
468
+ // Fused QKV — narrow then contiguous (narrow on last-dim creates non-contiguous views)
469
+ let qkv = uu_linear(state, &blk.attn_qkv_w, blk.attn_qkv_b.as_ref())?;
470
+ let q = qkv.narrow(D::Minus1, 0, cfg.q_size())?.contiguous()?;
471
+ let k = qkv.narrow(D::Minus1, cfg.q_size(), cfg.k_size())?.contiguous()?;
472
+ let v = qkv.narrow(D::Minus1, cfg.q_size() + cfg.k_size(), cfg.v_size())?.contiguous()?;
473
+
474
+ // [batch, seq, heads, head_dim] → [batch, heads, seq, head_dim]
475
+ let q = q.reshape((batch, seq, cfg.num_heads, cfg.qk_dim))?.permute([0, 2, 1, 3])?.contiguous()?;
476
+ let k = k.reshape((batch, seq, cfg.num_groups, cfg.qk_dim))?.permute([0, 2, 1, 3])?.contiguous()?;
477
+ let v = v.reshape((batch, seq, cfg.num_groups, cfg.v_dim))?.permute([0, 2, 1, 3])?.contiguous()?;
478
+
479
+ // PerDimScale on Q
480
+ let q = match blk.attn_pds.as_ref() {
481
+ Some(pds_w) => apply_per_dim_scale(&q, pds_w)?,
482
+ None => q,
483
+ };
484
+
485
+ // xPos-RoPE (time layers only)
486
+ let (q, k) = if !is_variate && !seq_ids.is_empty() && cfg.use_xpos {
487
+ let q = self.rope.apply(&q, seq_ids, 1.0, &self.device)?;
488
+ let k = self.rope.apply(&k, seq_ids, -1.0, &self.device)?;
489
+ (q, k)
490
+ } else {
491
+ (q, k)
492
+ };
493
+
494
+ // MuP scale: 1/qk_dim
495
+ let scale = 1.0 / cfg.qk_dim as f64;
496
+ let scores = (q.matmul(&k.transpose(D::Minus1, D::Minus2)?)? * scale)?;
497
+
498
+ // Causal mask for time layers
499
+ let scores = if !is_variate {
500
+ apply_causal_mask(scores, seq)?
501
+ } else {
502
+ scores
503
+ };
504
+
505
+ let attn = ops::softmax_last_dim(&scores)?;
506
+ let out = attn.matmul(&v)?;
507
+ // [batch, heads, seq, v_dim] → [batch, seq, heads*v_dim]
508
+ let out = out.permute([0, 2, 1, 3])?.contiguous()?.reshape((batch, seq, cfg.num_heads * cfg.v_dim))?;
509
+
510
+ uu_linear(&out, &blk.attn_out_w, blk.attn_out_b.as_ref())
511
+ }
512
+
513
+ // -----------------------------------------------------------------------
514
+ // SwiGLU FFN
515
+ // -----------------------------------------------------------------------
516
+
517
+ fn forward_ffn(&self, x: &Tensor, blk: &BlockWeights) -> Result<Tensor> {
518
+ let fc1_out = uu_linear(x, &blk.ffn_up_w, None)?;
519
+ let half = fc1_out.dim(D::Minus1)? / 2;
520
+ let gate = fc1_out.narrow(D::Minus1, 0, half)?;
521
+ let val = fc1_out.narrow(D::Minus1, half, half)?;
522
+ // Standard F.silu (not unit-scaled) as in the Python FFN
523
+ let activated = (gate * ops::silu(&val)?)?;
524
+ uu_linear(&activated, &blk.ffn_down_w, None)
525
+ }
526
+ }
527
+
528
+ // ---------------------------------------------------------------------------
529
+ // Unit-scaling ops
530
+ // ---------------------------------------------------------------------------
531
+
532
+ /// Unit-scaled linear for arbitrary batch shapes.
533
+ ///
534
+ /// Flattens leading dims to 2D for matmul (candle doesn't broadcast 2D weights against nD inputs),
535
+ /// then restores the original batch shape.
536
+ fn uu_linear(x: &Tensor, w: &Tensor, b: Option<&Tensor>) -> Result<Tensor> {
537
+ linear_with_scale(x, w, b, 1.0 / (w.dim(1)? as f64).sqrt())
538
+ }
539
+
540
+ fn uu_linear_readout(x: &Tensor, w: &Tensor, b: Option<&Tensor>) -> Result<Tensor> {
541
+ linear_with_scale(x, w, b, 1.0 / w.dim(1)? as f64)
542
+ }
543
+
544
+ fn linear_with_scale(x: &Tensor, w: &Tensor, b: Option<&Tensor>, scale: f64) -> Result<Tensor> {
545
+ let shape = x.dims().to_vec();
546
+ let d_in = *shape.last().unwrap();
547
+ let batch: usize = shape[..shape.len() - 1].iter().product();
548
+ let d_out = w.dim(0)?;
549
+
550
+ let x_flat = x.reshape((batch, d_in))?;
551
+ let out_flat = x_flat.matmul(&w.t()?)?; // [batch, d_out]
552
+
553
+ let mut out_shape = shape[..shape.len() - 1].to_vec();
554
+ out_shape.push(d_out);
555
+ let out = out_flat.reshape(out_shape)?;
556
+ let out = if let Some(b) = b { out.broadcast_add(b)? } else { out };
557
+ Ok((out * scale)?)
558
+ }
559
+
560
+ fn uu_silu(x: &Tensor) -> Result<Tensor> {
561
+ Ok((ops::silu(x)? * SILU_SCALE)?)
562
+ }
563
+
564
+ fn rms_norm(x: &Tensor, eps: f64) -> Result<Tensor> {
565
+ let x2 = x.sqr()?;
566
+ let mean_x2 = x2.mean_keepdim(D::Minus1)?;
567
+ let rms = mean_x2.affine(1.0, eps)?.sqrt()?;
568
+ Ok(x.broadcast_div(&rms)?)
569
+ }
570
+
571
+ fn residual_add(h: &Tensor, skip: &Tensor, tau: f64) -> Result<Tensor> {
572
+ let denom = (1.0 + tau * tau).sqrt();
573
+ Ok(((h * (tau / denom))? + (skip * (1.0 / denom))?)?)
574
+ }
575
+
576
+ fn apply_per_dim_scale(q: &Tensor, pds_w: &Tensor) -> Result<Tensor> {
577
+ // Python: q * F.softplus(pds_w) / log(2)
578
+ // The 0.52103 unit-scaling factors cancel between uu.softplus and per_dim_scale.
579
+ let sp = (pds_w.exp()? + 1.0)?.log()?; // F.softplus
580
+ let log2 = std::f64::consts::LN_2;
581
+ let r = (sp / log2)?;
582
+ Ok(q.broadcast_mul(&r)?)
583
+ }
584
+
585
+ fn apply_causal_mask(scores: Tensor, seq: usize) -> Result<Tensor> {
586
+ let mut mask_data = vec![0.0f32; seq * seq];
587
+ for i in 0..seq {
588
+ for j in (i + 1)..seq {
589
+ mask_data[i * seq + j] = f32::NEG_INFINITY;
590
+ }
591
+ }
592
+ let mask = Tensor::from_vec(mask_data, (seq, seq), scores.device())?
593
+ .to_dtype(scores.dtype())?;
594
+ Ok(scores.broadcast_add(&mask)?)
595
+ }
596
+
597
+ // ---------------------------------------------------------------------------
598
+ // Causal patched std scaler
599
+ // ---------------------------------------------------------------------------
600
+
601
+ fn causal_patched_std_scaler(
602
+ data: &[f32],
603
+ mask: &[bool],
604
+ patch_size: usize,
605
+ ) -> (Vec<f32>, Vec<f32>) {
606
+ let n = data.len();
607
+ let num_patches = n / patch_size;
608
+
609
+ let mut cum_count = 0.0f64;
610
+ let mut m1 = 0.0f64;
611
+ let mut m2 = 0.0f64;
612
+ let correction = 1.0f64;
613
+ let minimum_scale = 1e-6f64;
614
+
615
+ let mut patch_loc = vec![0.0f32; num_patches];
616
+ let mut patch_scale = vec![1e-6f32; num_patches];
617
+
618
+ for p in 0..num_patches {
619
+ for i in 0..patch_size {
620
+ let t = p * patch_size + i;
621
+ if mask[t] {
622
+ let x = data[t] as f64;
623
+ cum_count += 1.0;
624
+ let prev_m1 = m1;
625
+ m1 += (x - m1) / cum_count;
626
+ m2 += (x - prev_m1) * (x - m1);
627
+ }
628
+ }
629
+ patch_loc[p] = m1 as f32;
630
+ let denom = (cum_count - correction).max(1.0);
631
+ patch_scale[p] = (m2 / denom).sqrt().max(minimum_scale) as f32;
632
+ }
633
+
634
+ let mut loc = vec![0.0f32; n];
635
+ let mut scale = vec![1e-6f32; n];
636
+ for p in 0..num_patches {
637
+ for i in 0..patch_size {
638
+ let t = p * patch_size + i;
639
+ loc[t] = patch_loc[p];
640
+ scale[t] = patch_scale[p];
641
+ }
642
+ }
643
+ (loc, scale)
644
+ }
toto-rs/src/infer/rope.rs ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License.
2
+ //
3
+ // This product includes software developed at Datadog (https://www.datadoghq.com/)
4
+ // Copyright 2026 Datadog, Inc.
5
+
6
+ use anyhow::Result;
7
+ use candle_core::{Device, Tensor, D};
8
+
9
+ /// Precomputed RoPE (with xPos scaling) for a partial head dimension.
10
+ ///
11
+ /// Applied to the first `proj_width` dims of each head.
12
+ /// For the 2.5B model: `partial_factor=(0.0, 0.5)` → `proj_width = qk_dim/2 = 32`.
13
+ pub struct RopeCache {
14
+ pub proj_width: usize,
15
+ cos: Vec<Vec<f32>>, // [max_len][proj_width]
16
+ sin: Vec<Vec<f32>>, // [max_len][proj_width]
17
+ xpos_base_scale: Vec<f32>, // [proj_width/2]
18
+ }
19
+
20
+ impl RopeCache {
21
+ pub fn new(qk_dim: usize, max_len: usize) -> Self {
22
+ let proj_width = qk_dim / 2; // partial_factor (0.0, 0.5)
23
+ let half = proj_width / 2;
24
+ let base = 10000_f32;
25
+
26
+ let theta: Vec<f32> = (0..half)
27
+ .map(|i| 1.0 / base.powf(2.0 * i as f32 / proj_width as f32))
28
+ .collect();
29
+
30
+ let mut cos = vec![vec![0.0f32; proj_width]; max_len];
31
+ let mut sin = vec![vec![0.0f32; proj_width]; max_len];
32
+ for m in 0..max_len {
33
+ for i in 0..half {
34
+ let angle = m as f32 * theta[i];
35
+ cos[m][2 * i] = angle.cos();
36
+ cos[m][2 * i + 1] = angle.cos();
37
+ sin[m][2 * i] = angle.sin();
38
+ sin[m][2 * i + 1] = angle.sin();
39
+ }
40
+ }
41
+
42
+ // xPos base scale: (2i + 0.4*proj_width) / (1.4*proj_width)
43
+ let xpos_base_scale: Vec<f32> = (0..half)
44
+ .map(|i| ((2 * i) as f32 + 0.4 * proj_width as f32) / (1.4 * proj_width as f32))
45
+ .collect();
46
+
47
+ Self { proj_width, cos, sin, xpos_base_scale }
48
+ }
49
+
50
+ /// Apply xPos-RoPE to query or key.
51
+ ///
52
+ /// `x`: `[batch, heads, seq, qk_dim]` — first `proj_width` dims are rotated.
53
+ /// `seq_ids`: position index per sequence step.
54
+ /// `xpos_exponent`: +1.0 for query, -1.0 for key.
55
+ pub fn apply(
56
+ &self,
57
+ x: &Tensor,
58
+ seq_ids: &[u32],
59
+ xpos_exponent: f32,
60
+ device: &Device,
61
+ ) -> Result<Tensor> {
62
+ let qk_dim = x.dim(D::Minus1)?;
63
+ let proj_width = self.proj_width;
64
+ let half = proj_width / 2;
65
+ let seq_len = seq_ids.len();
66
+
67
+ let max_pos = seq_ids.iter().copied().max().unwrap_or(0) as f32;
68
+ let center = ((max_pos as u32 + 1) / 2) as f32;
69
+
70
+ let mut cos_data = vec![0.0f32; seq_len * proj_width];
71
+ let mut sin_data = vec![0.0f32; seq_len * proj_width];
72
+ for (si, &pos) in seq_ids.iter().enumerate() {
73
+ let power = (pos as f32 - center) / 256.0; // xpos_scale_base = 256
74
+ for i in 0..half {
75
+ let xpos_s = self.xpos_base_scale[i].powf(power).powf(xpos_exponent);
76
+ let c = self.cos[pos as usize][2 * i] * xpos_s;
77
+ let s = self.sin[pos as usize][2 * i] * xpos_s;
78
+ cos_data[si * proj_width + 2 * i] = c;
79
+ cos_data[si * proj_width + 2 * i + 1] = c;
80
+ sin_data[si * proj_width + 2 * i] = s;
81
+ sin_data[si * proj_width + 2 * i + 1] = s;
82
+ }
83
+ }
84
+
85
+ // [1, 1, seq, proj_width] for broadcasting; cast to match x dtype (e.g. F64)
86
+ let pos_cos = Tensor::from_vec(cos_data, (1usize, 1, seq_len, proj_width), device)?
87
+ .to_dtype(x.dtype())?;
88
+ let pos_sin = Tensor::from_vec(sin_data, (1usize, 1, seq_len, proj_width), device)?
89
+ .to_dtype(x.dtype())?;
90
+
91
+ // Split rotated vs pass-through dims
92
+ let x_rot = x.narrow(D::Minus1, 0, proj_width)?;
93
+ let rot_x = rotate_half(&x_rot, proj_width)?;
94
+ let rotated = (x_rot.broadcast_mul(&pos_cos)? + rot_x.broadcast_mul(&pos_sin)?)?;
95
+
96
+ let result = if qk_dim > proj_width {
97
+ let x_pass = x.narrow(D::Minus1, proj_width, qk_dim - proj_width)?.contiguous()?;
98
+ Tensor::cat(&[&rotated, &x_pass], D::Minus1)?
99
+ } else {
100
+ rotated
101
+ };
102
+ Ok(result.contiguous()?)
103
+ }
104
+ }
105
+
106
+ /// rotate_half: [a0, b0, a1, b1, ...] → [-b0, a0, -b1, a1, ...]
107
+ fn rotate_half(x: &Tensor, proj_width: usize) -> Result<Tensor> {
108
+ let half = proj_width / 2;
109
+ let mut shape = x.dims().to_vec();
110
+ let last = shape.len() - 1;
111
+ shape[last] = half;
112
+ shape.push(2);
113
+ let x_pairs = x.reshape(shape)?;
114
+ let x1 = x_pairs.narrow(D::Minus1, 0, 1)?;
115
+ let x2 = x_pairs.narrow(D::Minus1, 1, 1)?;
116
+ let rotated = Tensor::cat(&[&x2.neg()?, &x1], D::Minus1)?;
117
+ let out_shape = x.dims().to_vec();
118
+ Ok(rotated.reshape(out_shape)?)
119
+ }
toto-rs/src/main.rs ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ mod config;
2
+ mod convert;
3
+ mod download;
4
+ mod gguf;
5
+ mod infer;
6
+ mod tensor_map;
7
+ mod upload;
8
+
9
+ use std::path::PathBuf;
10
+
11
+ use anyhow::Context;
12
+ use clap::{Parser, Subcommand, ValueEnum};
13
+
14
+ use config::TotoConfig;
15
+ use convert::{convert, ConvertOptions};
16
+ use download::download_model;
17
+ use gguf::GGMLType;
18
+ use infer::{InferConfig, TotoModel};
19
+
20
+ #[derive(Parser)]
21
+ #[command(name = "toto-rs", about = "Convert and run Datadog/Toto-2.0-2.5B")]
22
+ struct Cli {
23
+ #[command(subcommand)]
24
+ command: Command,
25
+ }
26
+
27
+ #[derive(Subcommand)]
28
+ enum Command {
29
+ /// Upload source + GGUF files to HuggingFace Hub.
30
+ Upload {
31
+ /// HuggingFace repo to upload to (owner/name).
32
+ #[arg(short, long, default_value = "amaye15/toto-gguf")]
33
+ repo: String,
34
+ /// HuggingFace API token (or set HF_TOKEN env var).
35
+ #[arg(long, env = "HF_TOKEN")]
36
+ token: String,
37
+ },
38
+ /// Download a Toto model from HuggingFace and convert it to GGUF.
39
+ Convert {
40
+ #[arg(short, long, default_value = "Datadog/Toto-2.0-2.5B")]
41
+ model: String,
42
+ #[arg(short, long, default_value = "toto.gguf")]
43
+ output: PathBuf,
44
+ #[arg(long, default_value = "f16")]
45
+ dtype: DtypeArg,
46
+ #[arg(long, default_value = "models")]
47
+ model_dir: PathBuf,
48
+ #[arg(long, env = "HF_TOKEN")]
49
+ token: Option<String>,
50
+ },
51
+ /// Print all tensor names in a local safetensors file.
52
+ InspectTensors {
53
+ path: PathBuf,
54
+ },
55
+ /// Run Toto-2 time-series forecasting from a GGUF file.
56
+ ///
57
+ /// Reads CSV input (header + one column per variate).
58
+ /// Prints 9-quantile forecasts as CSV to stdout.
59
+ Infer {
60
+ /// Path to the GGUF file.
61
+ #[arg(short, long, default_value = "toto.gguf")]
62
+ gguf: PathBuf,
63
+
64
+ /// Path to config.json from the original HuggingFace model.
65
+ #[arg(long, default_value = "models/config.json")]
66
+ config: PathBuf,
67
+
68
+ /// CSV file: header row + one column per variate, one row per timestep.
69
+ /// Use '-' to read from stdin.
70
+ #[arg(short, long)]
71
+ input: PathBuf,
72
+
73
+ /// Number of future timesteps to forecast.
74
+ #[arg(short, long, default_value = "64")]
75
+ prediction_length: usize,
76
+
77
+ /// Context length fed to the model (must be divisible by patch_size=32).
78
+ /// Defaults to the last 4096 timesteps (or all if shorter).
79
+ #[arg(long)]
80
+ context_length: Option<usize>,
81
+
82
+ /// Run the forward pass in F64 (double precision) to match PyTorch
83
+ /// numerical accuracy. Uses ~2× memory. Default: F32.
84
+ #[arg(long = "f64", default_value_t = false)]
85
+ use_f64: bool,
86
+ },
87
+ }
88
+
89
+ #[derive(Clone, ValueEnum)]
90
+ enum DtypeArg {
91
+ F32,
92
+ F16,
93
+ Bf16,
94
+ Q8,
95
+ }
96
+
97
+ impl From<DtypeArg> for GGMLType {
98
+ fn from(d: DtypeArg) -> Self {
99
+ match d {
100
+ DtypeArg::F32 => GGMLType::F32,
101
+ DtypeArg::F16 => GGMLType::F16,
102
+ DtypeArg::Bf16 => GGMLType::BF16,
103
+ DtypeArg::Q8 => GGMLType::Q8_0,
104
+ }
105
+ }
106
+ }
107
+
108
+ #[tokio::main]
109
+ async fn main() -> anyhow::Result<()> {
110
+ let cli = Cli::parse();
111
+
112
+ match cli.command {
113
+ Command::Upload { repo, token } => {
114
+ let crate_root = std::env::current_dir().context("current dir")?;
115
+ let model_root = crate_root.parent()
116
+ .map(|p| p.to_path_buf())
117
+ .unwrap_or(crate_root);
118
+ upload::run(&repo, &token, &model_root).await?;
119
+ }
120
+
121
+ Command::Convert { model, output, dtype, model_dir, token } => {
122
+ println!("Downloading {model} into {} …", model_dir.display());
123
+ let files = download_model(&model, token.as_deref(), &model_dir)
124
+ .await
125
+ .context("download failed")?;
126
+
127
+ let config_str = std::fs::read_to_string(&files.config_json)
128
+ .context("read config.json")?;
129
+ let config = TotoConfig::from_json(&config_str)
130
+ .context("parse config.json")?;
131
+
132
+ println!(
133
+ "Config: {} layers, hidden={}, heads={}",
134
+ config.num_hidden_layers, config.hidden_size, config.num_attention_heads
135
+ );
136
+
137
+ let opts = ConvertOptions { output_dtype: dtype.into() };
138
+ convert(&model, &files, &config, &opts, &output)?;
139
+ println!("Wrote {}", output.display());
140
+ }
141
+
142
+ Command::InspectTensors { path } => {
143
+ let bytes = std::fs::read(&path)
144
+ .with_context(|| format!("read {}", path.display()))?;
145
+ let tensors = safetensors::SafeTensors::deserialize(&bytes)
146
+ .context("deserialize safetensors")?;
147
+ println!("Tensors in {}:", path.display());
148
+ let mut names: Vec<_> = tensors.names().into_iter().collect();
149
+ names.sort();
150
+ for name in names {
151
+ let t = tensors.tensor(name).unwrap();
152
+ println!(" {name:60} {:?} {:?}", t.dtype(), t.shape());
153
+ }
154
+ }
155
+
156
+ Command::Infer { gguf, config, input, prediction_length, context_length, use_f64 } => {
157
+ // Load model config
158
+ let config_str = std::fs::read_to_string(&config)
159
+ .with_context(|| format!("read {}", config.display()))?;
160
+ let toto_config: serde_json::Value = serde_json::from_str(&config_str)?;
161
+
162
+ let infer_config = InferConfig {
163
+ d_model: toto_config["d_model"].as_u64().unwrap_or(2048) as usize,
164
+ num_layers: toto_config["num_layers"].as_u64().unwrap_or(48) as usize,
165
+ num_heads: toto_config["num_heads"].as_u64().unwrap_or(32) as usize,
166
+ num_groups: toto_config["num_groups"].as_u64().unwrap_or(32) as usize,
167
+ qk_dim: toto_config["qk_dim"].as_u64().unwrap_or(64) as usize,
168
+ v_dim: toto_config["v_dim"].as_u64().unwrap_or(64) as usize,
169
+ patch_size: toto_config["patch_size"].as_u64().unwrap_or(32) as usize,
170
+ norm_eps: toto_config["norm_eps"].as_f64().unwrap_or(5e-4),
171
+ layer_group_size: toto_config["layer_group_size"].as_u64().unwrap_or(48) as usize,
172
+ num_variate_layers_per_group: toto_config["num_variate_layers_per_group"]
173
+ .as_u64().unwrap_or(1) as usize,
174
+ variate_layer_first: toto_config["variate_layer_first"].as_bool().unwrap_or(false),
175
+ use_xpos: toto_config["use_xpos"].as_bool().unwrap_or(true),
176
+ residual_mult: toto_config["residual_mult"].as_f64().unwrap_or(0.75),
177
+ residual_attn_ratio: toto_config["residual_attn_ratio"].as_f64().unwrap_or(5.136215466577748),
178
+ compute_f64: use_f64,
179
+ };
180
+
181
+ let patch_size = infer_config.patch_size;
182
+
183
+ // Read CSV input
184
+ let csv_str = if input.as_os_str() == "-" {
185
+ use std::io::Read;
186
+ let mut s = String::new();
187
+ std::io::stdin().read_to_string(&mut s)?;
188
+ s
189
+ } else {
190
+ std::fs::read_to_string(&input)
191
+ .with_context(|| format!("read {}", input.display()))?
192
+ };
193
+
194
+ let (mut data, header) = parse_csv(&csv_str)?;
195
+ let n_var = data.len();
196
+ let total_len = data[0].len();
197
+
198
+ // Determine context window
199
+ let ctx_len = {
200
+ let max_ctx = context_length.unwrap_or(4096);
201
+ let usable = total_len.min(max_ctx);
202
+ // Round down to nearest multiple of patch_size
203
+ (usable / patch_size) * patch_size
204
+ };
205
+ if ctx_len == 0 {
206
+ anyhow::bail!(
207
+ "Context length is 0. Need at least {patch_size} timesteps."
208
+ );
209
+ }
210
+
211
+ // Trim to context window (last ctx_len points)
212
+ let start = total_len.saturating_sub(ctx_len);
213
+ for v in 0..n_var {
214
+ data[v] = data[v][start..].to_vec();
215
+ }
216
+
217
+ // All-observed mask
218
+ let mask: Vec<Vec<bool>> = (0..n_var)
219
+ .map(|_| vec![true; ctx_len])
220
+ .collect();
221
+
222
+ eprintln!(
223
+ "Loading model from {} …",
224
+ gguf.display()
225
+ );
226
+ let model = TotoModel::load(&gguf, infer_config)
227
+ .context("load model")?;
228
+
229
+ eprintln!(
230
+ "Running inference: {n_var} variate(s), {ctx_len} context steps → \
231
+ {prediction_length} forecast steps"
232
+ );
233
+ let quantiles = model.forecast(&data, &mask, prediction_length)
234
+ .context("forecast")?;
235
+ // quantiles: [9][n_var][prediction_length]
236
+
237
+ // Print CSV header
238
+ let quantile_levels = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9];
239
+ let mut cols = Vec::new();
240
+ for q in quantile_levels {
241
+ for v in &header {
242
+ cols.push(format!("{v}_q{q:.1}"));
243
+ }
244
+ }
245
+ println!("{}", cols.join(","));
246
+
247
+ // Print forecast rows
248
+ for t in 0..prediction_length {
249
+ let mut row = Vec::new();
250
+ for qi in 0..9 {
251
+ for v in 0..n_var {
252
+ row.push(format!("{:.6}", quantiles[qi][v][t]));
253
+ }
254
+ }
255
+ println!("{}", row.join(","));
256
+ }
257
+ }
258
+ }
259
+
260
+ Ok(())
261
+ }
262
+
263
+ /// Parse CSV: first row is header, remaining rows are timestep values.
264
+ /// Returns `(data[n_var][timesteps], header[n_var])`.
265
+ fn parse_csv(csv: &str) -> anyhow::Result<(Vec<Vec<f32>>, Vec<String>)> {
266
+ let mut lines = csv.lines();
267
+ let header_line = lines.next().context("CSV is empty")?;
268
+ let headers: Vec<String> = header_line.split(',').map(|s| s.trim().to_string()).collect();
269
+ let n_var = headers.len();
270
+
271
+ let mut data: Vec<Vec<f32>> = vec![Vec::new(); n_var];
272
+ for (row_idx, line) in lines.enumerate() {
273
+ if line.trim().is_empty() {
274
+ continue;
275
+ }
276
+ let fields: Vec<&str> = line.split(',').collect();
277
+ if fields.len() != n_var {
278
+ anyhow::bail!(
279
+ "Row {row_idx}: expected {n_var} columns, got {}",
280
+ fields.len()
281
+ );
282
+ }
283
+ for (v, f) in fields.iter().enumerate() {
284
+ let val: f32 = f.trim().parse()
285
+ .with_context(|| format!("row {row_idx}, col {v}: {:?}", f.trim()))?;
286
+ data[v].push(val);
287
+ }
288
+ }
289
+ Ok((data, headers))
290
+ }
toto-rs/src/tensor_map.rs ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /// Convert a Toto HuggingFace tensor name to the GGUF blk.N convention.
2
+ /// Returns `None` for unrecognised names (caller will warn and skip them).
3
+ ///
4
+ /// Actual tensor names were confirmed by running `inspect-tensors` on the
5
+ /// downloaded model.safetensors checkpoint.
6
+ pub fn map_tensor_name(hf_name: &str) -> Option<String> {
7
+ // --- patch projection (two-layer MLP with skip connection) ---
8
+ match hf_name {
9
+ "patch_proj.linear1.weight" => return Some("patch_proj.linear1.weight".into()),
10
+ "patch_proj.linear1.bias" => return Some("patch_proj.linear1.bias".into()),
11
+ "patch_proj.linear2.weight" => return Some("patch_proj.linear2.weight".into()),
12
+ "patch_proj.linear2.bias" => return Some("patch_proj.linear2.bias".into()),
13
+ "patch_proj.skip_proj.weight" => return Some("patch_proj.skip_proj.weight".into()),
14
+ "patch_proj.skip_proj.bias" => return Some("patch_proj.skip_proj.bias".into()),
15
+ _ => {}
16
+ }
17
+
18
+ // --- output head projection (two-layer MLP with skip connection) ---
19
+ match hf_name {
20
+ "output_head.param_projection.proj.linear1.weight" =>
21
+ return Some("output_head.linear1.weight".into()),
22
+ "output_head.param_projection.proj.linear1.bias" =>
23
+ return Some("output_head.linear1.bias".into()),
24
+ "output_head.param_projection.proj.linear2.weight" =>
25
+ return Some("output_head.linear2.weight".into()),
26
+ "output_head.param_projection.proj.linear2.bias" =>
27
+ return Some("output_head.linear2.bias".into()),
28
+ "output_head.param_projection.proj.skip_proj.weight" =>
29
+ return Some("output_head.skip_proj.weight".into()),
30
+ "output_head.param_projection.proj.skip_proj.bias" =>
31
+ return Some("output_head.skip_proj.bias".into()),
32
+ _ => {}
33
+ }
34
+
35
+ // --- per-block tensors ---
36
+ // Pattern: transformer.layers.{N}.<suffix>
37
+ let rest = hf_name.strip_prefix("transformer.layers.")?;
38
+ let (block_str, suffix) = rest.split_once('.')?;
39
+ let block: u32 = block_str.parse().ok()?;
40
+
41
+ let gguf_suffix = map_block_suffix(suffix)?;
42
+ Some(format!("blk.{block}.{gguf_suffix}"))
43
+ }
44
+
45
+ fn map_block_suffix(suffix: &str) -> Option<&'static str> {
46
+ Some(match suffix {
47
+ // Fused QKV projection
48
+ "attn.in_proj.weight" => "attn_qkv.weight",
49
+ "attn.in_proj.bias" => "attn_qkv.bias",
50
+ // Output projection
51
+ "attn.out_proj.weight" => "attn_output.weight",
52
+ "attn.out_proj.bias" => "attn_output.bias",
53
+ // Per-dimension scale (Toto-specific learned scaling)
54
+ "attn._pds.per_dim_scale" => "attn_pds.weight",
55
+ // Attention temperature (learned scalar per block)
56
+ "attn_tau" => "attn_tau",
57
+ // Feed-forward
58
+ "ffn.fc1.weight" => "ffn_up.weight",
59
+ "ffn.fc1.bias" => "ffn_up.bias",
60
+ "ffn.fc2.weight" => "ffn_down.weight",
61
+ "ffn.fc2.bias" => "ffn_down.bias",
62
+ // MLP temperature (learned scalar per block)
63
+ "mlp_tau" => "mlp_tau",
64
+ _ => return None,
65
+ })
66
+ }
67
+
68
+ #[cfg(test)]
69
+ mod tests {
70
+ use super::*;
71
+
72
+ #[test]
73
+ fn patch_proj() {
74
+ assert_eq!(
75
+ map_tensor_name("patch_proj.linear1.weight"),
76
+ Some("patch_proj.linear1.weight".into())
77
+ );
78
+ assert_eq!(
79
+ map_tensor_name("patch_proj.skip_proj.bias"),
80
+ Some("patch_proj.skip_proj.bias".into())
81
+ );
82
+ }
83
+
84
+ #[test]
85
+ fn block_attn_qkv() {
86
+ assert_eq!(
87
+ map_tensor_name("transformer.layers.0.attn.in_proj.weight"),
88
+ Some("blk.0.attn_qkv.weight".into())
89
+ );
90
+ }
91
+
92
+ #[test]
93
+ fn block_attn_pds() {
94
+ assert_eq!(
95
+ map_tensor_name("transformer.layers.3.attn._pds.per_dim_scale"),
96
+ Some("blk.3.attn_pds.weight".into())
97
+ );
98
+ }
99
+
100
+ #[test]
101
+ fn block_attn_tau() {
102
+ assert_eq!(
103
+ map_tensor_name("transformer.layers.47.attn_tau"),
104
+ Some("blk.47.attn_tau".into())
105
+ );
106
+ }
107
+
108
+ #[test]
109
+ fn block_ffn() {
110
+ assert_eq!(
111
+ map_tensor_name("transformer.layers.12.ffn.fc1.weight"),
112
+ Some("blk.12.ffn_up.weight".into())
113
+ );
114
+ assert_eq!(
115
+ map_tensor_name("transformer.layers.12.ffn.fc2.weight"),
116
+ Some("blk.12.ffn_down.weight".into())
117
+ );
118
+ }
119
+
120
+ #[test]
121
+ fn output_head() {
122
+ assert_eq!(
123
+ map_tensor_name("output_head.param_projection.proj.linear2.weight"),
124
+ Some("output_head.linear2.weight".into())
125
+ );
126
+ assert_eq!(
127
+ map_tensor_name("output_head.param_projection.proj.skip_proj.bias"),
128
+ Some("output_head.skip_proj.bias".into())
129
+ );
130
+ }
131
+
132
+ #[test]
133
+ fn unknown_returns_none() {
134
+ assert_eq!(map_tensor_name("some.unknown.tensor"), None);
135
+ }
136
+ }
toto-rs/src/upload.rs ADDED
@@ -0,0 +1,533 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::io::Read;
2
+ use std::path::{Path, PathBuf};
3
+
4
+ use anyhow::Context;
5
+ use base64::Engine as _;
6
+ use futures_util::StreamExt;
7
+ use indicatif::{ProgressBar, ProgressStyle};
8
+ use sha2::{Digest, Sha256};
9
+ use tokio_util::io::ReaderStream;
10
+
11
+ const HF_BASE: &str = "https://huggingface.co";
12
+ const PREUPLOAD_SAMPLE: usize = 512;
13
+
14
+ const SKIP_DIRS: &[&str] = &["target", ".git", "__pycache__", ".venv", "models"];
15
+
16
+ struct RegularFile {
17
+ remote: String,
18
+ content_b64: String,
19
+ }
20
+
21
+ struct LfsFile {
22
+ local: PathBuf,
23
+ remote: String,
24
+ size: u64,
25
+ oid: String, // sha256 hex
26
+ }
27
+
28
+ pub async fn run(repo_id: &str, token: &str, root: &Path) -> anyhow::Result<()> {
29
+ let client = build_client(token)?;
30
+
31
+ ensure_repo(&client, repo_id).await?;
32
+
33
+ let files = gather_files(root)?;
34
+ println!("Gathered {} file(s) to upload …", files.len());
35
+
36
+ let (regular, lfs) = classify_files(&client, repo_id, &files).await?;
37
+ println!(" {} regular, {} LFS", regular.len(), lfs.len());
38
+
39
+ if !lfs.is_empty() {
40
+ upload_lfs(&client, repo_id, &lfs).await?;
41
+ }
42
+
43
+ make_commit(&client, repo_id, &regular, &lfs).await?;
44
+
45
+ println!("Uploaded → https://huggingface.co/{repo_id}");
46
+ Ok(())
47
+ }
48
+
49
+ fn build_client(token: &str) -> anyhow::Result<reqwest::Client> {
50
+ let mut headers = reqwest::header::HeaderMap::new();
51
+ headers.insert(
52
+ reqwest::header::AUTHORIZATION,
53
+ format!("Bearer {token}").parse().context("invalid HF token")?,
54
+ );
55
+ headers.insert(
56
+ reqwest::header::USER_AGENT,
57
+ "toto-rs/0.1".parse().unwrap(),
58
+ );
59
+ Ok(reqwest::Client::builder().default_headers(headers).build()?)
60
+ }
61
+
62
+ async fn ensure_repo(client: &reqwest::Client, repo_id: &str) -> anyhow::Result<()> {
63
+ let name = repo_id.split('/').nth(1).context("repo_id must be owner/name")?;
64
+ let resp: reqwest::Response = client
65
+ .post(format!("{HF_BASE}/api/repos/create"))
66
+ .json(&serde_json::json!({ "name": name, "type": "model", "private": false }))
67
+ .send()
68
+ .await
69
+ .context("create repo")?;
70
+
71
+ let status = resp.status();
72
+ if status.is_success() {
73
+ println!("Created repo {repo_id}");
74
+ } else if status.as_u16() == 409 {
75
+ // already exists — fine
76
+ } else {
77
+ let body: String = resp.text().await.unwrap_or_default();
78
+ anyhow::bail!("create repo: HTTP {status}: {body}");
79
+ }
80
+ Ok(())
81
+ }
82
+
83
+ /// Ask HF which files need LFS vs regular inline upload, then prepare both lists.
84
+ async fn classify_files(
85
+ client: &reqwest::Client,
86
+ repo_id: &str,
87
+ files: &[(PathBuf, String)],
88
+ ) -> anyhow::Result<(Vec<RegularFile>, Vec<LfsFile>)> {
89
+ // Build preupload request
90
+ let mut preupload_entries: Vec<serde_json::Value> = Vec::new();
91
+ for (local, remote) in files {
92
+ let size = std::fs::metadata(local)
93
+ .with_context(|| format!("stat {}", local.display()))?
94
+ .len();
95
+ let sample = {
96
+ let bytes = std::fs::read(local)
97
+ .with_context(|| format!("read {}", local.display()))?;
98
+ let n = bytes.len().min(PREUPLOAD_SAMPLE);
99
+ base64::engine::general_purpose::STANDARD.encode(&bytes[..n])
100
+ };
101
+ preupload_entries.push(serde_json::json!({
102
+ "path": remote,
103
+ "size": size,
104
+ "sample": sample,
105
+ }));
106
+ }
107
+
108
+ let url = format!("{HF_BASE}/api/models/{repo_id}/preupload/main");
109
+ let resp: reqwest::Response = client
110
+ .post(&url)
111
+ .json(&serde_json::json!({ "files": preupload_entries }))
112
+ .send()
113
+ .await
114
+ .context("preupload request")?;
115
+
116
+ let status = resp.status();
117
+ if !status.is_success() {
118
+ let body: String = resp.text().await.unwrap_or_default();
119
+ anyhow::bail!("preupload: HTTP {status}: {body}");
120
+ }
121
+
122
+ #[derive(serde::Deserialize)]
123
+ struct PreuploadFile {
124
+ path: String,
125
+ #[serde(rename = "uploadMode")]
126
+ upload_mode: String,
127
+ #[serde(rename = "shouldIgnore", default)]
128
+ should_ignore: bool,
129
+ }
130
+ #[derive(serde::Deserialize)]
131
+ struct PreuploadResp { files: Vec<PreuploadFile> }
132
+
133
+ let preupload: PreuploadResp = resp.json().await.context("parse preupload response")?;
134
+ let modes: std::collections::HashMap<String, (String, bool)> = preupload.files
135
+ .into_iter()
136
+ .map(|f| (f.path, (f.upload_mode, f.should_ignore)))
137
+ .collect();
138
+
139
+ let mut regular: Vec<RegularFile> = Vec::new();
140
+ let mut lfs: Vec<LfsFile> = Vec::new();
141
+
142
+ for (local, remote) in files {
143
+ let (mode, _should_ignore) = modes
144
+ .get(remote)
145
+ .map(|(m, s)| (m.as_str(), *s))
146
+ .unwrap_or(("regular", false));
147
+ if mode == "lfs" {
148
+ let size = std::fs::metadata(local)?.len();
149
+ let oid = sha256_file(local)?;
150
+ lfs.push(LfsFile { local: local.clone(), remote: remote.clone(), size, oid });
151
+ } else {
152
+ let bytes = std::fs::read(local)
153
+ .with_context(|| format!("read {}", local.display()))?;
154
+ let content_b64 = base64::engine::general_purpose::STANDARD.encode(&bytes);
155
+ regular.push(RegularFile { remote: remote.clone(), content_b64 });
156
+ }
157
+ }
158
+
159
+ Ok((regular, lfs))
160
+ }
161
+
162
+ fn gather_files(root: &Path) -> anyhow::Result<Vec<(PathBuf, String)>> {
163
+ let mut out = Vec::new();
164
+ walk_dir(root, "", &mut out)?;
165
+ Ok(out)
166
+ }
167
+
168
+ fn walk_dir(dir: &Path, prefix: &str, out: &mut Vec<(PathBuf, String)>) -> anyhow::Result<()> {
169
+ let mut entries: Vec<_> = std::fs::read_dir(dir)
170
+ .with_context(|| format!("read_dir {}", dir.display()))?
171
+ .collect::<Result<_, _>>()?;
172
+ entries.sort_by_key(|e| e.file_name());
173
+ for entry in entries {
174
+ let path = entry.path();
175
+ let name = entry
176
+ .file_name()
177
+ .into_string()
178
+ .map_err(|_| anyhow::anyhow!("non-UTF-8 filename"))?;
179
+ if SKIP_DIRS.contains(&name.as_str()) {
180
+ continue;
181
+ }
182
+ let remote = if prefix.is_empty() { name.clone() } else { format!("{prefix}/{name}") };
183
+ if path.is_file() {
184
+ out.push((path, remote));
185
+ } else if path.is_dir() {
186
+ walk_dir(&path, &remote, out)?;
187
+ }
188
+ }
189
+ Ok(())
190
+ }
191
+
192
+ fn sha256_file(path: &Path) -> anyhow::Result<String> {
193
+ let mut f =
194
+ std::fs::File::open(path).with_context(|| format!("open {}", path.display()))?;
195
+ let mut hasher = Sha256::new();
196
+ let mut buf = vec![0u8; 8 * 1024 * 1024];
197
+ loop {
198
+ let n = f.read(&mut buf)?;
199
+ if n == 0 {
200
+ break;
201
+ }
202
+ hasher.update(&buf[..n]);
203
+ }
204
+ Ok(format!("{:x}", hasher.finalize()))
205
+ }
206
+
207
+ async fn upload_lfs(
208
+ client: &reqwest::Client,
209
+ repo_id: &str,
210
+ files: &[LfsFile],
211
+ ) -> anyhow::Result<()> {
212
+ let objects: Vec<_> = files
213
+ .iter()
214
+ .map(|f| serde_json::json!({ "oid": f.oid, "size": f.size }))
215
+ .collect();
216
+
217
+ // Request both multipart (for files >5 GB) and basic transfers
218
+ let url = format!("{HF_BASE}/{repo_id}.git/info/lfs/objects/batch");
219
+ let lfs_body = serde_json::to_string(&serde_json::json!({
220
+ "operation": "upload",
221
+ "transfers": ["multipart", "basic"],
222
+ "objects": objects,
223
+ }))?;
224
+ let resp: reqwest::Response = client
225
+ .post(&url)
226
+ .header("Content-Type", "application/vnd.git-lfs+json")
227
+ .header("Accept", "application/vnd.git-lfs+json")
228
+ .body(lfs_body)
229
+ .send()
230
+ .await
231
+ .context("LFS batch request")?;
232
+
233
+ let status = resp.status();
234
+ if !status.is_success() {
235
+ let body: String = resp.text().await.unwrap_or_default();
236
+ anyhow::bail!("LFS batch: HTTP {status}: {body}");
237
+ }
238
+
239
+ #[derive(serde::Deserialize)]
240
+ struct BatchResp {
241
+ objects: Vec<serde_json::Value>,
242
+ }
243
+ let batch: BatchResp = resp.json().await.context("parse LFS batch response")?;
244
+
245
+ for (file, obj) in files.iter().zip(batch.objects.iter()) {
246
+ let Some(upload_href) =
247
+ obj.pointer("/actions/upload/href").and_then(|v: &serde_json::Value| v.as_str())
248
+ else {
249
+ println!(" (already on LFS) {}", file.remote);
250
+ continue;
251
+ };
252
+
253
+ // Multipart if the server provided chunk_size in the header
254
+ let is_multipart = obj.pointer("/actions/upload/header/chunk_size").is_some();
255
+
256
+ if is_multipart {
257
+ upload_lfs_object_multipart(file, upload_href, obj).await
258
+ .with_context(|| format!("upload (multipart) {}", file.remote))?;
259
+ } else {
260
+ upload_lfs_object(file, upload_href, obj).await
261
+ .with_context(|| format!("upload {}", file.remote))?;
262
+ }
263
+
264
+ // Verify step (optional but recommended by Git LFS spec)
265
+ if let Some(verify_href) =
266
+ obj.pointer("/actions/verify/href").and_then(|v: &serde_json::Value| v.as_str())
267
+ {
268
+ let verify_headers = obj
269
+ .pointer("/actions/verify/header")
270
+ .and_then(|v: &serde_json::Value| v.as_object())
271
+ .cloned()
272
+ .unwrap_or_default();
273
+
274
+ let mut vreq: reqwest::RequestBuilder = reqwest::Client::new()
275
+ .post(verify_href)
276
+ .header("Content-Type", "application/vnd.git-lfs+json")
277
+ .json(&serde_json::json!({ "oid": file.oid, "size": file.size }));
278
+ for (k, v) in &verify_headers {
279
+ if let Some(val) = v.as_str() {
280
+ vreq = vreq.header(k.as_str(), val);
281
+ }
282
+ }
283
+ let vresp: reqwest::Response = vreq.send().await.context("LFS verify")?;
284
+ if !vresp.status().is_success() {
285
+ eprintln!(" Warning: LFS verify returned {}", vresp.status());
286
+ }
287
+ }
288
+ }
289
+
290
+ Ok(())
291
+ }
292
+
293
+ async fn upload_lfs_object(
294
+ file: &LfsFile,
295
+ href: &str,
296
+ obj: &serde_json::Value,
297
+ ) -> anyhow::Result<()> {
298
+ let pb = ProgressBar::new(file.size);
299
+ pb.set_style(
300
+ ProgressStyle::with_template(
301
+ " {msg} [{bar:40}] {bytes}/{total_bytes} ({bytes_per_sec}, eta {eta})",
302
+ )
303
+ .unwrap()
304
+ .progress_chars("=>-"),
305
+ );
306
+ pb.set_message(file.remote.clone());
307
+
308
+ let f = tokio::fs::File::open(&file.local)
309
+ .await
310
+ .with_context(|| format!("open {}", file.local.display()))?;
311
+
312
+ let pb2 = pb.clone();
313
+ let stream = ReaderStream::new(f).map(move |chunk| {
314
+ if let Ok(ref b) = chunk {
315
+ pb2.inc(b.len() as u64);
316
+ }
317
+ chunk
318
+ });
319
+
320
+ // LFS upload goes to S3 / Azure — use a plain client (no HF auth header)
321
+ let mut req = reqwest::Client::new()
322
+ .put(href)
323
+ .header("Content-Length", file.size.to_string());
324
+
325
+ if let Some(extra) = obj.pointer("/actions/upload/header").and_then(|v| v.as_object()) {
326
+ for (k, v) in extra {
327
+ if let Some(val) = v.as_str() {
328
+ req = req.header(k.as_str(), val);
329
+ }
330
+ }
331
+ }
332
+
333
+ let resp = req
334
+ .body(reqwest::Body::wrap_stream(stream))
335
+ .send()
336
+ .await
337
+ .context("PUT LFS object")?;
338
+
339
+ pb.finish_and_clear();
340
+
341
+ let status = resp.status();
342
+ if !status.is_success() {
343
+ let body = resp.text().await.unwrap_or_default();
344
+ anyhow::bail!("LFS PUT HTTP {status}: {body}");
345
+ }
346
+
347
+ Ok(())
348
+ }
349
+
350
+ async fn upload_lfs_object_multipart(
351
+ file: &LfsFile,
352
+ complete_href: &str,
353
+ obj: &serde_json::Value,
354
+ ) -> anyhow::Result<()> {
355
+ use tokio::io::AsyncReadExt;
356
+
357
+ let header = obj
358
+ .pointer("/actions/upload/header")
359
+ .and_then(|v| v.as_object())
360
+ .ok_or_else(|| anyhow::anyhow!("no header in multipart LFS response"))?;
361
+
362
+ let chunk_size: usize = header
363
+ .get("chunk_size")
364
+ .and_then(|v| v.as_str())
365
+ .and_then(|s| s.parse().ok())
366
+ .ok_or_else(|| anyhow::anyhow!("missing chunk_size in multipart header"))?;
367
+
368
+ // Collect part URLs sorted numerically by key ("00001", "00002", …)
369
+ let mut parts: Vec<(u32, String)> = header
370
+ .iter()
371
+ .filter_map(|(k, v)| {
372
+ let n: u32 = k.parse().ok()?;
373
+ Some((n, v.as_str()?.to_string()))
374
+ })
375
+ .collect();
376
+ parts.sort_by_key(|(n, _)| *n);
377
+
378
+ let pb = ProgressBar::new(file.size);
379
+ pb.set_style(
380
+ ProgressStyle::with_template(
381
+ " {msg} [{bar:40}] {bytes}/{total_bytes} ({bytes_per_sec}, eta {eta})",
382
+ )
383
+ .unwrap()
384
+ .progress_chars("=>-"),
385
+ );
386
+ pb.set_message(file.remote.clone());
387
+
388
+ let mut f = tokio::fs::File::open(&file.local)
389
+ .await
390
+ .with_context(|| format!("open {}", file.local.display()))?;
391
+
392
+ let s3 = reqwest::Client::new(); // plain client — S3 parts use pre-signed URLs
393
+ let mut etags: Vec<(u32, String)> = Vec::with_capacity(parts.len());
394
+
395
+ for (part_num, url) in &parts {
396
+ // Read up to chunk_size bytes for this part
397
+ let mut buf = vec![0u8; chunk_size];
398
+ let mut pos = 0;
399
+ while pos < chunk_size {
400
+ let n = f.read(&mut buf[pos..]).await?;
401
+ if n == 0 { break; }
402
+ pos += n;
403
+ }
404
+ if pos == 0 { break; }
405
+ buf.truncate(pos);
406
+ let len = buf.len();
407
+
408
+ // Retry up to 3 times on transient connection errors
409
+ const MAX_RETRIES: usize = 3;
410
+ let mut last_err: Option<anyhow::Error> = None;
411
+ let mut etag_opt: Option<String> = None;
412
+ for attempt in 0..MAX_RETRIES {
413
+ if attempt > 0 {
414
+ tokio::time::sleep(std::time::Duration::from_secs(2)).await;
415
+ eprintln!(" Retrying part {part_num} (attempt {})…", attempt + 1);
416
+ }
417
+ match s3
418
+ .put(url.as_str())
419
+ .header("Content-Length", len.to_string())
420
+ .body(buf.clone())
421
+ .send()
422
+ .await
423
+ {
424
+ Err(e) => {
425
+ last_err = Some(anyhow::anyhow!("PUT part {part_num}: {e}"));
426
+ }
427
+ Ok(resp) => {
428
+ let status = resp.status();
429
+ let tag = resp.headers().get("etag")
430
+ .and_then(|v| v.to_str().ok())
431
+ .map(|s| s.to_string());
432
+ if status.is_success() {
433
+ if let Some(t) = tag {
434
+ etag_opt = Some(t);
435
+ last_err = None;
436
+ break;
437
+ } else {
438
+ last_err = Some(anyhow::anyhow!("PUT part {part_num}: no ETag in response"));
439
+ }
440
+ } else {
441
+ let body = resp.text().await.unwrap_or_default();
442
+ last_err = Some(anyhow::anyhow!("PUT part {part_num}: HTTP {status}: {body}"));
443
+ }
444
+ }
445
+ }
446
+ }
447
+ let etag = etag_opt.ok_or_else(|| {
448
+ last_err.unwrap_or_else(|| anyhow::anyhow!("PUT part {part_num}: exhausted retries"))
449
+ })?;
450
+
451
+ pb.inc(len as u64);
452
+ etags.push((*part_num, etag));
453
+ }
454
+
455
+ pb.finish_and_clear();
456
+
457
+ // Tell HF to assemble the parts on S3
458
+ let parts_json: Vec<serde_json::Value> = etags
459
+ .iter()
460
+ .map(|(n, e)| serde_json::json!({ "partNumber": n, "etag": e }))
461
+ .collect();
462
+
463
+ let resp = s3
464
+ .post(complete_href)
465
+ .json(&serde_json::json!({ "oid": file.oid, "parts": parts_json }))
466
+ .send()
467
+ .await
468
+ .context("complete multipart")?;
469
+
470
+ let status = resp.status();
471
+ if !status.is_success() {
472
+ let body = resp.text().await.unwrap_or_default();
473
+ anyhow::bail!("complete multipart: HTTP {status}: {body}");
474
+ }
475
+
476
+ Ok(())
477
+ }
478
+
479
+ async fn make_commit(
480
+ client: &reqwest::Client,
481
+ repo_id: &str,
482
+ regular: &[RegularFile],
483
+ lfs: &[LfsFile],
484
+ ) -> anyhow::Result<()> {
485
+ let mut lines: Vec<String> = Vec::new();
486
+
487
+ lines.push(serde_json::to_string(&serde_json::json!({
488
+ "key": "header",
489
+ "value": { "summary": "Upload model files", "description": "" },
490
+ }))?);
491
+
492
+ for rf in regular {
493
+ lines.push(serde_json::to_string(&serde_json::json!({
494
+ "key": "file",
495
+ "value": {
496
+ "path": rf.remote,
497
+ "encoding": "base64",
498
+ "content": rf.content_b64,
499
+ },
500
+ }))?);
501
+ }
502
+
503
+ for lf in lfs {
504
+ lines.push(serde_json::to_string(&serde_json::json!({
505
+ "key": "lfsFile",
506
+ "value": {
507
+ "path": lf.remote,
508
+ "algo": "sha256",
509
+ "oid": lf.oid,
510
+ "size": lf.size,
511
+ },
512
+ }))?);
513
+ }
514
+
515
+ let body = lines.join("\n");
516
+
517
+ let url = format!("{HF_BASE}/api/models/{repo_id}/commit/main");
518
+ let resp = client
519
+ .post(&url)
520
+ .header("Content-Type", "application/x-ndjson")
521
+ .body(body)
522
+ .send()
523
+ .await
524
+ .context("POST commit")?;
525
+
526
+ let status = resp.status();
527
+ if !status.is_success() {
528
+ let body = resp.text().await.unwrap_or_default();
529
+ anyhow::bail!("commit: HTTP {status}: {body}");
530
+ }
531
+
532
+ Ok(())
533
+ }
uv.lock ADDED
The diff for this file is too large to render. See raw diff