Compare commits
533
Commits
glm-5.3-flash
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3f50180ec | ||
|
|
5e3a3449e2 | ||
|
|
cd1c5507b6 | ||
|
|
2cd0ae7064 | ||
|
|
5c9ed63c16 | ||
|
|
7bba0f7d2c | ||
|
|
a2798605b7 | ||
|
|
0dbfd05e16 | ||
|
|
afd33efab2 | ||
|
|
c942a90fdd | ||
|
|
4ef9e4d0d0 | ||
|
|
9eb4a12f25 | ||
|
|
e4709b44be | ||
|
|
890898cfb4 | ||
|
|
9fe88cd312 | ||
|
|
a4bbb38ce4 | ||
|
|
c54841002d | ||
|
|
8c54d7fb01 | ||
|
|
0accf8b395 | ||
|
|
6075227f1e | ||
|
|
224e77e791 | ||
|
|
a88711246c | ||
|
|
b4e3faf526 | ||
|
|
da2a102dd0 | ||
|
|
b7615c141f | ||
|
|
36e2680676 | ||
|
|
ae40c5c4f0 | ||
|
|
c1bce729fc | ||
|
|
ef8e536501 | ||
|
|
524c874506 | ||
|
|
46ade1bb3e | ||
|
|
1c6d058d9f | ||
|
|
d9fc9cf30c | ||
|
|
b799c90e3a | ||
|
|
773247a881 | ||
|
|
8c9b1986a4 | ||
|
|
c2779bb4e8 | ||
|
|
32604bc6de | ||
|
|
dc9580daf4 | ||
|
|
babe24db9f | ||
|
|
db67455bed | ||
|
|
efd018cc43 | ||
|
|
b00bbf42da | ||
|
|
79e59d7ac2 | ||
|
|
95976d737f | ||
|
|
d61aa0c139 | ||
|
|
e3137ce6bc | ||
|
|
23c333bd1c | ||
|
|
c7bb2d7656 | ||
|
|
4c29e82b3e | ||
|
|
47400ed008 | ||
|
|
32f4243a09 | ||
|
|
f2e60304a3 | ||
|
|
e14d7f8b1e | ||
|
|
c97964b8aa | ||
|
|
21178462db | ||
|
|
345d7f44cb | ||
|
|
e73e516e75 | ||
|
|
64baaa0678 | ||
|
|
347c2e1fbe | ||
|
|
82aa7e8e60 | ||
|
|
5382eac5e0 | ||
|
|
a8ce74afd5 | ||
|
|
97e32768e6 | ||
|
|
381808d839 | ||
|
|
a4c922c560 | ||
|
|
2768d0ed3c | ||
|
|
3459d5546b | ||
|
|
fc69b626ae | ||
|
|
1e6a0e9602 | ||
|
|
694620956b | ||
|
|
1be7b4db2a | ||
|
|
d9f2d15b40 | ||
|
|
5fd33a4e53 | ||
|
|
9d7a00b4fb | ||
|
|
01772c087a | ||
|
|
57df7a5bf4 | ||
|
|
be2773bc6f | ||
|
|
72ccf36317 | ||
|
|
51fef8784a | ||
|
|
3650da359e | ||
|
|
ce5b718be8 | ||
|
|
5b83cdfe8c | ||
|
|
e1d8114167 | ||
|
|
4ff3af604f | ||
|
|
64ceeea4f5 | ||
|
|
e99dc21751 | ||
|
|
f0b4f55fcc | ||
|
|
55b1e75e64 | ||
|
|
b66c926046 | ||
|
|
95606b5045 | ||
|
|
26335c7044 | ||
|
|
f5127dfc82 | ||
|
|
5a588dbf12 | ||
|
|
565db43559 | ||
|
|
187f46b4fa | ||
|
|
bdae7640ef | ||
|
|
e91c453ec2 | ||
|
|
ee7e840523 | ||
|
|
0f9b03c9df | ||
|
|
7d9903b2ed | ||
|
|
b5aedb9b96 | ||
|
|
f768d146e1 | ||
|
|
6605c3cefb | ||
|
|
0f12ea4577 | ||
|
|
9000fb74f9 | ||
|
|
6b7cb44613 | ||
|
|
a3a7e2de6b | ||
|
|
4ed25e4f07 | ||
|
|
b440c3c20e | ||
|
|
d2ae13eac0 | ||
|
|
fd16a72f52 | ||
|
|
47c34fb444 | ||
|
|
e2d09d1b05 | ||
|
|
f41806dcd2 | ||
|
|
732af32f9c | ||
|
|
75408d41a6 | ||
|
|
a101246338 | ||
|
|
5379c1c9ac | ||
|
|
902baebb70 | ||
|
|
31786cb594 | ||
|
|
5958ac718c | ||
|
|
8c2b536717 | ||
|
|
f547bf91ca | ||
|
|
c25ff9a4d4 | ||
|
|
c91c968b52 | ||
|
|
a3a231ce40 | ||
|
|
62c6684eb2 | ||
|
|
2d5ef0d3c1 | ||
|
|
b71e7bcb8c | ||
|
|
78d5d4d7fa | ||
|
|
0a02a3cd56 | ||
|
|
15a3d710d3 | ||
|
|
cbb687462b | ||
|
|
b90fce71f4 | ||
|
|
aa5692e985 | ||
|
|
8f194c8158 | ||
|
|
559616bb19 | ||
|
|
724336c8e9 | ||
|
|
52780d4165 | ||
|
|
e255ed02d8 | ||
|
|
7e7e595455 | ||
|
|
9f365a1748 | ||
|
|
d329b3c523 | ||
|
|
f38445cf46 | ||
|
|
0df64b67a9 | ||
|
|
97079d7dcf | ||
|
|
8d543ecc95 | ||
|
|
792e7e6aad | ||
|
|
b290d83bc5 | ||
|
|
784617c8f7 | ||
|
|
4c64ae44c1 | ||
|
|
f3ee00a1d2 | ||
|
|
5c3b0a49ea | ||
|
|
2dcead8ce2 | ||
|
|
87a95e0c11 | ||
|
|
da5a1272cb | ||
|
|
a615eb6f07 | ||
|
|
cef4a4d906 | ||
|
|
3920942d7a | ||
|
|
68ecb1b0c4 | ||
|
|
8a522702bd | ||
|
|
fc80bd695d | ||
|
|
d6b516abb4 | ||
|
|
b14bf6220f | ||
|
|
cad79332b3 | ||
|
|
ff95f53e87 | ||
|
|
f0962e3757 | ||
|
|
b3afc8e991 | ||
|
|
63059b654c | ||
|
|
43a91aaa3b | ||
|
|
77570b465c | ||
|
|
962ac1913d | ||
|
|
4f806387f7 | ||
|
|
37f66ba3c9 | ||
|
|
575369b19d | ||
|
|
67f2ca912c | ||
|
|
0231312820 | ||
|
|
f32a16a561 | ||
|
|
7172b249a8 | ||
|
|
b96a12be7b | ||
|
|
ee79f22b90 | ||
|
|
4642526317 | ||
|
|
81ee4badb8 | ||
|
|
e5b19e280e | ||
|
|
089d966069 | ||
|
|
c71ca56d81 | ||
|
|
b1af94b438 | ||
|
|
028b43f3a1 | ||
|
|
47ca2eb5d9 | ||
|
|
3d3c83bbbd | ||
|
|
798c64f444 | ||
|
|
6bf43ee8da | ||
|
|
92197579f8 | ||
|
|
2a55193649 | ||
|
|
64240fb9c2 | ||
|
|
0aaea5a238 | ||
|
|
6687c6f90b | ||
|
|
a6ad6361ca | ||
|
|
fd0c24af54 | ||
|
|
06f7135d0f | ||
|
|
a15028ee40 | ||
|
|
8be1005089 | ||
|
|
0719a0baf8 | ||
|
|
d9bc45dbda | ||
|
|
0ac42d6fe0 | ||
|
|
8387625991 | ||
|
|
5432223d60 | ||
|
|
7d89c59c4d | ||
|
|
c6f4086f6d | ||
|
|
ed43e1a56b | ||
|
|
c4b66b9032 | ||
|
|
66dff86949 | ||
|
|
258ad331dc | ||
|
|
4fe6a78029 | ||
|
|
11811b96b2 | ||
|
|
9bff1ca4e1 | ||
|
|
b3d4a267c0 | ||
|
|
c9bfdcc951 | ||
|
|
aafc65b444 | ||
|
|
077a25760e | ||
|
|
6c00e2d535 | ||
|
|
5994f062de | ||
|
|
4067320f06 | ||
|
|
25334fef16 | ||
|
|
89986c3c89 | ||
|
|
af7c02b594 | ||
|
|
4b1b1519ed | ||
|
|
04c08672b5 | ||
|
|
2d6a207bbb | ||
|
|
0024ca0803 | ||
|
|
ecf7a93148 | ||
|
|
63bbba4ba7 | ||
|
|
064bc229c6 | ||
|
|
62867e913a | ||
|
|
ac43e900d8 | ||
|
|
39bc628cc3 | ||
|
|
d31089d5ad | ||
|
|
fff391e6c7 | ||
|
|
9775e26ccd | ||
|
|
582d809fd3 | ||
|
|
fd7091916a | ||
|
|
1c6920666c | ||
|
|
0b2d6e28cb | ||
|
|
0edbd9cd64 | ||
|
|
aea1a0bae9 | ||
|
|
5c2dac7e37 | ||
|
|
29ce271717 | ||
|
|
b7bcccdea2 | ||
|
|
7da9535f2d | ||
|
|
bd85ba6aca | ||
|
|
1e3bcf8d87 | ||
|
|
c674b198d4 | ||
|
|
a6b50ff6c4 | ||
|
|
262b4a66e3 | ||
|
|
e49643fff9 | ||
|
|
867465cc66 | ||
|
|
a13b513e63 | ||
|
|
8d73a0effa | ||
|
|
a3f6f313a6 | ||
|
|
1923131d4c | ||
|
|
dc81436da2 | ||
|
|
9f3892d2fe | ||
|
|
4fbbc4a45b | ||
|
|
d8d1523b33 | ||
|
|
a60edc61f5 | ||
|
|
c016ea7730 | ||
|
|
3b7f8f2242 | ||
|
|
d744ebb9e7 | ||
|
|
ce5a812fd3 | ||
|
|
ccbf2c083e | ||
|
|
e768396179 | ||
|
|
10118742ae | ||
|
|
edceb7ab7f | ||
|
|
d95f8b6101 | ||
|
|
2a281b0802 | ||
|
|
f47db4fb83 | ||
|
|
8db1d1d155 | ||
|
|
ab38bc6b7a | ||
|
|
d1620ba061 | ||
|
|
8e608a66e0 | ||
|
|
9ba464d529 | ||
|
|
307778657f | ||
|
|
548cdaf897 | ||
|
|
e2cba0b7be | ||
|
|
d569d05aec | ||
|
|
a504136766 | ||
|
|
467b6f05a0 | ||
|
|
9e91baccda | ||
|
|
b4da720207 | ||
|
|
65d2eb3e15 | ||
|
|
643d4cb5bc | ||
|
|
891c0053c7 | ||
|
|
a154af774b | ||
|
|
3c0cfc3108 | ||
|
|
54a39d7fa2 | ||
|
|
d36dd5d592 | ||
|
|
8378039eb4 | ||
|
|
fe457518a6 | ||
|
|
2c5b1aab14 | ||
|
|
4e01ef9dec | ||
|
|
3e068bb0e7 | ||
|
|
6efb642d53 | ||
|
|
ba3b0c23e3 | ||
|
|
147b2632e0 | ||
|
|
d0b74340bf | ||
|
|
9a8462a19f | ||
|
|
13c53d922b | ||
|
|
a37fd7f53d | ||
|
|
bbbc01207e | ||
|
|
a50fecce6f | ||
|
|
2ba92ab234 | ||
|
|
29607116c3 | ||
|
|
f396751952 | ||
|
|
bb6a7485f3 | ||
|
|
442982dd87 | ||
|
|
f74d410962 | ||
|
|
02a2558225 | ||
|
|
7fe838b8cb | ||
|
|
63b5dca93e | ||
|
|
df6eedd972 | ||
|
|
5cbdb6ef81 | ||
|
|
81b9ebbf7a | ||
|
|
e2b4a47e07 | ||
|
|
9e1429b8b6 | ||
|
|
b86c8ae1db | ||
|
|
92f57fc4d7 | ||
|
|
496b153eed | ||
|
|
cdfc0d51e1 | ||
|
|
d6cbc77ca6 | ||
|
|
85d37aec2a | ||
|
|
e37f18576c | ||
|
|
9139e2ae58 | ||
|
|
67bd55c0ec | ||
|
|
846428239d | ||
|
|
97ba327538 | ||
|
|
e68bcc8fab | ||
|
|
15e88cfe91 | ||
|
|
948307e189 | ||
|
|
cbaf6cd336 | ||
|
|
923b819abf | ||
|
|
efc27d6d86 | ||
|
|
bab202990e | ||
|
|
f06d3ebd52 | ||
|
|
0a21d1abb8 | ||
|
|
7a18d09149 | ||
|
|
6e4c285a92 | ||
|
|
e9e1baaff1 | ||
|
|
c2c3ce324c | ||
|
|
a04f46fa42 | ||
|
|
ccea768827 | ||
|
|
9b50495847 | ||
|
|
bd66c40207 | ||
|
|
6289c51627 | ||
|
|
74fbe546ff | ||
|
|
e33a5b2538 | ||
|
|
81a76585ff | ||
|
|
6546ae5473 | ||
|
|
f62ca29a30 | ||
|
|
b6af0adf8c | ||
|
|
c0a6119f36 | ||
|
|
49ec955c8c | ||
|
|
d12898f4e2 | ||
|
|
78c522e96b | ||
|
|
5ad935d7bb | ||
|
|
bff84aec32 | ||
|
|
909d6560cb | ||
|
|
b36e26ed13 | ||
|
|
db1942148c | ||
|
|
85a4f0e611 | ||
|
|
394865c5d1 | ||
|
|
e83b106309 | ||
|
|
1ef9bba608 | ||
|
|
90aa4b560a | ||
|
|
d7ade9fd65 | ||
|
|
fb2abb29e4 | ||
|
|
4814f44fed | ||
|
|
31d1992633 | ||
|
|
437dbcba8e | ||
|
|
660e1d4b64 | ||
|
|
715ed7a865 | ||
|
|
91dda0bc88 | ||
|
|
85837d02e1 | ||
|
|
edcafe4d57 | ||
|
|
7a8c1b27c8 | ||
|
|
64e490bc1a | ||
|
|
e965352906 | ||
|
|
54fc4f691c | ||
|
|
6ccc1e7e76 | ||
|
|
22e417286c | ||
|
|
d6580a5ffc | ||
|
|
aa8fef4a16 | ||
|
|
46e46e7d7c | ||
|
|
42da390e2f | ||
|
|
6eac69e0aa | ||
|
|
930ab73279 | ||
|
|
e1711c21d9 | ||
|
|
d108ae4e8d | ||
|
|
fc6414cdfc | ||
|
|
759dd7cd69 | ||
|
|
5b3cc8bfd1 | ||
|
|
d077fa67e5 | ||
|
|
aa1e71cf03 | ||
|
|
2803820dc5 | ||
|
|
45f54e9d11 | ||
|
|
558cd34311 | ||
|
|
f6c169f825 | ||
|
|
df80a87da1 | ||
|
|
9ab705347c | ||
|
|
4a14eb83ba | ||
|
|
b42682f12f | ||
|
|
189ff0b302 | ||
|
|
eb847ac568 | ||
|
|
f4d03f6cf9 | ||
|
|
96f547a245 | ||
|
|
0e9cc2d43e | ||
|
|
9d9e1296d2 | ||
|
|
ade4d623f9 | ||
|
|
6c25bba129 | ||
|
|
2f0bc1e3bf | ||
|
|
233eeb85a7 | ||
|
|
932896fd0a | ||
|
|
bbec2006f8 | ||
|
|
5806d0994d | ||
|
|
be76012927 | ||
|
|
8d309ab062 | ||
|
|
8fcd61d6bf | ||
|
|
2ea3839ef3 | ||
|
|
29aa7267af | ||
|
|
469ed2f4b5 | ||
|
|
b0a147a7fb | ||
|
|
5d41a80fd2 | ||
|
|
c185c7aabf | ||
|
|
4886c56815 | ||
|
|
e0b8c8fdf9 | ||
|
|
b941e98aad | ||
|
|
cffcd64ae9 | ||
|
|
7fc640c1bb | ||
|
|
b65ad7dfbc | ||
|
|
daa0324569 | ||
|
|
a06a1bed27 | ||
|
|
8cbdd7624f | ||
|
|
22402728c3 | ||
|
|
e2c39074f4 | ||
|
|
1cb18a2c2b | ||
|
|
399e48a73b | ||
|
|
5024711a7d | ||
|
|
aef6daebbd | ||
|
|
313bebc639 | ||
|
|
23bbbf36e3 | ||
|
|
75787d9bb9 | ||
|
|
c69e57fe91 | ||
|
|
6cfc278706 | ||
|
|
1a976b7dbf | ||
|
|
110afdd888 | ||
|
|
b0982a1b4e | ||
|
|
98b91351ad | ||
|
|
3da2da96b2 | ||
|
|
f9c459ce2f | ||
|
|
200b9f45bf | ||
|
|
409080a623 | ||
|
|
e976a54331 | ||
|
|
fc8bf3c39c | ||
|
|
ad4ac91a39 | ||
|
|
ab06d196da | ||
|
|
ec7642cdd9 | ||
|
|
4b321cf1ec | ||
|
|
f5ee85b341 | ||
|
|
e4593c374f | ||
|
|
5da32061ba | ||
|
|
dcc70b41ef | ||
|
|
81c8c39a5c | ||
|
|
c8b6e4f4d8 | ||
|
|
01f3316c9c | ||
|
|
a547fdff4a | ||
|
|
9254c3daf4 | ||
|
|
e1147cdcbd | ||
|
|
742adcaf14 | ||
|
|
c012d5cedf | ||
|
|
17c2855987 | ||
|
|
985cacd07b | ||
|
|
d208b2b762 | ||
|
|
f9e4c0fe2c | ||
|
|
9dfb291b0b | ||
|
|
e532136631 | ||
|
|
24665f8192 | ||
|
|
e73b367db9 | ||
|
|
d94a54e612 | ||
|
|
0efd822b9b | ||
|
|
e8686b9d04 | ||
|
|
9ca8642d01 | ||
|
|
227fa190aa | ||
|
|
307dc093d9 | ||
|
|
a4e015e004 | ||
|
|
ab3346a695 | ||
|
|
1ef0680b79 | ||
|
|
49a30025d6 | ||
|
|
b7f2f2d37f | ||
|
|
86128aae00 | ||
|
|
4e23cf1bcc | ||
|
|
becf9f96d8 | ||
|
|
8c1629c95c | ||
|
|
491e7e3d3f | ||
|
|
9fd5e399e9 | ||
|
|
c40e8a8e09 | ||
|
|
11e1eb31b0 | ||
|
|
4bf7c47b71 | ||
|
|
540ccbafa7 | ||
|
|
9ab922f50e | ||
|
|
45dbb31505 | ||
|
|
6bdc68bccf | ||
|
|
b0d80b13c8 | ||
|
|
e4cd58705b | ||
|
|
d8d5fa679a | ||
|
|
4d89fe9e84 | ||
|
|
df1269414d | ||
|
|
64b22d74f1 | ||
|
|
300c5814b7 | ||
|
|
68e0ca1e44 | ||
|
|
e43738a0ab | ||
|
|
d104a31b14 | ||
|
|
c2b67652e9 | ||
|
|
f6022ee42d | ||
|
|
0610a6577e | ||
|
|
dd356c3fad | ||
|
|
7470dd60a7 | ||
|
|
c53b2ef6fa | ||
|
|
b93fb08937 | ||
|
|
9933e01f50 | ||
|
|
61e9b991c3 | ||
|
|
e6e7e6a5d7 | ||
|
|
ada805ad59 | ||
|
|
4d7028e066 |
No files matched your search
+93
-1
@@ -9,7 +9,7 @@
|
||||
/ds4_test
|
||||
/ds4flash.gguf
|
||||
/TODO.md
|
||||
/gguf/
|
||||
/gguf
|
||||
/core
|
||||
/core.*
|
||||
/tests/test_engine_mgpu_placement
|
||||
@@ -25,12 +25,93 @@
|
||||
/tests/cuda_long_context_smoke
|
||||
/tests/test_layer_pack
|
||||
/tests/test_metal_session_batch
|
||||
/tests/test_metal_tp_spec
|
||||
/tests/test_metal_tp_cancel
|
||||
/tests/test_metal_moe_prefill
|
||||
/tests/test_metal_ssd_experts
|
||||
/tests/test_metal_ssd_reuse
|
||||
/tests/test_metal_command_memory
|
||||
/tests/test_metal_dense_mpp
|
||||
/tests/test_mxfp4_cuda
|
||||
/tests/test_mmvq_cuda
|
||||
/tests/test_mmvq_rocm
|
||||
/tests/test_mxfp4_dot
|
||||
/tests/test_mxfp4_metal
|
||||
/tests/test_q4k_dot
|
||||
/tests/test_sampling
|
||||
/tests/test_web_recovery
|
||||
/tests/test_quality_api
|
||||
/tests/test_cuda_q8_scratch
|
||||
/tests/test_cuda_dspark_moe
|
||||
/tests/test_session_state
|
||||
/tests/test_session_state_gpu
|
||||
/tests/test_tp_commands
|
||||
/tests/test_tp_rdma
|
||||
/tests/test_tp_tcp
|
||||
/tests/test_tp_link
|
||||
/tests/test_cuda_tp
|
||||
/tests/test_cuda_tp_link
|
||||
/tests/test_cuda_verify_head
|
||||
/tests/test_cuda_shared_side
|
||||
/tests/test_cuda_tokentile
|
||||
/tests/test_cuda_attn_side
|
||||
/tests/test_cuda_compressor
|
||||
/tests/test_cuda_copy_spans
|
||||
/tests/test_cuda_dspark
|
||||
/tests/test_cuda_dspark_tp_markov
|
||||
/tests/test_cuda_dspark_tp_split
|
||||
/tests/test_cuda_embed
|
||||
/tests/test_cuda_f16_chunks
|
||||
/tests/test_cuda_grouped_q8
|
||||
/tests/test_cuda_hc_norm
|
||||
/tests/test_cuda_hc_pre
|
||||
/tests/test_cuda_indexed_one
|
||||
/tests/test_cuda_indexer
|
||||
/tests/test_cuda_mxfp4_split
|
||||
/tests/test_cuda_pool
|
||||
/tests/test_cuda_q8_0_gemv
|
||||
/tests/test_cuda_q8_pair
|
||||
/tests/test_cuda_router_stream
|
||||
/tests/test_cuda_topk_stream
|
||||
/tests/test_deepseek_indexer
|
||||
/tests/test_mxfp4_staged_cuda
|
||||
/tests/test_cuda_tp_repack
|
||||
/tests/test_metal_tp_bulk
|
||||
/tests/test_glm53_kda
|
||||
/tests/test_glm53_vision_engine
|
||||
/tests/test_glm_attention
|
||||
/tests/test_glm_attention_rocm
|
||||
/tests/test_linux_memory
|
||||
/tests/test_rocm_memory
|
||||
/tests/test_qwen4_kernels
|
||||
/tests/test_metal_device_policy
|
||||
/tests/test_metal_q8_reduction
|
||||
/tests/test_qwen4_moe_mm_specialize
|
||||
/tests/test_qwen4_ssd_experts
|
||||
/tests/test_qwen4_memory
|
||||
/tests/test_qwen4_generation
|
||||
/tests/test_qwen4_vision
|
||||
/tests/test_deepseek4_vision_image
|
||||
/tests/test_image_decode
|
||||
/tests/test_deepseek41_metal
|
||||
/tests/test_deepseek41_cuda
|
||||
/tests/test_cuda_q8_rows
|
||||
/tests/test_cuda_reductions
|
||||
/tests/test_cuda_shared
|
||||
/tests/test_cuda_ssd_cache
|
||||
/tests/test_cuda_ssd_batch
|
||||
/tests/test_cuda_ssd_repack
|
||||
/tests/test_deepseek41_gguf
|
||||
/tests/test_qwen4_ngrams
|
||||
/tests/test_qwen4_ngram_state
|
||||
/tests/test_qwen4_mtp_prefill
|
||||
/tests/test_qwen4_mtp_policy
|
||||
/tests/test_qwen4_ple_prefetch
|
||||
/tests/test_deepseek41_graph
|
||||
/tests/test_v41_indexer_score
|
||||
/tests/test_deepseek41_prefill
|
||||
/tests/test_deepseek41_cli
|
||||
/tests/test_engram
|
||||
*.o
|
||||
*.dSYM/
|
||||
__pycache__/
|
||||
@@ -40,3 +121,14 @@ __pycache__/
|
||||
/misc/
|
||||
.*.swp
|
||||
.DS_Store
|
||||
|
||||
/tests/test_qwen4_conv_parallel
|
||||
/tests/test_q8_prefill_variants
|
||||
/tests/test_metal_qwen4_hc
|
||||
/tests/test_metal_qwen4_moe_mid
|
||||
|
||||
/tests/test_metal_qwen4_moe_down
|
||||
/tests/test_metal_qwen4_mxfp4_stage
|
||||
/tests/test_metal_q8_gemv_reference
|
||||
/tests/test_metal_qwen4_moe_half
|
||||
/tests/test_qwen4_resident_arithmetic
|
||||
@@ -0,0 +1,54 @@
|
||||
# Evaluation data
|
||||
|
||||
`ds4-eval` contains small, curated subsets of external benchmarks. They are
|
||||
integration tests for DwarfStar, not official benchmark distributions or
|
||||
leaderboard scores. Case IDs are retained so each prompt can be checked against
|
||||
its source.
|
||||
|
||||
## Hard suite
|
||||
|
||||
The 50-case `hard` suite was assembled on 2026-09-03:
|
||||
|
||||
| Source | Cases | Source revision | License |
|
||||
|---|---:|---|---|
|
||||
| [MMLU-Pro](https://github.com/TIGER-AI-Lab/MMLU-Pro) | 30 | `f418b116db00b065c2aea046518d8fcf74d39872` | Apache-2.0 |
|
||||
| [OlympiadBench](https://huggingface.co/datasets/Hothan/OlympiadBench) | 10 | `91184b52131e7fc9455fef848035173aea8cc01a` | Apache-2.0 |
|
||||
| [LiveBench](https://huggingface.co/datasets/livebench/reasoning) | 5 | `6fc6498a5dfba553f69f4413feabade1f1a2d384` | Apache-2.0 |
|
||||
| [NIST Juliet 1.3](https://samate.nist.gov/SARD/test-suites/112) | 5 | archive SHA-256 `ada9d7e1c323d283446df3f55bdee0d00bda1fed786785fe98764d58688f38eb` | CC0-1.0 / public domain |
|
||||
|
||||
Selected source IDs:
|
||||
|
||||
- MMLU-Pro: `86`, `671`, `899`, `1467`, `1995`, `2398`, `2826`, `3024`,
|
||||
`3546`, `3696`, `4674`, `4699`, `5078`, `5848`, `6034`, `6500`, `6844`,
|
||||
`6955`, `7738`, `7754`, `8771`, `8863`, `9055`, `9487`, `10360`, `10567`,
|
||||
`10775`, `11120`, `11292`, `11346`.
|
||||
- OlympiadBench: `1606`, `1613`, `1614`, `1618`, `1697`, `1709`, `1716`,
|
||||
`1736`, `1750`, `1766`.
|
||||
- LiveBench: `c833574b0ff6e8609bee19b36fabc48501cdcd9a64b99cd0422f54ada1acc53d`,
|
||||
`b7b7e331df4614775210ef5e524ed7f20c5fec6655fb0dd179c94e4d71170862`,
|
||||
`0daa7ca38beec4441b9d5c04d0b98912322926f0a3ac28a5097889d4ed83506f`,
|
||||
`9ee37b9a04ab050936c86b2c5bb7abbaa0bc0e737d59a7bff9ba11e9b4069c1d`,
|
||||
`d7071c9ff5d9779e7ab955366d0ae8db40f785aadfe7ff0b5a7ede98c05c44ea`.
|
||||
- NIST Juliet reductions use the local IDs beginning with `juliet-` in
|
||||
`ds4_eval_cases.c`.
|
||||
|
||||
The MMLU-Pro choices and keys are unchanged. OlympiadBench cases are English,
|
||||
text-only rows; only their published final answers are embedded. LiveBench's
|
||||
benchmark-specific output wrapper was removed because `ds4-eval` supplies one
|
||||
uniform final-answer instruction. The NIST cases are short, locally written
|
||||
reductions of Juliet weakness patterns rather than copies of complete Juliet
|
||||
test cases.
|
||||
|
||||
Multiple-choice order is retained for MMLU-Pro. Open answers declare their
|
||||
grading type and, where needed, a small list of equivalent surface forms. The
|
||||
12-case `hard-smoke` suite is a fixed subset of `hard`.
|
||||
|
||||
Apache-2.0 terms for redistributed benchmark material are included in
|
||||
[`licenses/Apache-2.0.txt`](licenses/Apache-2.0.txt). DwarfStar's own source
|
||||
remains under the repository's top-level MIT license.
|
||||
|
||||
## Core suite
|
||||
|
||||
The original 92-case `core` suite contains 25 GPQA Diamond, 25 SuperGPQA, 25
|
||||
AIME 2025, and 17 reduced defensive code-review cases. Its existing source IDs,
|
||||
ordering, prompts, and answer keys are unchanged by the hard-suite addition.
|
||||
@@ -2,6 +2,7 @@ MIT License
|
||||
|
||||
Copyright (c) 2026 The ds4.c authors
|
||||
Copyright (c) 2023-2026 The ggml authors
|
||||
Copyright (c) 2023 DeepSeek
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
+1423
-128
File diff suppressed because it is too large.
Load diff
+3
-143
@@ -1,146 +1,6 @@
|
||||
# DS4 on Strix Halo
|
||||
|
||||
This is the minimal setup for DS4 ROCm inference on a
|
||||
Strix Halo machine with 128 GB RAM and Radeon 8060S (`gfx1151`).
|
||||
The setup guide has moved to [docs/STRIX_HALO.md](docs/STRIX_HALO.md).
|
||||
|
||||
## 1. Install ROCm
|
||||
|
||||
On Ubuntu 26.04 LTS, install the ROCm compiler/runtime and libraries used by the Strix Halo backend:
|
||||
|
||||
```sh
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
hipcc rocminfo rocm-smi \
|
||||
libamdhip64-dev \
|
||||
libhipblas-dev libhipblaslt-dev \
|
||||
librocblas-dev \
|
||||
librocwmma-dev \
|
||||
libhipcub-dev
|
||||
```
|
||||
|
||||
The backend uses rocWMMA. On this Ubuntu 26.04 setup, `librocwmma-dev`
|
||||
installs the top-level rocWMMA headers but misses `rocwmma/internal/`.
|
||||
No Ubuntu package currently provides those internal headers. Install a complete
|
||||
matching rocWMMA header tree:
|
||||
|
||||
```sh
|
||||
git clone --depth 1 --branch rocm-7.1.0 https://github.com/ROCm/rocWMMA.git /tmp/rocWMMA-rocm-7.1.0
|
||||
sudo mkdir -p /usr/local/include
|
||||
sudo cp -a /tmp/rocWMMA-rocm-7.1.0/library/include/rocwmma /usr/local/include/
|
||||
```
|
||||
|
||||
If ROCm is installed under `/usr` but tooling expects `/opt/rocm`, add these
|
||||
compatibility links:
|
||||
|
||||
```sh
|
||||
sudo mkdir -p /opt/rocm/bin
|
||||
sudo ln -sf /usr/bin/hipcc /opt/rocm/bin/hipcc
|
||||
sudo ln -sfn /usr/lib/x86_64-linux-gnu /opt/rocm/lib
|
||||
sudo ln -sfn /usr/include /opt/rocm/include
|
||||
```
|
||||
|
||||
## 2. Enable ROCm access
|
||||
|
||||
The user running DS4 must be able to open `/dev/kfd` and the DRM render node:
|
||||
|
||||
```sh
|
||||
sudo usermod -aG render,video "$USER"
|
||||
```
|
||||
|
||||
Log out and back in, or reboot. Verify:
|
||||
|
||||
```sh
|
||||
rocminfo | grep -A80 'Name: gfx1151'
|
||||
```
|
||||
|
||||
If DS4 says `no ROCm-capable device is detected`, check that `rocminfo` can open
|
||||
`/dev/kfd` and that `groups` includes `render`.
|
||||
|
||||
## 3. Increase GPU-visible memory
|
||||
|
||||
A 128 GB Strix Halo system may initially expose only about 62 GB of GPU-visible
|
||||
memory. DS4 needs the larger GTT aperture for the 80.76 GiB model plus runtime
|
||||
buffers.
|
||||
|
||||
Use these kernel parameters:
|
||||
|
||||
```text
|
||||
amd_iommu=off amdgpu.gttsize=126976 ttm.pages_limit=32505856 ttm.page_pool_size=32505856
|
||||
```
|
||||
|
||||
On Ubuntu with GRUB:
|
||||
|
||||
```sh
|
||||
sudo cp /etc/default/grub /etc/default/grub.bak
|
||||
sudoedit /etc/default/grub
|
||||
```
|
||||
|
||||
Set:
|
||||
|
||||
```text
|
||||
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash amd_iommu=off amdgpu.gttsize=126976 ttm.pages_limit=32505856 ttm.page_pool_size=32505856"
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```sh
|
||||
sudo update-grub
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
After reboot, verify:
|
||||
|
||||
```sh
|
||||
cat /proc/cmdline
|
||||
sudo dmesg | grep -Ei 'GTT|gttsize|TTM|VRAM'
|
||||
rocminfo | grep -A80 'Name: gfx1151'
|
||||
```
|
||||
|
||||
Expected signs:
|
||||
|
||||
```text
|
||||
amdgpu: 126976M of GTT memory ready
|
||||
rocminfo gfx1151 pool: 130023424 KB
|
||||
```
|
||||
|
||||
## 4. Build DS4
|
||||
|
||||
Use the normal Strix Halo target. It builds the standard binary names:
|
||||
|
||||
```sh
|
||||
make strix-halo -j"$(nproc)"
|
||||
```
|
||||
|
||||
`make rocm` is an alias for `make strix-halo`.
|
||||
|
||||
After changes to the ROCm MXFP4 or routed-MoE kernels, run the standalone
|
||||
CPU-oracle regression:
|
||||
|
||||
```sh
|
||||
make test-mxfp4-rocm
|
||||
```
|
||||
|
||||
This test does not require a full model GGUF. It covers resident decode and
|
||||
batched routed-MoE execution at 1, 3, 32, 128, and 512 tokens.
|
||||
|
||||
## 5. Use the right GGUF
|
||||
|
||||
Use the standard IQ2XXS/Q2K/Q8 imatrix GGUF:
|
||||
|
||||
```text
|
||||
DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix.gguf
|
||||
```
|
||||
|
||||
Avoid the mixed IQ2/IQ4 or IQ2/Q4 GGUFs on this machine for now. They put much
|
||||
more memory pressure on the ROCm path and can trigger system OOM instead of a
|
||||
clean DS4 failure.
|
||||
|
||||
## 6. Run DS4
|
||||
|
||||
Run it normally:
|
||||
|
||||
```sh
|
||||
./ds4 -m gguf/DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix.gguf
|
||||
```
|
||||
|
||||
The ROCm build uses the Strix Halo backend automatically.
|
||||
It covers ROCm prerequisites, GPU-visible memory, current model downloads,
|
||||
and first runs for DeepSeek Flash and GLM 5.3 Flash.
|
||||
@@ -94,6 +94,14 @@ Symbols the vendored files reference, and how they resolve in this directory:
|
||||
|
||||
## Re-syncing with upstream
|
||||
|
||||
Local MMVQ bounds fix: pass the logical row count to the generic vector
|
||||
kernel instead of using its output stride as a row bound. Both generic and
|
||||
multi-token MoE kernels also guard tail-row weight loads.
|
||||
|
||||
MXFP4 MMQ uses Q8_1 activations on Blackwell as on older GPUs. Its tile
|
||||
loader, K iteration and activation-block stride use the integer-MMA layout;
|
||||
the stored MXFP4 weights are unchanged.
|
||||
|
||||
When upstream lands a bugfix or perf improvement we want, the procedure is:
|
||||
|
||||
```sh
|
||||
|
||||
@@ -4,19 +4,25 @@
|
||||
// new_pool_for_device that the vendored common.cuh declares without
|
||||
// defining.
|
||||
//
|
||||
// Phase 0: pool is plain cudaMallocAsync / cudaFreeAsync. Phase 4 swaps
|
||||
// in ds4's existing cuda_tmp_alloc slab allocator.
|
||||
// CUDA uses stream-ordered allocation. HIP retains stream-local allocations
|
||||
// because returning live MMQ scratch to its asynchronous pool is unreliable.
|
||||
|
||||
#include "common.cuh" // pulls in ds4_ggml_stubs.h via redirect headers
|
||||
|
||||
#if defined(GGML_USE_HIP)
|
||||
#include "vendors/hip.h"
|
||||
#include "../../ds4_rocm_memory.h"
|
||||
#else
|
||||
#include <cuda_runtime.h>
|
||||
#include <cublas_v2.h>
|
||||
#endif
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Device info singleton.
|
||||
@@ -44,10 +50,23 @@ const ggml_cuda_device_info & ggml_cuda_info() {
|
||||
for (int i = 0; i < count; i++) {
|
||||
cudaDeviceProp p;
|
||||
CUDA_CHECK(cudaGetDeviceProperties(&p, i));
|
||||
#if defined(GGML_USE_HIP)
|
||||
unsigned gfx_id = 0;
|
||||
if (sscanf(p.gcnArchName, "gfx%x", &gfx_id) == 1) {
|
||||
info.devices[i].cc = GGML_CUDA_CC_OFFSET_AMD + (int)gfx_id;
|
||||
} else {
|
||||
info.devices[i].cc = GGML_CUDA_CC_OFFSET_AMD + p.major * 0x100 + p.minor * 0x10;
|
||||
}
|
||||
#else
|
||||
info.devices[i].cc = p.major * 100 + p.minor * 10;
|
||||
#endif
|
||||
info.devices[i].nsm = p.multiProcessorCount;
|
||||
info.devices[i].smpb = p.sharedMemPerBlock;
|
||||
#if defined(GGML_USE_HIP)
|
||||
info.devices[i].smpbo = p.sharedMemPerBlock;
|
||||
#else
|
||||
info.devices[i].smpbo = p.sharedMemPerBlockOptin;
|
||||
#endif
|
||||
info.devices[i].integrated = p.integrated != 0;
|
||||
info.devices[i].vmm = false;
|
||||
info.devices[i].vmm_granularity = 0;
|
||||
@@ -64,13 +83,13 @@ const ggml_cuda_device_info & ggml_cuda_info() {
|
||||
|
||||
int ggml_cuda_get_device() {
|
||||
int dev = 0;
|
||||
cudaGetDevice(&dev);
|
||||
(void)cudaGetDevice(&dev);
|
||||
return dev;
|
||||
}
|
||||
|
||||
void ggml_cuda_set_device(int device) {
|
||||
int cur = -1;
|
||||
cudaGetDevice(&cur);
|
||||
(void)cudaGetDevice(&cur);
|
||||
if (cur != device) {
|
||||
CUDA_CHECK(cudaSetDevice(device));
|
||||
}
|
||||
@@ -95,7 +114,7 @@ int64_t ggml_time_us() {
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Concrete pool wrapping cudaMallocAsync / cudaFreeAsync.
|
||||
// Concrete device scratch pool.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
@@ -126,19 +145,78 @@ struct ds4_naive_pool : public ggml_cuda_pool {
|
||||
|
||||
explicit ds4_naive_pool(int device) : device(device) {}
|
||||
|
||||
#if defined(GGML_USE_HIP)
|
||||
struct cached_block {
|
||||
void *ptr;
|
||||
size_t size;
|
||||
cudaStream_t stream;
|
||||
};
|
||||
|
||||
std::mutex mutex;
|
||||
std::vector<cached_block> available;
|
||||
std::vector<void *> owned;
|
||||
#endif
|
||||
|
||||
void * alloc(size_t size, size_t * actual_size) override {
|
||||
ggml_cuda_set_device(device);
|
||||
#if defined(GGML_USE_HIP)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(mutex);
|
||||
size_t best = available.size();
|
||||
for (size_t i = 0; i < available.size(); i++) {
|
||||
if (available[i].stream != t_ds4_pool_stream ||
|
||||
available[i].size < size) {
|
||||
continue;
|
||||
}
|
||||
if (best == available.size() ||
|
||||
available[i].size < available[best].size) {
|
||||
best = i;
|
||||
}
|
||||
}
|
||||
if (best != available.size()) {
|
||||
const cached_block block = available[best];
|
||||
available[best] = available.back();
|
||||
available.pop_back();
|
||||
if (actual_size) *actual_size = block.size;
|
||||
return block.ptr;
|
||||
}
|
||||
}
|
||||
|
||||
void * ptr = nullptr;
|
||||
CUDA_CHECK(ds4_rocm_allocation_fits(size) ? hipSuccess : hipErrorOutOfMemory);
|
||||
CUDA_CHECK(cudaMallocAsync(&ptr, size, t_ds4_pool_stream));
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(mutex);
|
||||
owned.push_back(ptr);
|
||||
}
|
||||
if (actual_size) *actual_size = size;
|
||||
return ptr;
|
||||
#else
|
||||
void * ptr = nullptr;
|
||||
CUDA_CHECK(cudaMallocAsync(&ptr, size, t_ds4_pool_stream));
|
||||
if (actual_size) *actual_size = size;
|
||||
return ptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
void free(void * ptr, size_t /*size*/) override {
|
||||
void free(void * ptr, size_t size) override {
|
||||
if (!ptr) return;
|
||||
ggml_cuda_set_device(device);
|
||||
#if defined(GGML_USE_HIP)
|
||||
std::lock_guard<std::mutex> guard(mutex);
|
||||
available.push_back({ptr, size, t_ds4_pool_stream});
|
||||
#else
|
||||
CUDA_CHECK(cudaFreeAsync(ptr, t_ds4_pool_stream));
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(GGML_USE_HIP)
|
||||
~ds4_naive_pool() override {
|
||||
ggml_cuda_set_device(device);
|
||||
(void)cudaDeviceSynchronize();
|
||||
for (void *ptr : owned) (void)cudaFree(ptr);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
@@ -149,7 +227,7 @@ std::unique_ptr<ggml_cuda_pool> ggml_backend_cuda_context::new_pool_for_device(i
|
||||
|
||||
ggml_backend_cuda_context::~ggml_backend_cuda_context() {
|
||||
if (copy_event) {
|
||||
cudaEventDestroy(copy_event);
|
||||
(void)cudaEventDestroy(copy_event);
|
||||
copy_event = nullptr;
|
||||
}
|
||||
// streams[][], cublas_handles[], and pools[][] are owned-by-value
|
||||
|
||||
+873
-59
File diff suppressed because it is too large.
Load diff
+11
-4
@@ -14,7 +14,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(GGML_USE_HIP)
|
||||
#include "vendors/hip.h"
|
||||
#else
|
||||
#include <cuda_runtime.h>
|
||||
#endif
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -319,7 +323,8 @@ int ds4_mmq_iq2_xxs_moe_pair_soa(
|
||||
* assignment map once, runs the paired gate/up MMQs, computes clamp +
|
||||
* SwiGLU + router weighting in mid_f32, then gathers and quantizes those
|
||||
* rows for the Q2_K down MMQ through the same ids_dst/expert_bounds. No
|
||||
* second mm_ids_helper; gate/up/mid/down keep the pair-major layout. */
|
||||
* second mm_ids_helper; gate/up/mid/down keep the pair-major layout.
|
||||
* preserve_reduction retains the raw MMQ accumulation order instead of D2R. */
|
||||
int ds4_mmq_iq2_xxs_q2_K_moe_fused_soa(
|
||||
const void * W_gate_soa,
|
||||
const void * W_up_soa,
|
||||
@@ -338,6 +343,7 @@ int ds4_mmq_iq2_xxs_q2_K_moe_fused_soa(
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
float clamp,
|
||||
int preserve_reduction,
|
||||
cudaStream_t stream);
|
||||
|
||||
/* Aligned-artifact production fast path: gate/up stay in registers, weighted
|
||||
@@ -878,11 +884,12 @@ int ds4_mmq_q4_K_dense_pair_vec(
|
||||
int K,
|
||||
cudaStream_t stream);
|
||||
|
||||
// Set the thread-local stream that the internal cuda pool uses for
|
||||
// cudaMallocAsync / cudaFreeAsync. Defaults to cudaStreamPerThread.
|
||||
// Set the thread-local stream used for internal stream-ordered scratch.
|
||||
// Defaults to cudaStreamPerThread.
|
||||
// Step 8 (CUDA Graphs) calls this with the capture stream so pool
|
||||
// allocations land on the captured stream and don't invalidate capture.
|
||||
// Pass NULL to reset to cudaStreamPerThread.
|
||||
// Pass cudaStreamPerThread to restore the default; NULL selects the legacy
|
||||
// default stream.
|
||||
void ds4_pool_set_stream(cudaStream_t stream);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -14,8 +14,12 @@
|
||||
#include "common.cuh"
|
||||
#include "mmq.cuh"
|
||||
|
||||
#if defined(GGML_USE_HIP)
|
||||
#include "vendors/hip.h"
|
||||
#else
|
||||
#include <cuda_fp16.h>
|
||||
#include <cuda_runtime.h>
|
||||
#endif
|
||||
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(GGML_USE_HIP)
|
||||
#include "vendors/hip.h"
|
||||
#else
|
||||
#include <cuda_runtime.h>
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
+42
-4
@@ -6,8 +6,12 @@
|
||||
* lines are the gate. */
|
||||
#include "ds4_repack.h"
|
||||
|
||||
#if defined(GGML_USE_HIP)
|
||||
#include "vendors/hip.h"
|
||||
#else
|
||||
#include <cuda_runtime.h>
|
||||
#include <cuda_fp16.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
@@ -201,6 +205,7 @@ static bool tensor_type_info(uint32_t type, uint64_t &block_elems, uint64_t &blo
|
||||
case 28: block_elems = 1; block_bytes = 8; return true;
|
||||
case 29: block_elems = 256; block_bytes = 56; return true;
|
||||
case 30: block_elems = 1; block_bytes = 2; return true;
|
||||
case 39: block_elems = 32; block_bytes = 17; return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
@@ -337,12 +342,16 @@ __global__ static void repack_iq2_xxs_aligned_kernel(
|
||||
__half *dq,
|
||||
uint2 *qs,
|
||||
const unsigned char *raw,
|
||||
uint64_t nblk) {
|
||||
uint64_t nblk,
|
||||
const int32_t *slots = nullptr,
|
||||
uint64_t expert_blocks = 0) {
|
||||
const uint64_t i = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i >= nblk * 8ull) return;
|
||||
const uint64_t blk = i >> 3;
|
||||
const uint32_t p = (uint32_t)(i & 7u);
|
||||
const unsigned char *src = raw + blk * 66ull;
|
||||
const uint64_t source = slots ?
|
||||
(uint64_t)slots[blk / expert_blocks] * expert_blocks + blk % expert_blocks : blk;
|
||||
const unsigned char *src = raw + source * 66ull;
|
||||
if (p == 0u) {
|
||||
uint16_t h;
|
||||
memcpy(&h, src, 2u);
|
||||
@@ -373,7 +382,8 @@ __global__ static void repack_q2_k_aligned_kernel(
|
||||
uint64_t g0, // absolute raw-block index of raw[0]
|
||||
uint64_t cblk, // raw blocks in this chunk
|
||||
uint32_t nb_row, // blocks per row = K/256
|
||||
uint32_t nrows) { // rows per expert = M
|
||||
uint32_t nrows, // rows per expert = M
|
||||
const int32_t *slots = nullptr) {
|
||||
const uint64_t i = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i >= cblk * 16ull) return;
|
||||
const uint64_t j = i >> 4;
|
||||
@@ -384,7 +394,10 @@ __global__ static void repack_q2_k_aligned_kernel(
|
||||
const uint64_t e = g / ((uint64_t)nb_row * nrows);
|
||||
const uint64_t pblk = ((uint64_t)e * (nrows/2u) + r/2u) * nb_row + b;
|
||||
const uint32_t parity = r & 1u;
|
||||
const unsigned char *src = raw + j * 84ull;
|
||||
const uint64_t expert_blocks = (uint64_t)nb_row * nrows;
|
||||
const uint64_t source = slots ?
|
||||
(uint64_t)slots[j / expert_blocks] * expert_blocks + j % expert_blocks : j;
|
||||
const unsigned char *src = raw + source * 84ull;
|
||||
uint32_t w;
|
||||
memcpy(&w, src + 16u + (uint64_t)p * 4u, 4u);
|
||||
qs2[pblk * 32ull + (uint64_t)p * 2ull + parity] = w;
|
||||
@@ -399,6 +412,31 @@ __global__ static void repack_q2_k_aligned_kernel(
|
||||
}
|
||||
}
|
||||
|
||||
bool ds4_repack_selected_experts(void *dst, const void *raw, const int32_t *slots,
|
||||
uint32_t kind, uint32_t rows, uint32_t cols,
|
||||
uint32_t count) {
|
||||
if (!dst || !raw || !slots || !rows || !cols || !count || cols % 256u) return false;
|
||||
const uint64_t expert_blocks = (uint64_t)rows * (cols / 256u);
|
||||
if (expert_blocks > UINT64_MAX / count) return false;
|
||||
const uint64_t blocks = expert_blocks * count;
|
||||
if (blocks > (uint64_t)INT_MAX * 16u) return false;
|
||||
if (kind == DS4_REPACK_IQ2_XXS_ALIGNED_MOE) {
|
||||
const uint64_t scales = (blocks * 2u + 63u) & ~UINT64_C(63);
|
||||
repack_iq2_xxs_aligned_kernel<<<(unsigned)((blocks * 8u + 255u) / 256u), 256>>>(
|
||||
(__half *)dst, (uint2 *)((char *)dst + scales),
|
||||
(const unsigned char *)raw, blocks, slots, expert_blocks);
|
||||
} else if (kind == DS4_REPACK_Q2_K_ALIGNED_MOE && rows % 2u == 0u) {
|
||||
const uint64_t pairs = blocks / 2u;
|
||||
const uint64_t dm_bytes = (pairs * 8u + 63u) & ~UINT64_C(63);
|
||||
const uint64_t sc_bytes = (pairs * 32u + 63u) & ~UINT64_C(63);
|
||||
repack_q2_k_aligned_kernel<<<(unsigned)((blocks * 16u + 255u) / 256u), 256>>>(
|
||||
(uint32_t *)dst, (uint32_t *)((char *)dst + dm_bytes),
|
||||
(uint32_t *)((char *)dst + dm_bytes + sc_bytes),
|
||||
(const unsigned char *)raw, 0, blocks, cols / 256u, rows, slots);
|
||||
} else return false;
|
||||
return cudaGetLastError() == cudaSuccess;
|
||||
}
|
||||
|
||||
/* One thread per (block, 16B half of the 32B code payload); p==0 additionally
|
||||
* splits out the block scale. Source raw block_q8_0 is 34 bytes =
|
||||
* [half d][32 x int8 codes]. */
|
||||
|
||||
@@ -106,6 +106,13 @@ void ds4_repack_set_hash(bool on);
|
||||
|
||||
uint64_t ds4_repack_fnv1a(const unsigned char *p, uint64_t n, uint64_t h);
|
||||
|
||||
/* Gather raw cached experts into one aligned artifact on the default stream.
|
||||
* slots is a device array of count valid source expert indices. The caller
|
||||
* sizes dst using the matching ds4_mmq_*_aligned_bytes layout contract. */
|
||||
bool ds4_repack_selected_experts(void *dst, const void *raw, const int32_t *slots,
|
||||
uint32_t kind, uint32_t rows, uint32_t cols,
|
||||
uint32_t count);
|
||||
|
||||
/* Build every candidate's aligned artifact. On success appends the built
|
||||
* artifacts to out, adds their byte total to *repacked_bytes_out (may be
|
||||
* NULL), and returns true; on any failure releases every artifact this call
|
||||
|
||||
+99
-14
@@ -109,13 +109,14 @@ struct tile_x_sizes {
|
||||
};
|
||||
|
||||
static int get_mmq_x_max_host(const int cc) {
|
||||
int base = (turing_mma_available(cc) || amd_wmma_available(cc)) ? 128 :
|
||||
const int hardware_max = (turing_mma_available(cc) || amd_wmma_available(cc)) ? 128 :
|
||||
GGML_CUDA_CC_IS_NVIDIA(cc) && ggml_cuda_highest_compiled_arch(cc) >= GGML_CUDA_CC_VOLTA ?
|
||||
#ifdef GGML_CUDA_FORCE_MMQ
|
||||
128 : 64;
|
||||
#else
|
||||
MMQ_DP4A_MAX_BATCH_SIZE : 64;
|
||||
#endif // GGML_CUDA_FORCE_MMQ
|
||||
int base = cc == GGML_CUDA_CC_OFFSET_AMD + 0x1151 ? 64 : hardware_max;
|
||||
// ds4: optional Step-4 experiment hook. DS4_CUDA_MMQ_X_MAX=N clips the
|
||||
// tile-width selector to N when sweeping for an sm_120-specific
|
||||
// optimum. Value rounded down to a multiple of 8 (the iteration step
|
||||
@@ -133,7 +134,7 @@ static int get_mmq_x_max_host(const int cc) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (g_override > 0 && g_override < base) base = g_override;
|
||||
if (g_override > 0) base = std::min(g_override, hardware_max);
|
||||
return base;
|
||||
}
|
||||
|
||||
@@ -161,13 +162,18 @@ static constexpr __device__ int get_mmq_x_max_device() {
|
||||
}
|
||||
|
||||
static int get_mmq_y_host(const int cc) {
|
||||
#if defined(GGML_USE_HIP) && defined(DS4_HIP_MMQ_Y)
|
||||
GGML_UNUSED(cc);
|
||||
return DS4_HIP_MMQ_Y;
|
||||
#else
|
||||
return GGML_CUDA_CC_IS_AMD(cc) ? (GGML_CUDA_CC_IS_RDNA1(cc) ? 64 : 128) :
|
||||
((GGML_CUDA_CC_IS_NVIDIA(cc) && ggml_cuda_highest_compiled_arch(cc) >= GGML_CUDA_CC_VOLTA) ? 128 : 64);
|
||||
#endif
|
||||
}
|
||||
|
||||
static constexpr __device__ int get_iter_k([[maybe_unused]] const ggml_type type) {
|
||||
#if defined(BLACKWELL_MMA_AVAILABLE)
|
||||
if (type == GGML_TYPE_NVFP4 || type == GGML_TYPE_MXFP4) {
|
||||
if (type == GGML_TYPE_NVFP4) {
|
||||
return MMQ_ITER_K_FP4;
|
||||
}
|
||||
#endif // defined(BLACKWELL_MMA_AVAILABLE)
|
||||
@@ -176,11 +182,15 @@ if (type == GGML_TYPE_NVFP4 || type == GGML_TYPE_MXFP4) {
|
||||
|
||||
static constexpr __device__ int get_mmq_y_device() {
|
||||
#if defined(GGML_USE_HIP)
|
||||
#if defined(DS4_HIP_MMQ_Y)
|
||||
return DS4_HIP_MMQ_Y;
|
||||
#else
|
||||
#if defined(RDNA1)
|
||||
return 64;
|
||||
#else
|
||||
return 128;
|
||||
#endif // defined RDNA1
|
||||
#endif // defined(DS4_HIP_MMQ_Y)
|
||||
#else
|
||||
#if __CUDA_ARCH__ >= GGML_CUDA_CC_VOLTA
|
||||
return 128;
|
||||
@@ -317,7 +327,11 @@ static constexpr __device__ int mmq_get_granularity_device(const int /*mmq_x*/)
|
||||
|
||||
#if defined(GGML_USE_HIP)
|
||||
static int mmq_get_nwarps_host(const int cc, const int warp_size) {
|
||||
#if defined(DS4_HIP_MMQ_Y)
|
||||
return amd_mfma_available(cc) ? 8 : DS4_HIP_MMQ_Y/16;
|
||||
#else
|
||||
return amd_mfma_available(cc) ? 8 : 256/warp_size;
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
static int mmq_get_nwarps_host(const int /*cc*/, const int warp_size) {
|
||||
@@ -327,7 +341,11 @@ static int mmq_get_nwarps_host(const int /*cc*/, const int warp_size) {
|
||||
|
||||
static constexpr __device__ int mmq_get_nwarps_device() {
|
||||
#if defined(AMD_MFMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
|
||||
#if defined(DS4_HIP_MMQ_Y)
|
||||
return DS4_HIP_MMQ_Y/16;
|
||||
#else
|
||||
return 8;
|
||||
#endif
|
||||
#else
|
||||
return 256/ggml_cuda_get_physical_warp_size();
|
||||
#endif // AMD_MFMA_AVAILABLE
|
||||
@@ -2966,12 +2984,13 @@ template <int mmq_y, bool need_check> static __device__ __forceinline__ void loa
|
||||
#pragma unroll
|
||||
for (int l = 0; l < QR2_XXS; ++l) {
|
||||
const uint2 grid_pos = ((const uint2*)iq2xxs_grid)[aux8[l]];
|
||||
const uint32_t signs = unpack_ksigns(aux32 >> (7 * l));
|
||||
const uint2 sign_masks =
|
||||
((const uint2 *) ksigns64)[(aux32 >> (7 * l)) & 0x7fu];
|
||||
|
||||
const int signs0 = __vcmpne4(signs & 0x08040201, 0);
|
||||
const int signs0 = (int)sign_masks.x;
|
||||
const int grid0 = __vsub4(grid_pos.x ^ signs0, signs0);
|
||||
|
||||
const int signs1 = __vcmpne4(signs & 0x80402010, 0);
|
||||
const int signs1 = (int)sign_masks.y;
|
||||
const int grid1 = __vsub4(grid_pos.y ^ signs1, signs1);
|
||||
|
||||
#if defined(AMD_MFMA_AVAILABLE) || defined(TURING_MMA_AVAILABLE) || defined(AMD_WMMA_AVAILABLE)
|
||||
@@ -3511,13 +3530,10 @@ struct mmq_type_traits<mmq_x, mmq_y, need_check, GGML_TYPE_Q8_0> {
|
||||
template <int mmq_x, int mmq_y, bool need_check>
|
||||
struct mmq_type_traits<mmq_x, mmq_y, need_check, GGML_TYPE_MXFP4> {
|
||||
static constexpr int vdr = VDR_MXFP4_Q8_1_MMQ;
|
||||
#ifdef BLACKWELL_MMA_AVAILABLE
|
||||
static constexpr load_tiles_mmq_t load_tiles = load_tiles_mxfp4_fp4<mmq_y, need_check>;
|
||||
static constexpr vec_dot_mmq_t vec_dot_mma = vec_dot_fp4_fp4_mma<mmq_x, mmq_y, GGML_TYPE_MXFP4>;
|
||||
#else
|
||||
// Packed FP4 weights do not imply FP4 activations. Preserve Q8 activation
|
||||
// precision on Blackwell too, using the exact integer weight mantissas.
|
||||
static constexpr load_tiles_mmq_t load_tiles = load_tiles_mxfp4<mmq_y, need_check>;
|
||||
static constexpr vec_dot_mmq_t vec_dot_mma = vec_dot_q8_0_q8_1_mma<mmq_x, mmq_y, MMQ_Q8_1_DS_LAYOUT_D4>;
|
||||
#endif // BLACKWELL_MMA_AVAILABLE
|
||||
static constexpr vec_dot_mmq_t vec_dot_dp4a = vec_dot_q8_0_q8_1_dp4a<mmq_x, mmq_y>;
|
||||
};
|
||||
|
||||
@@ -3666,13 +3682,36 @@ static __device__ __forceinline__ void mul_mat_q_process_tile(
|
||||
|
||||
#if defined(BLACKWELL_MMA_AVAILABLE)
|
||||
// FP4 tile stores 8 blocks
|
||||
constexpr int ne_block = (type == GGML_TYPE_MXFP4 || type == GGML_TYPE_NVFP4) ? QK_K : 4 * QK8_1;
|
||||
constexpr int ne_block = type == GGML_TYPE_NVFP4 ? QK_K : 4 * QK8_1;
|
||||
#else
|
||||
constexpr int ne_block = 4 * QK8_1;
|
||||
#endif // defined(BLACKWELL_MMA_AVAILABLE)
|
||||
|
||||
constexpr int ITER_K = get_iter_k(type);
|
||||
constexpr int blocks_per_iter = ITER_K / qk;
|
||||
static_assert(type != GGML_TYPE_MXFP4 ||
|
||||
(ITER_K == MMQ_ITER_K && ne_block == 4 * QK8_1),
|
||||
"MXFP4 MMQ requires Q8_1 activation tiles");
|
||||
// Invalid columns must be zero before the matrix instruction. Masking
|
||||
// only write-back is too late: stale or NaN tail blocks can contaminate
|
||||
// other values in the same hardware matrix fragment.
|
||||
const int valid_y_words = min(mmq_x, tile_y_max_j + 1) * MMQ_TILE_Y_K;
|
||||
#if defined(GGML_USE_HIP) && defined(__gfx1151__)
|
||||
// Load scheduling can change fast-math accumulation order. Keep the
|
||||
// established path outside the larger gfx1151 IQ2 prefill templates.
|
||||
constexpr bool fast_y_loads = type == GGML_TYPE_IQ2_XXS && mmq_x >= 32;
|
||||
const bool full_y_tile = tile_y_max_j >= mmq_x - 1;
|
||||
const int linear_tid = threadIdx.y*warp_size + threadIdx.x;
|
||||
constexpr int nthreads = nwarps * warp_size;
|
||||
|
||||
// Seed invalid columns once. Both K stages refresh only the valid prefix,
|
||||
// and the existing barriers keep the suffix zero before every matrix op.
|
||||
if (fast_y_loads && !full_y_tile) {
|
||||
for (int l = valid_y_words + linear_tid; l < mmq_x * MMQ_TILE_Y_K; l += nthreads) {
|
||||
tile_y[l] = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
float sum[mmq_x*mmq_y / (nwarps*warp_size)] = {0.0f};
|
||||
|
||||
@@ -3702,12 +3741,35 @@ static __device__ __forceinline__ void mul_mat_q_process_tile(
|
||||
}
|
||||
{
|
||||
const int * by0 = y + ncols_y * (kb0 * qk / ne_block) * sz;
|
||||
#if defined(GGML_USE_HIP) && defined(__gfx1151__)
|
||||
if constexpr (fast_y_loads) {
|
||||
if (full_y_tile) {
|
||||
#pragma unroll
|
||||
for (int l0 = 0; l0 < mmq_x * MMQ_TILE_Y_K; l0 += nthreads) {
|
||||
const int l = l0 + linear_tid;
|
||||
tile_y[l] = by0[l];
|
||||
}
|
||||
} else {
|
||||
for (int l = linear_tid; l < valid_y_words; l += nthreads) {
|
||||
tile_y[l] = by0[l];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#pragma unroll
|
||||
for (int l0 = 0; l0 < mmq_x * MMQ_TILE_Y_K; l0 += nwarps * warp_size) {
|
||||
int l = l0 + threadIdx.y*warp_size + threadIdx.x;
|
||||
|
||||
tile_y[l] = l < valid_y_words ? by0[l] : 0;
|
||||
}
|
||||
}
|
||||
#else
|
||||
#pragma unroll
|
||||
for (int l0 = 0; l0 < mmq_x * MMQ_TILE_Y_K; l0 += nwarps * warp_size) {
|
||||
int l = l0 + threadIdx.y*warp_size + threadIdx.x;
|
||||
|
||||
tile_y[l] = by0[l];
|
||||
tile_y[l] = l < valid_y_words ? by0[l] : 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
@@ -3718,12 +3780,35 @@ static __device__ __forceinline__ void mul_mat_q_process_tile(
|
||||
|
||||
{
|
||||
const int * by0 = y + ncols_y * ((kb0 * qk / ne_block) * sz + sz);
|
||||
#if defined(GGML_USE_HIP) && defined(__gfx1151__)
|
||||
if constexpr (fast_y_loads) {
|
||||
if (full_y_tile) {
|
||||
#pragma unroll
|
||||
for (int l0 = 0; l0 < mmq_x * MMQ_TILE_Y_K; l0 += nthreads) {
|
||||
const int l = l0 + linear_tid;
|
||||
tile_y[l] = by0[l];
|
||||
}
|
||||
} else {
|
||||
for (int l = linear_tid; l < valid_y_words; l += nthreads) {
|
||||
tile_y[l] = by0[l];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#pragma unroll
|
||||
for (int l0 = 0; l0 < mmq_x * MMQ_TILE_Y_K; l0 += nwarps * warp_size) {
|
||||
int l = l0 + threadIdx.y*warp_size + threadIdx.x;
|
||||
|
||||
tile_y[l] = l < valid_y_words ? by0[l] : 0;
|
||||
}
|
||||
}
|
||||
#else
|
||||
#pragma unroll
|
||||
for (int l0 = 0; l0 < mmq_x * MMQ_TILE_Y_K; l0 += nwarps * warp_size) {
|
||||
int l = l0 + threadIdx.y*warp_size + threadIdx.x;
|
||||
|
||||
tile_y[l] = by0[l];
|
||||
tile_y[l] = l < valid_y_words ? by0[l] : 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
|
||||
+19
-16
@@ -395,7 +395,7 @@ template <ggml_type type, int ncols_dst, bool has_fusion, bool small_k = false>
|
||||
__launch_bounds__(calc_nwarps(type, ncols_dst, get_device_table_id())*ggml_cuda_get_physical_warp_size(), 1)
|
||||
static __global__ void mul_mat_vec_q(
|
||||
const void * __restrict__ vx, const void * __restrict__ vy, const int32_t * __restrict__ ids, const ggml_cuda_mm_fusion_args_device fusion, float * __restrict__ dst,
|
||||
const uint32_t ncols_x, const uint3 nchannels_y, const uint32_t stride_row_x, const uint32_t stride_col_y,
|
||||
const uint32_t ncols_x, const uint3 nchannels_y, const uint32_t nrows_x, const uint32_t stride_row_x, const uint32_t stride_col_y,
|
||||
const uint32_t stride_col_dst, const uint3 channel_ratio, const uint32_t stride_channel_x,
|
||||
const uint32_t stride_channel_y, const uint32_t stride_channel_dst, const uint3 sample_ratio,
|
||||
const uint32_t stride_sample_x, const uint32_t stride_sample_y, const uint32_t stride_sample_dst,
|
||||
@@ -463,7 +463,7 @@ static __global__ void mul_mat_vec_q(
|
||||
// 1. Hide latency by prefetching bias and gate here
|
||||
// 2. load only on threads that won't die after partial sum calculation
|
||||
if (threadIdx.x < rows_per_cuda_block && threadIdx.y == 0 &&
|
||||
(rows_per_cuda_block == 1 || uint32_t(row0 + threadIdx.x) < stride_col_dst)) {
|
||||
uint32_t(row0 + threadIdx.x) < nrows_x) {
|
||||
#pragma unroll
|
||||
for (int j = 0; j < ncols_dst; ++j) {
|
||||
x_biases[j] = x_bias[j * stride_col_dst + threadIdx.x];
|
||||
@@ -473,7 +473,7 @@ static __global__ void mul_mat_vec_q(
|
||||
if (use_gate_bias) {
|
||||
gate_bias = gate_bias + sample_dst*stride_sample_dst + channel_bias*stride_channel_dst + row0;
|
||||
if (threadIdx.x < rows_per_cuda_block && threadIdx.y == 0 &&
|
||||
(rows_per_cuda_block == 1 || uint32_t(row0 + threadIdx.x) < stride_col_dst)) {
|
||||
uint32_t(row0 + threadIdx.x) < nrows_x) {
|
||||
#pragma unroll
|
||||
for (int j = 0; j < ncols_dst; ++j) {
|
||||
gate_biases[j] = gate_bias[j * stride_col_dst + threadIdx.x];
|
||||
@@ -499,6 +499,8 @@ static __global__ void mul_mat_vec_q(
|
||||
for (int j = 0; j < ncols_dst; ++j) {
|
||||
#pragma unroll
|
||||
for (int i = 0; i < rows_per_cuda_block; ++i) {
|
||||
// Routed output strides include other experts, not just rows.
|
||||
if (uint32_t(row0 + i) >= nrows_x) continue;
|
||||
tmp[j][i] += vec_dot_q_cuda(
|
||||
vx, &y[j*stride_col_y + kby], kbx_offset + i*stride_row_x + kbx, kqs);
|
||||
if constexpr (has_fusion) {
|
||||
@@ -562,7 +564,7 @@ static __global__ void mul_mat_vec_q(
|
||||
}
|
||||
}
|
||||
|
||||
if (threadIdx.x < rows_per_cuda_block && (rows_per_cuda_block == 1 || uint32_t(row0 + threadIdx.x) < stride_col_dst)) {
|
||||
if (threadIdx.x < rows_per_cuda_block && uint32_t(row0 + threadIdx.x) < nrows_x) {
|
||||
float result = tmp[j][threadIdx.x];
|
||||
if constexpr (has_fusion) {
|
||||
if (use_bias) {
|
||||
@@ -654,6 +656,7 @@ static __global__ void mul_mat_vec_q_moe(
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < c_rows_per_block; ++i) {
|
||||
if (uint32_t(row0 + i) >= nrows_x) continue;
|
||||
tmp[i] += vec_dot_q_cuda(vx, &y[kby], kbx_offset + i*stride_row_x + kbx, kqs);
|
||||
}
|
||||
}
|
||||
@@ -685,7 +688,7 @@ static std::pair<dim3, dim3> calc_launch_params(
|
||||
template<ggml_type type, int c_ncols_dst, bool small_k = false>
|
||||
static void mul_mat_vec_q_switch_fusion(
|
||||
const void * vx, const void * vy, const int32_t * ids, const ggml_cuda_mm_fusion_args_device fusion, float * dst,
|
||||
const uint32_t ncols_x, const uint3 nchannels_y, const uint32_t stride_row_x, const uint32_t stride_col_y,
|
||||
const uint32_t ncols_x, const uint3 nchannels_y, const uint32_t nrows_x, const uint32_t stride_row_x, const uint32_t stride_col_y,
|
||||
const uint32_t stride_col_dst, const uint3 channel_ratio, const uint32_t stride_channel_x,
|
||||
const uint32_t stride_channel_y, const uint32_t stride_channel_dst, const uint3 sample_ratio,
|
||||
const uint32_t stride_sample_x, const uint32_t stride_sample_y, const uint32_t stride_sample_dst,
|
||||
@@ -696,7 +699,7 @@ static void mul_mat_vec_q_switch_fusion(
|
||||
if constexpr (c_ncols_dst == 1) {
|
||||
if (has_fusion) {
|
||||
mul_mat_vec_q<type, c_ncols_dst, true, small_k><<<block_nums, block_dims, nbytes_shared, stream>>>
|
||||
(vx, vy, ids, fusion, dst, ncols_x, nchannels_y, stride_row_x, stride_col_y, stride_col_dst,
|
||||
(vx, vy, ids, fusion, dst, ncols_x, nchannels_y, nrows_x, stride_row_x, stride_col_y, stride_col_dst,
|
||||
channel_ratio, stride_channel_x, stride_channel_y, stride_channel_dst,
|
||||
sample_ratio, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride);
|
||||
return;
|
||||
@@ -706,7 +709,7 @@ static void mul_mat_vec_q_switch_fusion(
|
||||
GGML_ASSERT(!has_fusion && "fusion only supported for ncols_dst=1");
|
||||
|
||||
mul_mat_vec_q<type, c_ncols_dst, false, small_k><<<block_nums, block_dims, nbytes_shared, stream>>>
|
||||
(vx, vy, ids, fusion, dst, ncols_x, nchannels_y, stride_row_x, stride_col_y, stride_col_dst,
|
||||
(vx, vy, ids, fusion, dst, ncols_x, nchannels_y, nrows_x, stride_row_x, stride_col_y, stride_col_dst,
|
||||
channel_ratio, stride_channel_x, stride_channel_y, stride_channel_dst,
|
||||
sample_ratio, stride_sample_x, stride_sample_y, stride_sample_dst, ids_stride);
|
||||
}
|
||||
@@ -819,7 +822,7 @@ static void mul_mat_vec_q_switch_ncols_dst(
|
||||
std::pair<dim3, dim3> dims = calc_launch_params<type>(c_ncols_dst, nrows_x, nchannels_dst,
|
||||
nsamples_dst, warp_size, table_id, true);
|
||||
mul_mat_vec_q_switch_fusion<type, c_ncols_dst, true>(
|
||||
vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, stride_row_x, stride_col_y, stride_col_dst,
|
||||
vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, nrows_x, stride_row_x, stride_col_y, stride_col_dst,
|
||||
channel_ratio_fd, stride_channel_x, stride_channel_y, stride_channel_dst, sample_ratio_fd,
|
||||
stride_sample_x, stride_sample_y, stride_sample_dst, dims.first, dims.second, 0, ids_stride,
|
||||
stream);
|
||||
@@ -827,7 +830,7 @@ static void mul_mat_vec_q_switch_ncols_dst(
|
||||
std::pair<dim3, dim3> dims = calc_launch_params<type>(c_ncols_dst, nrows_x, nchannels_dst,
|
||||
nsamples_dst, warp_size, table_id);
|
||||
mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(
|
||||
vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, stride_row_x, stride_col_y, stride_col_dst,
|
||||
vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, nrows_x, stride_row_x, stride_col_y, stride_col_dst,
|
||||
channel_ratio_fd, stride_channel_x, stride_channel_y, stride_channel_dst, sample_ratio_fd,
|
||||
stride_sample_x, stride_sample_y, stride_sample_dst, dims.first, dims.second, 0, ids_stride,
|
||||
stream);
|
||||
@@ -836,7 +839,7 @@ static void mul_mat_vec_q_switch_ncols_dst(
|
||||
case 2: {
|
||||
constexpr int c_ncols_dst = 2;
|
||||
std::pair<dim3, dim3> dims = calc_launch_params<type>(c_ncols_dst, nrows_x, nchannels_dst, nsamples_dst, warp_size, table_id);
|
||||
mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, stride_row_x, stride_col_y, stride_col_dst,
|
||||
mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, nrows_x, stride_row_x, stride_col_y, stride_col_dst,
|
||||
channel_ratio_fd, stride_channel_x, stride_channel_y, stride_channel_dst,
|
||||
sample_ratio_fd, stride_sample_x, stride_sample_y, stride_sample_dst,
|
||||
dims.first, dims.second, 0, ids_stride, stream);
|
||||
@@ -844,7 +847,7 @@ static void mul_mat_vec_q_switch_ncols_dst(
|
||||
case 3: {
|
||||
constexpr int c_ncols_dst = 3;
|
||||
std::pair<dim3, dim3> dims = calc_launch_params<type>(c_ncols_dst, nrows_x, nchannels_dst, nsamples_dst, warp_size, table_id);
|
||||
mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, stride_row_x, stride_col_y, stride_col_dst,
|
||||
mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, nrows_x, stride_row_x, stride_col_y, stride_col_dst,
|
||||
channel_ratio_fd, stride_channel_x, stride_channel_y, stride_channel_dst,
|
||||
sample_ratio_fd, stride_sample_x, stride_sample_y, stride_sample_dst,
|
||||
dims.first, dims.second, 0, ids_stride, stream);
|
||||
@@ -852,7 +855,7 @@ static void mul_mat_vec_q_switch_ncols_dst(
|
||||
case 4: {
|
||||
constexpr int c_ncols_dst = 4;
|
||||
std::pair<dim3, dim3> dims = calc_launch_params<type>(c_ncols_dst, nrows_x, nchannels_dst, nsamples_dst, warp_size, table_id);
|
||||
mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, stride_row_x, stride_col_y, stride_col_dst,
|
||||
mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, nrows_x, stride_row_x, stride_col_y, stride_col_dst,
|
||||
channel_ratio_fd, stride_channel_x, stride_channel_y, stride_channel_dst,
|
||||
sample_ratio_fd, stride_sample_x, stride_sample_y, stride_sample_dst,
|
||||
dims.first, dims.second, 0, ids_stride, stream);
|
||||
@@ -860,7 +863,7 @@ static void mul_mat_vec_q_switch_ncols_dst(
|
||||
case 5: {
|
||||
constexpr int c_ncols_dst = 5;
|
||||
std::pair<dim3, dim3> dims = calc_launch_params<type>(c_ncols_dst, nrows_x, nchannels_dst, nsamples_dst, warp_size, table_id);
|
||||
mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, stride_row_x, stride_col_y, stride_col_dst,
|
||||
mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, nrows_x, stride_row_x, stride_col_y, stride_col_dst,
|
||||
channel_ratio_fd, stride_channel_x, stride_channel_y, stride_channel_dst,
|
||||
sample_ratio_fd, stride_sample_x, stride_sample_y, stride_sample_dst,
|
||||
dims.first, dims.second, 0, ids_stride, stream);
|
||||
@@ -868,7 +871,7 @@ static void mul_mat_vec_q_switch_ncols_dst(
|
||||
case 6: {
|
||||
constexpr int c_ncols_dst = 6;
|
||||
std::pair<dim3, dim3> dims = calc_launch_params<type>(c_ncols_dst, nrows_x, nchannels_dst, nsamples_dst, warp_size, table_id);
|
||||
mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, stride_row_x, stride_col_y, stride_col_dst,
|
||||
mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, nrows_x, stride_row_x, stride_col_y, stride_col_dst,
|
||||
channel_ratio_fd, stride_channel_x, stride_channel_y, stride_channel_dst,
|
||||
sample_ratio_fd, stride_sample_x, stride_sample_y, stride_sample_dst,
|
||||
dims.first, dims.second, 0, ids_stride, stream);
|
||||
@@ -876,7 +879,7 @@ static void mul_mat_vec_q_switch_ncols_dst(
|
||||
case 7: {
|
||||
constexpr int c_ncols_dst = 7;
|
||||
std::pair<dim3, dim3> dims = calc_launch_params<type>(c_ncols_dst, nrows_x, nchannels_dst, nsamples_dst, warp_size, table_id);
|
||||
mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, stride_row_x, stride_col_y, stride_col_dst,
|
||||
mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, nrows_x, stride_row_x, stride_col_y, stride_col_dst,
|
||||
channel_ratio_fd, stride_channel_x, stride_channel_y, stride_channel_dst,
|
||||
sample_ratio_fd, stride_sample_x, stride_sample_y, stride_sample_dst,
|
||||
dims.first, dims.second, 0, ids_stride, stream);
|
||||
@@ -884,7 +887,7 @@ static void mul_mat_vec_q_switch_ncols_dst(
|
||||
case 8: {
|
||||
constexpr int c_ncols_dst = 8;
|
||||
std::pair<dim3, dim3> dims = calc_launch_params<type>(c_ncols_dst, nrows_x, nchannels_dst, nsamples_dst, warp_size, table_id);
|
||||
mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, stride_row_x, stride_col_y, stride_col_dst,
|
||||
mul_mat_vec_q_switch_fusion<type, c_ncols_dst>(vx, vy, ids, fusion, dst, ncols_x, nchannels_y_fd, nrows_x, stride_row_x, stride_col_y, stride_col_dst,
|
||||
channel_ratio_fd, stride_channel_x, stride_channel_y, stride_channel_dst,
|
||||
sample_ratio_fd, stride_sample_x, stride_sample_y, stride_sample_dst,
|
||||
dims.first, dims.second, 0, ids_stride, stream);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
#include "ds4_mmq_d2r.cuh"
|
||||
|
||||
extern "C" int ds4_cuda_q8_fold_take_q81(
|
||||
const void *src, uint64_t in_dim, const void **q81) {
|
||||
(void)src;
|
||||
(void)in_dim;
|
||||
(void)q81;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The D2R kernels use NVIDIA cp.async and MMA assembly. Disable them in ROCm
|
||||
// builds and use the portable raw/SoA paths instead.
|
||||
bool ds4_mmq_q2_K_moe_d2r_available(int) { return false; }
|
||||
bool ds4_mmq_iq2_xxs_moe_d2r_available(int) { return false; }
|
||||
bool ds4_mmq_q8_0_dense_d2r_available(int) { return false; }
|
||||
|
||||
size_t ds4_mmq_q2_K_moe_d2r_scratch_bytes(int64_t, int) { return 0; }
|
||||
size_t ds4_mmq_iq2_xxs_moe_d2r_pair_scratch_bytes(int64_t, int) { return 0; }
|
||||
size_t ds4_mmq_iq2_xxs_moe_d2r_fused_scratch_bytes(int64_t, int) { return 0; }
|
||||
|
||||
int ds4_mmq_q2_K_moe_d2r_launch(
|
||||
const void *, int64_t, const void *, const int32_t *, const int32_t *,
|
||||
float *, int, int, int64_t, int, void *, size_t, cudaStream_t) { return 1; }
|
||||
|
||||
int ds4_mmq_q8_0_dense_d2r_launch(
|
||||
const void *, const void *, float *, int, int, int, cudaStream_t) { return 1; }
|
||||
|
||||
int ds4_mmq_iq2_xxs_moe_d2r_pair_launch(
|
||||
const void *, const void *, int64_t, const void *, const int32_t *,
|
||||
const int32_t *, float *, float *, int, int, int64_t, int, void *, size_t,
|
||||
cudaStream_t) { return 1; }
|
||||
|
||||
int ds4_mmq_iq2_xxs_moe_d2r_fused_launch(
|
||||
const void *, const void *, int64_t, const void *, const int32_t *, int,
|
||||
const int32_t *, const int32_t *, const float *, void *, int, int, int64_t,
|
||||
int, float, void *, size_t, cudaStream_t) { return 1; }
|
||||
@@ -1,18 +1,9 @@
|
||||
// test_iq2_aligned_entry.cu — M1-Inc1 integration parity gate.
|
||||
//
|
||||
// A/B of the PRODUCTION aligned entry (ds4_mmq_iq2_xxs_aligned_moe_vec, fed
|
||||
// the artifact layout the weight server builds under --repack-iq2-aligned)
|
||||
// against the production raw-layout vec entry (ds4_mmq_iq2_xxs_moe_vec) at
|
||||
// the decode shape. Both entries quantize the activation through
|
||||
// quantize_row_q8_1_cuda, so only the float accumulation order differs.
|
||||
//
|
||||
// The kernel-level A/B with hand-rolled quantize lives in
|
||||
// proto_iq2_aligned.cu (the original +12% proof); this test locks the
|
||||
// integrated entry + artifact layout contract.
|
||||
//
|
||||
// Build (box):
|
||||
// nvcc -O3 --use_fast_math -std=c++17 -arch=sm_121 -I.. \
|
||||
// test_iq2_aligned_entry.cu ../*.o -lcudart -lcuda -o test_iq2_aligned_entry
|
||||
// Production IQ2/Q2 MoE parity, including small verifier batches.
|
||||
// Compare raw and aligned entries, fused gate/up and separate token rows,
|
||||
// and fused down/sum against the sum of independent expert projections.
|
||||
// Both entries use the same Q8_1 activation quantizer.
|
||||
// Build/run: make test-cuda-dspark-moe CUDA_ARCH=sm_121
|
||||
// Use --check-only to skip timing loops under Compute Sanitizer.
|
||||
|
||||
#include "ds4_mmq.h"
|
||||
|
||||
@@ -30,19 +21,79 @@
|
||||
#define CK(x) do { cudaError_t e_ = (x); if (e_ != cudaSuccess) { \
|
||||
printf("CUDA ERR %s @%d: %s\n", #x, __LINE__, cudaGetErrorString(e_)); exit(1); } } while (0)
|
||||
|
||||
static int test_q2_down(cudaStream_t stream, std::mt19937 &rng) {
|
||||
const int rows = 256, columns = 2048, experts = 64;
|
||||
const size_t pairs = (size_t)experts * (rows / 2) * (columns / 256);
|
||||
std::vector<uint8_t> artifact(ds4_mmq_q2_k_aligned_bytes(rows, columns, experts));
|
||||
for (auto &byte : artifact) byte = (uint8_t)rng();
|
||||
for (size_t i = 0; i < pairs * 8; i += 2) {
|
||||
const uint16_t scale = 0x2400;
|
||||
memcpy(artifact.data() + i, &scale, 2);
|
||||
}
|
||||
void *aligned, *raw;
|
||||
CK(cudaMalloc(&aligned, artifact.size()));
|
||||
CK(cudaMalloc(&raw, (size_t)experts * rows * (columns / 256) * 84));
|
||||
CK(cudaMemcpy(aligned, artifact.data(), artifact.size(), cudaMemcpyHostToDevice));
|
||||
if (ds4_mmq_q2_K_aligned_derepack(aligned, raw, rows, columns, experts, stream)) return 1;
|
||||
int bad = 0;
|
||||
for (int tokens = 1; tokens <= 8; tokens++) {
|
||||
const int assignments = tokens * 6;
|
||||
std::vector<float> x((size_t)assignments * columns);
|
||||
for (float &v : x) v = ((int)(rng() % 2000) - 1000) / 500.0f;
|
||||
std::vector<int32_t> ids(assignments);
|
||||
for (int i = 0; i < assignments; i++) ids[i] = (i * 7) % experts;
|
||||
ids.back() = -1;
|
||||
float *dx, *a, *b;
|
||||
int32_t *di;
|
||||
const size_t out_bytes = (size_t)assignments * rows * sizeof(float);
|
||||
CK(cudaMalloc(&dx, x.size() * sizeof(float)));
|
||||
CK(cudaMalloc(&di, ids.size() * sizeof(int32_t)));
|
||||
CK(cudaMalloc(&a, out_bytes));
|
||||
CK(cudaMalloc(&b, out_bytes));
|
||||
CK(cudaMemcpyAsync(dx, x.data(), x.size() * sizeof(float), cudaMemcpyHostToDevice, stream));
|
||||
CK(cudaMemcpyAsync(di, ids.data(), ids.size() * sizeof(int32_t), cudaMemcpyHostToDevice, stream));
|
||||
if (ds4_mmq_q2_K_aligned_moe_vec(aligned, dx, di, a, rows, columns,
|
||||
assignments, experts, 1, stream) ||
|
||||
ds4_mmq_q2_K_moe_vec(raw, dx, di, b, rows, columns,
|
||||
assignments, experts, 1, stream)) return 1;
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
std::vector<float> got(out_bytes / sizeof(float)), ref(got.size());
|
||||
CK(cudaMemcpy(got.data(), a, out_bytes, cudaMemcpyDeviceToHost));
|
||||
CK(cudaMemcpy(ref.data(), b, out_bytes, cudaMemcpyDeviceToHost));
|
||||
if (memcmp(got.data(), ref.data(), out_bytes)) bad++;
|
||||
if (ds4_mmq_q2_K_moe_down_sum6_vec(raw, dx, di, a, rows, columns,
|
||||
tokens, experts, 6, stream)) return 1;
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
CK(cudaMemcpy(got.data(), a, (size_t)tokens * rows * sizeof(float), cudaMemcpyDeviceToHost));
|
||||
for (int t = 0; t < tokens; t++) {
|
||||
for (int row = 0; row < rows; row++) {
|
||||
float expected = 0.0f;
|
||||
for (int s = 0; s < 6; s++) expected += ref[((size_t)t * 6 + s) * rows + row];
|
||||
const float actual = got[(size_t)t * rows + row];
|
||||
if (!std::isfinite(actual) || fabsf(actual - expected) > 1e-3f + fabsf(expected) * 2e-5f) bad++;
|
||||
}
|
||||
}
|
||||
CK(cudaFree(dx)); CK(cudaFree(di)); CK(cudaFree(a)); CK(cudaFree(b));
|
||||
}
|
||||
CK(cudaFree(aligned)); CK(cudaFree(raw));
|
||||
printf("Q2 down assignments 6..48: layout/sum failures=%d\n", bad);
|
||||
return bad;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const bool parity_only = argc > 1 && !strcmp(argv[1], "--check-only");
|
||||
const int M = 2048, K = 4096;
|
||||
const int n_experts = 256;
|
||||
const int n_slots = 6;
|
||||
const int n_idsets = 32;
|
||||
const int iters = 2000;
|
||||
const int iters = parity_only ? 1 : 2000;
|
||||
const int nb = K / 256;
|
||||
const long long row_bytes = (long long)nb * 66;
|
||||
const double slot_gb = (double)n_slots * M * row_bytes / 1e9;
|
||||
|
||||
if (ds4_mmq_init(0) != 0) { printf("ds4_mmq_init failed\n"); return 1; }
|
||||
|
||||
std::mt19937 rng(argc > 1 ? (uint32_t)atoi(argv[1]) : 1234u);
|
||||
std::mt19937 rng(argc > 1 && !parity_only ? (uint32_t)atoi(argv[1]) : 1234u);
|
||||
|
||||
// Random bytes are valid IQ2_XXS content; keep block scales sane.
|
||||
std::vector<uint8_t> W((size_t)n_experts * M * row_bytes);
|
||||
@@ -114,7 +165,7 @@ int main(int argc, char **argv) {
|
||||
return 1;
|
||||
}
|
||||
CK(cudaEventRecord(e0, stream));
|
||||
const int dr_iters = 50;
|
||||
const int dr_iters = parity_only ? 1 : 50;
|
||||
for (int i = 0; i < dr_iters; i++)
|
||||
(void)ds4_mmq_iq2_xxs_aligned_derepack(dArt, dRaw2, M, K, n_experts, stream);
|
||||
CK(cudaEventRecord(e1, stream));
|
||||
@@ -221,6 +272,7 @@ int main(int argc, char **argv) {
|
||||
double max_rel = 0.0, max_abs = 0.0; int bad = 0;
|
||||
for (size_t i = 0; i < ref.size(); i++) {
|
||||
const double a = ref[i], b = got[i];
|
||||
if (!std::isfinite(a) || !std::isfinite(b)) { bad++; continue; }
|
||||
const double ad = fabs(a - b);
|
||||
const double rd = ad / (fabs(a) > 1e-3 ? fabs(a) : 1e-3);
|
||||
if (ad > max_abs) max_abs = ad;
|
||||
@@ -254,23 +306,34 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
int bad_mid = count_bad(em, om, nullptr, nullptr);
|
||||
|
||||
// ---- multi-token parity (batch_eval-hang fix, 2026-07-03): aligned vs
|
||||
// raw production entries at n_tokens=4, flat assignment layout
|
||||
// [tok*n_slots+slot], including one -1 router id (NaN-path guard).
|
||||
int bad4 = 0, bad4_mid = 0;
|
||||
{
|
||||
const int n_tok = 4, n_asg = n_tok * n_slots;
|
||||
// Tiny verifier batches: distinct, shared and partly overlapping experts.
|
||||
// Deduplicating gate/up must be exact against separate one-token calls.
|
||||
// Use different gate/up matrices so swapped operands cannot pass.
|
||||
std::vector<uint8_t> up_art = ART;
|
||||
for (size_t i = dq_bytes; i < up_art.size(); i++) up_art[i] ^= 0x35;
|
||||
uint8_t *dRawUp;
|
||||
CK(cudaMalloc(&dRawUp, W.size()));
|
||||
CK(cudaMemcpyAsync(dArt2, up_art.data(), up_art.size(), cudaMemcpyHostToDevice, stream));
|
||||
if (ds4_mmq_iq2_xxs_aligned_derepack(dArt2, dRawUp, M, K, n_experts, stream)) return 1;
|
||||
int bad4 = 0, bad4_mid = 0, bad_dedup = test_q2_down(stream, rng);
|
||||
for (int n_tok = 1; n_tok <= 8; n_tok++) {
|
||||
for (int overlap = 0; overlap < 3; overlap++) {
|
||||
const int n_asg = n_tok * n_slots;
|
||||
std::vector<float> X4((size_t)n_tok * K);
|
||||
for (auto &v : X4) v = ((float)(rng() % 2000) - 1000.0f) / 500.0f;
|
||||
std::vector<int32_t> ids4(n_asg);
|
||||
for (int a = 0; a < n_asg; a++) ids4[a] = (a * 7 + 3) % n_experts;
|
||||
ids4[13] = -1; // router NaN path: both entries must write a clean 0 row
|
||||
for (int a = 0; a < n_asg; a++) {
|
||||
ids4[a] = overlap == 0 ? (a * 7 + 3) % n_experts :
|
||||
(a % n_slots + (overlap == 2 ? 2 * (a / n_slots) : 0)) % n_experts;
|
||||
}
|
||||
if (n_asg > 13) ids4[13] = -1;
|
||||
std::vector<float> w4(n_asg);
|
||||
for (int a = 0; a < n_asg; a++) w4[a] = 0.25f + 0.05f * a;
|
||||
float *dX4, *dOut4B, *dOut4A, *dMid4, *dW4; int32_t *dIds4;
|
||||
float *dX4, *dOut4B, *dOut4A, *dUp4B, *dMid4, *dW4; int32_t *dIds4;
|
||||
CK(cudaMalloc(&dX4, sizeof(float) * X4.size()));
|
||||
CK(cudaMalloc(&dOut4B, sizeof(float) * n_asg * M));
|
||||
CK(cudaMalloc(&dOut4A, sizeof(float) * n_asg * M));
|
||||
CK(cudaMalloc(&dUp4B, sizeof(float) * n_asg * M));
|
||||
CK(cudaMalloc(&dMid4, sizeof(float) * n_asg * M));
|
||||
CK(cudaMalloc(&dW4, sizeof(float) * n_asg));
|
||||
CK(cudaMalloc(&dIds4, sizeof(int32_t) * n_asg));
|
||||
@@ -281,8 +344,10 @@ int main(int argc, char **argv) {
|
||||
int r2 = ds4_mmq_iq2_xxs_aligned_moe_vec(dArt, dX4, dIds4, dOut4A, M, K, n_tok, n_experts, n_slots, stream);
|
||||
int r3 = ds4_mmq_iq2_xxs_aligned_moe_gate_up_mid_vec(dArt, dArt2, dX4, dIds4, dW4, dMid4,
|
||||
M, K, n_tok, n_experts, n_slots, clampv, stream);
|
||||
if (r1 != 0 || r2 != 0 || r3 != 0) {
|
||||
printf("multi-token entries rc=%d/%d/%d\n", r1, r2, r3);
|
||||
int r4 = ds4_mmq_iq2_xxs_moe_vec(dRawUp, dX4, dIds4, dUp4B,
|
||||
M, K, n_tok, n_experts, n_slots, stream);
|
||||
if (r1 != 0 || r2 != 0 || r3 != 0 || r4 != 0) {
|
||||
printf("multi-token entries rc=%d/%d/%d/%d\n", r1, r2, r3, r4);
|
||||
return 1;
|
||||
}
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
@@ -290,11 +355,24 @@ int main(int argc, char **argv) {
|
||||
CK(cudaMemcpy(ob4.data(), dOut4B, sizeof(float) * ob4.size(), cudaMemcpyDeviceToHost));
|
||||
CK(cudaMemcpy(oa4.data(), dOut4A, sizeof(float) * oa4.size(), cudaMemcpyDeviceToHost));
|
||||
CK(cudaMemcpy(om4.data(), dMid4, sizeof(float) * om4.size(), cudaMemcpyDeviceToHost));
|
||||
bad4 = count_bad(ob4, oa4, nullptr, nullptr);
|
||||
std::vector<float> up4(om4.size());
|
||||
CK(cudaMemcpy(up4.data(), dUp4B, sizeof(float) * up4.size(), cudaMemcpyDeviceToHost));
|
||||
bad4 += count_bad(ob4, oa4, nullptr, nullptr);
|
||||
for (int t = 0; t < n_tok; t++) {
|
||||
if (ds4_mmq_iq2_xxs_aligned_moe_gate_up_mid_vec(
|
||||
dArt, dArt2, dX4 + t * K, dIds4 + t * n_slots,
|
||||
dW4 + t * n_slots, dMid4 + t * n_slots * M,
|
||||
M, K, 1, n_experts, n_slots, clampv, stream) != 0) return 1;
|
||||
}
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
std::vector<float> serial_mid(om4.size());
|
||||
CK(cudaMemcpy(serial_mid.data(), dMid4, sizeof(float) * serial_mid.size(),
|
||||
cudaMemcpyDeviceToHost));
|
||||
if (memcmp(om4.data(), serial_mid.data(), sizeof(float) * om4.size())) bad_dedup++;
|
||||
std::vector<float> em4((size_t)n_asg * M);
|
||||
for (int a = 0; a < n_asg; a++) {
|
||||
for (int r = 0; r < M; r++) {
|
||||
float g = ob4[(size_t)a * M + r], u = g;
|
||||
float g = ob4[(size_t)a * M + r], u = up4[(size_t)a * M + r];
|
||||
if (!std::isfinite(g)) g = 0.0f;
|
||||
if (!std::isfinite(u)) u = 0.0f;
|
||||
if (clampv > 1.0e-6f) {
|
||||
@@ -306,10 +384,27 @@ int main(int argc, char **argv) {
|
||||
em4[(size_t)a * M + r] = silu * u * w4[a];
|
||||
}
|
||||
}
|
||||
bad4_mid = count_bad(em4, om4, nullptr, nullptr);
|
||||
bad4_mid += count_bad(em4, om4, nullptr, nullptr);
|
||||
if (ds4_mmq_iq2_xxs_moe_gate_up_mid_vec(
|
||||
dW, dRawUp, dX4, dIds4, dW4, dMid4,
|
||||
M, K, n_tok, n_experts, n_slots, clampv, stream)) return 1;
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
CK(cudaMemcpy(om4.data(), dMid4, sizeof(float) * om4.size(), cudaMemcpyDeviceToHost));
|
||||
bad4_mid += count_bad(em4, om4, nullptr, nullptr);
|
||||
for (int t = 0; t < n_tok; t++) {
|
||||
if (ds4_mmq_iq2_xxs_moe_gate_up_mid_vec(
|
||||
dW, dRawUp, dX4 + t * K, dIds4 + t * n_slots,
|
||||
dW4 + t * n_slots, dMid4 + t * n_slots * M,
|
||||
M, K, 1, n_experts, n_slots, clampv, stream)) return 1;
|
||||
}
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
CK(cudaMemcpy(serial_mid.data(), dMid4, sizeof(float) * serial_mid.size(), cudaMemcpyDeviceToHost));
|
||||
if (memcmp(om4.data(), serial_mid.data(), sizeof(float) * om4.size())) bad_dedup++;
|
||||
CK(cudaFree(dX4)); CK(cudaFree(dOut4B)); CK(cudaFree(dOut4A));
|
||||
CK(cudaFree(dMid4)); CK(cudaFree(dW4)); CK(cudaFree(dIds4));
|
||||
CK(cudaFree(dMid4)); CK(cudaFree(dW4)); CK(cudaFree(dIds4)); CK(cudaFree(dUp4B));
|
||||
}
|
||||
}
|
||||
CK(cudaFree(dRawUp));
|
||||
|
||||
printf("TEST_IQ2_ALIGNED_ENTRY M=%d K=%d slots=%d experts=%d iters=%d (weights/iter %.1f MB)\n",
|
||||
M, K, n_slots, n_experts, iters, slot_gb * 1000.0);
|
||||
@@ -322,8 +417,8 @@ int main(int argc, char **argv) {
|
||||
printf(" fused entry : %.4f ms -> %6.1f GB/s (vs 2x aligned %+.1f%%)\n",
|
||||
ms_fused, 2.0 * slot_gb / (ms_fused / 1e3), 100.0 * (2.0f * ms_al / ms_fused - 1.0));
|
||||
printf(" parity: aligned max_rel=%.3e max_abs=%.3e bad=%d | pair bad=%d | fused-mid bad=%d"
|
||||
" | ntok4 bad=%d ntok4-mid bad=%d -> %s\n",
|
||||
max_rel, max_abs, bad, bad_pair, bad_mid, bad4, bad4_mid,
|
||||
(bad == 0 && bad_pair == 0 && bad_mid == 0 && bad4 == 0 && bad4_mid == 0) ? "PASS" : "FAIL");
|
||||
return (bad == 0 && bad_pair == 0 && bad_mid == 0 && bad4 == 0 && bad4_mid == 0) ? 0 : 2;
|
||||
" | tiny-batch bad=%d mid bad=%d dedup-exact bad=%d -> %s\n",
|
||||
max_rel, max_abs, bad, bad_pair, bad_mid, bad4, bad4_mid, bad_dedup,
|
||||
(bad == 0 && bad_pair == 0 && bad_mid == 0 && bad4 == 0 && bad4_mid == 0 && bad_dedup == 0) ? "PASS" : "FAIL");
|
||||
return (bad == 0 && bad_pair == 0 && bad_mid == 0 && bad4 == 0 && bad4_mid == 0 && bad_dedup == 0) ? 0 : 2;
|
||||
}
|
||||
@@ -24,20 +24,28 @@
|
||||
|
||||
// Pull in the block_* struct definitions. We use the CUDA decl/impl mode
|
||||
// so the field paths match what the vendored mmq code uses (anonymous
|
||||
// outer union + named "data" inner struct). cuda_fp16.h is available
|
||||
// because nvcc compiles this TU. Half-precision conversions go via
|
||||
// outer union + named "data" inner struct). Half-precision conversions go via
|
||||
// __half_raw <-> uint16_t bit patterns, which makes the CPU-side
|
||||
// fp16<->float helpers below independent of any host-side fp16 ABI.
|
||||
//
|
||||
// We DON'T use the host IQ2 lookup tables from this mode (they'd be
|
||||
// __device__). iq2_host_tables.h instead provides plain host const
|
||||
// arrays generated directly from ggml-common.h's bit-for-bit contents.
|
||||
#if defined(GGML_USE_HIP)
|
||||
#define GGML_COMMON_DECL_HIP
|
||||
#define GGML_COMMON_IMPL_HIP
|
||||
#else
|
||||
#define GGML_COMMON_DECL_CUDA
|
||||
#define GGML_COMMON_IMPL_CUDA
|
||||
#endif
|
||||
#include "../ggml-common.h"
|
||||
|
||||
#if defined(GGML_USE_HIP)
|
||||
#include "vendors/hip.h"
|
||||
#else
|
||||
#include <cuda_runtime.h>
|
||||
#include <cuda_fp16.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
@@ -719,7 +727,9 @@ bool run_moe_pair_generic(
|
||||
const int32_t *, float *, float *,
|
||||
int, int, int, int, int, cudaStream_t),
|
||||
int (*single_entry)(const void *, const float *, const int32_t *,
|
||||
float *, int, int, int, int, int, cudaStream_t)) {
|
||||
float *, int, int, int, int, int, cudaStream_t),
|
||||
bool default_stream = false,
|
||||
int repeats = 1) {
|
||||
fprintf(stderr, "=== %s/PAIR M=%d K=%d ntok=%d nexp=%d nused=%d seed=%u ===\n",
|
||||
tag, M, K, n_tokens, n_experts, n_expert_used, seed);
|
||||
|
||||
@@ -752,8 +762,10 @@ bool run_moe_pair_generic(
|
||||
|
||||
const int64_t ne_get_rows = (int64_t)n_tokens * n_expert_used;
|
||||
const size_t out_count = (size_t)M * ne_get_rows;
|
||||
const size_t pair_count = out_count * (size_t)repeats;
|
||||
|
||||
cudaStream_t stream; cudaStreamCreate(&stream);
|
||||
cudaStream_t stream = nullptr;
|
||||
if (!default_stream) cudaStreamCreate(&stream);
|
||||
void * dWa = nullptr; void * dWb = nullptr;
|
||||
float * dX = nullptr; int32_t * dIds = nullptr;
|
||||
float * dYa_single = nullptr; float * dYb_single = nullptr;
|
||||
@@ -764,45 +776,69 @@ bool run_moe_pair_generic(
|
||||
cudaMalloc(&dIds, ids.size() * sizeof(int32_t));
|
||||
cudaMalloc(&dYa_single, out_count * sizeof(float));
|
||||
cudaMalloc(&dYb_single, out_count * sizeof(float));
|
||||
cudaMalloc(&dYa_pair, out_count * sizeof(float));
|
||||
cudaMalloc(&dYb_pair, out_count * sizeof(float));
|
||||
cudaMalloc(&dYa_pair, pair_count * sizeof(float));
|
||||
cudaMalloc(&dYb_pair, pair_count * sizeof(float));
|
||||
cudaMemcpyAsync(dWa, W_a.data(), W_a.size() * sizeof(BlockT), cudaMemcpyHostToDevice, stream);
|
||||
cudaMemcpyAsync(dWb, W_b.data(), W_b.size() * sizeof(BlockT), cudaMemcpyHostToDevice, stream);
|
||||
cudaMemcpyAsync(dX, X.data(), X.size() * sizeof(float), cudaMemcpyHostToDevice, stream);
|
||||
cudaMemcpyAsync(dIds, ids.data(), ids.size() * sizeof(int32_t), cudaMemcpyHostToDevice, stream);
|
||||
cudaMemsetAsync(dYa_single, 0, out_count * sizeof(float), stream);
|
||||
cudaMemsetAsync(dYb_single, 0, out_count * sizeof(float), stream);
|
||||
cudaMemsetAsync(dYa_pair, 0, out_count * sizeof(float), stream);
|
||||
cudaMemsetAsync(dYb_pair, 0, out_count * sizeof(float), stream);
|
||||
cudaMemsetAsync(dYa_pair, 0, pair_count * sizeof(float), stream);
|
||||
cudaMemsetAsync(dYb_pair, 0, pair_count * sizeof(float), stream);
|
||||
|
||||
int rc_sa = single_entry(dWa, dX, dIds, dYa_single, M, K, n_tokens, n_experts, n_expert_used, stream);
|
||||
int rc_sb = single_entry(dWb, dX, dIds, dYb_single, M, K, n_tokens, n_experts, n_expert_used, stream);
|
||||
int rc_p = pair_entry (dWa, dWb, dX, dIds, dYa_pair, dYb_pair,
|
||||
M, K, n_tokens, n_experts, n_expert_used, stream);
|
||||
int rc_p = 0;
|
||||
for (int repeat = 0; repeat < repeats && rc_p == 0; repeat++) {
|
||||
rc_p = pair_entry(
|
||||
dWa, dWb, dX, dIds,
|
||||
dYa_pair + (size_t)repeat * out_count,
|
||||
dYb_pair + (size_t)repeat * out_count,
|
||||
M, K, n_tokens, n_experts, n_expert_used, stream);
|
||||
}
|
||||
if (rc_sa != 0 || rc_sb != 0 || rc_p != 0) {
|
||||
fprintf(stderr, "%s pair entry: rc_sa=%d rc_sb=%d rc_p=%d\n", tag, rc_sa, rc_sb, rc_p);
|
||||
cudaFree(dWa); cudaFree(dWb); cudaFree(dX); cudaFree(dIds);
|
||||
cudaFree(dYa_single); cudaFree(dYb_single); cudaFree(dYa_pair); cudaFree(dYb_pair);
|
||||
cudaStreamDestroy(stream);
|
||||
if (stream) cudaStreamDestroy(stream);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<float> ya_single(out_count, 0.0f), yb_single(out_count, 0.0f);
|
||||
std::vector<float> ya_pair (out_count, 0.0f), yb_pair (out_count, 0.0f);
|
||||
std::vector<float> ya_pair(pair_count, 0.0f), yb_pair(pair_count, 0.0f);
|
||||
cudaMemcpyAsync(ya_single.data(), dYa_single, out_count * sizeof(float), cudaMemcpyDeviceToHost, stream);
|
||||
cudaMemcpyAsync(yb_single.data(), dYb_single, out_count * sizeof(float), cudaMemcpyDeviceToHost, stream);
|
||||
cudaMemcpyAsync(ya_pair.data(), dYa_pair, out_count * sizeof(float), cudaMemcpyDeviceToHost, stream);
|
||||
cudaMemcpyAsync(yb_pair.data(), dYb_pair, out_count * sizeof(float), cudaMemcpyDeviceToHost, stream);
|
||||
cudaMemcpyAsync(ya_pair.data(), dYa_pair, pair_count * sizeof(float), cudaMemcpyDeviceToHost, stream);
|
||||
cudaMemcpyAsync(yb_pair.data(), dYb_pair, pair_count * sizeof(float), cudaMemcpyDeviceToHost, stream);
|
||||
cudaStreamSynchronize(stream);
|
||||
cudaFree(dWa); cudaFree(dWb); cudaFree(dX); cudaFree(dIds);
|
||||
cudaFree(dYa_single); cudaFree(dYb_single); cudaFree(dYa_pair); cudaFree(dYb_pair);
|
||||
cudaStreamDestroy(stream);
|
||||
if (stream) cudaStreamDestroy(stream);
|
||||
|
||||
// Both pair outputs should be bit-identical to their single counterparts -
|
||||
// same kernel, same Q8_1 buffer. Allow exactly zero abs/rel tolerance.
|
||||
const bool ok_a = check_close(ya_pair, ya_single, 0.0f, 0.0f);
|
||||
const bool ok_b = check_close(yb_pair, yb_single, 0.0f, 0.0f);
|
||||
const bool ok = ok_a && ok_b;
|
||||
// The pair path can choose a tighter tile width than two separate calls,
|
||||
// which changes accumulation order slightly. Repeated pair calls must
|
||||
// still be bit-identical to the first pair result.
|
||||
#if defined(GGML_USE_HIP)
|
||||
constexpr float pair_atol = 2.0e-4f;
|
||||
constexpr float pair_rtol = 1.0e-5f;
|
||||
#else
|
||||
constexpr float pair_atol = 0.0f;
|
||||
constexpr float pair_rtol = 0.0f;
|
||||
#endif
|
||||
const std::vector<float> ya_first(ya_pair.begin(), ya_pair.begin() + out_count);
|
||||
const std::vector<float> yb_first(yb_pair.begin(), yb_pair.begin() + out_count);
|
||||
bool ok_a = check_close(ya_first, ya_single, pair_atol, pair_rtol);
|
||||
bool ok_b = check_close(yb_first, yb_single, pair_atol, pair_rtol);
|
||||
for (int repeat = 1; repeat < repeats; repeat++) {
|
||||
const auto a_first = ya_pair.begin() + (size_t)repeat * out_count;
|
||||
const auto b_first = yb_pair.begin() + (size_t)repeat * out_count;
|
||||
const std::vector<float> ya(a_first, a_first + out_count);
|
||||
const std::vector<float> yb(b_first, b_first + out_count);
|
||||
ok_a &= check_close(ya, ya_first, 0.0f, 0.0f);
|
||||
ok_b &= check_close(yb, yb_first, 0.0f, 0.0f);
|
||||
}
|
||||
const bool ok = ok_a && ok_b;
|
||||
fprintf(stderr, "%s\n\n", ok ? "PASS" : "FAIL");
|
||||
return ok;
|
||||
}
|
||||
@@ -1197,6 +1233,15 @@ int main(int argc, char ** argv) {
|
||||
"IQ2_XXS", QK_K_LOCAL, /*M=*/256, /*K=*/512, /*nt=*/8,
|
||||
/*ne=*/16, /*nu=*/6, 0xC0FE10, gen_iq2,
|
||||
ds4_mmq_iq2_xxs_moe_pair, ds4_mmq_iq2_xxs_moe);
|
||||
// Production admission geometry. This uses the default stream so the
|
||||
// persistent routing maps and tight expert-column bound are exercised
|
||||
// together.
|
||||
all_ok &= run_moe_pair_generic<block_iq2_xxs>(
|
||||
"IQ2_XXS/PRODUCTION_ADMISSION", QK_K_LOCAL,
|
||||
/*M=*/256, /*K=*/512, /*nt=*/371, /*ne=*/256, /*nu=*/6,
|
||||
0xC0FE11, gen_iq2,
|
||||
ds4_mmq_iq2_xxs_moe_pair, ds4_mmq_iq2_xxs_moe,
|
||||
/*default_stream=*/true, /*repeats=*/32);
|
||||
all_ok &= run_moe_pair_generic<block_q4_K>(
|
||||
"Q4_K", QK_K_LOCAL, /*M=*/256, /*K=*/512, /*nt=*/8,
|
||||
/*ne=*/16, /*nu=*/6, 0xC4FE10, gen_q4k,
|
||||
|
||||
@@ -30,7 +30,11 @@
|
||||
|
||||
#include "ds4_mmq.h"
|
||||
|
||||
#if defined(GGML_USE_HIP)
|
||||
#include "vendors/hip.h"
|
||||
#else
|
||||
#include <cuda_runtime.h>
|
||||
#endif
|
||||
|
||||
// libds4mmq.a references this ds4_cuda.cu symbol from the q8-fold vec paths
|
||||
// (C3 Inc4); the entries under test never reach it, so a "no fold available"
|
||||
@@ -123,9 +127,9 @@ float time_ms(F &&fn, int reps) {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Q2_K down leg.
|
||||
// ---------------------------------------------------------------------------
|
||||
void run_q2k_leg(std::mt19937 &rng, int reps) {
|
||||
void run_q2k_leg(std::mt19937 &rng, int reps, int prompt_tokens) {
|
||||
const int E = 256, M = 4096, K = 2048;
|
||||
const int n_tokens = 4096 * 6; // assignments, n_expert_used == 1
|
||||
const int n_tokens = prompt_tokens * 6; // assignments, n_expert_used == 1
|
||||
const uint64_t nb_row = (uint64_t)K / 256;
|
||||
const uint64_t nblk = (uint64_t)E * M * nb_row;
|
||||
const uint64_t npair = nblk / 2;
|
||||
@@ -192,9 +196,9 @@ void run_q2k_leg(std::mt19937 &rng, int reps) {
|
||||
// ---------------------------------------------------------------------------
|
||||
// IQ2_XXS gate/up pair leg.
|
||||
// ---------------------------------------------------------------------------
|
||||
void run_iq2_leg(std::mt19937 &rng, int reps) {
|
||||
void run_iq2_leg(std::mt19937 &rng, int reps, int prompt_tokens) {
|
||||
const int E = 256, M = 2048, K = 4096;
|
||||
const int n_tokens = 4096, n_used = 6;
|
||||
const int n_tokens = prompt_tokens, n_used = 6;
|
||||
const uint64_t nb_row = (uint64_t)K / 256;
|
||||
const uint64_t nblk = (uint64_t)E * M * nb_row;
|
||||
const uint64_t soa_bytes = ds4_mmq_iq2_xxs_aligned_bytes(M, K, E);
|
||||
@@ -277,9 +281,10 @@ void run_iq2_leg(std::mt19937 &rng, int reps) {
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const int reps = (argc > 1) ? atoi(argv[1]) : 20;
|
||||
const int prompt_tokens = (argc > 2) ? atoi(argv[2]) : 4096;
|
||||
std::mt19937 rng(0x5A0Au);
|
||||
run_q2k_leg(rng, reps);
|
||||
run_iq2_leg(rng, reps);
|
||||
run_q2k_leg(rng, reps, prompt_tokens);
|
||||
run_iq2_leg(rng, reps, prompt_tokens);
|
||||
printf(g_failures ? "SOA-TILES FAIL (%d)\n" : "SOA-TILES PASS\n", g_failures);
|
||||
return g_failures ? 1 : 0;
|
||||
}
|
||||
Vendored
+308
@@ -0,0 +1,308 @@
|
||||
#pragma once
|
||||
|
||||
#define HIP_DISABLE_WARP_SYNC_BUILTINS 1
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <hipblas/hipblas.h>
|
||||
#include <hip/hip_fp16.h>
|
||||
#include <hip/hip_bf16.h>
|
||||
|
||||
#ifdef GGML_USE_NCCL
|
||||
#include <rccl/rccl.h>
|
||||
#endif // GGML_USE_NCCL
|
||||
|
||||
|
||||
#define CUBLAS_GEMM_DEFAULT HIPBLAS_GEMM_DEFAULT
|
||||
#define CUBLAS_GEMM_DEFAULT_TENSOR_OP HIPBLAS_GEMM_DEFAULT
|
||||
#define CUBLAS_OP_N HIPBLAS_OP_N
|
||||
#define CUBLAS_OP_T HIPBLAS_OP_T
|
||||
#define CUBLAS_STATUS_SUCCESS HIPBLAS_STATUS_SUCCESS
|
||||
#define CUBLAS_TF32_TENSOR_OP_MATH 0
|
||||
#define CUDA_R_16F HIPBLAS_R_16F
|
||||
#define CUDA_R_16BF HIPBLAS_R_16B
|
||||
#define CUDA_R_32F HIPBLAS_R_32F
|
||||
#define CUBLAS_SIDE_RIGHT HIPBLAS_SIDE_RIGHT
|
||||
#define CUBLAS_FILL_MODE_UPPER HIPBLAS_FILL_MODE_UPPER
|
||||
#define CUBLAS_DIAG_NON_UNIT HIPBLAS_DIAG_NON_UNIT
|
||||
#define CU_DEVICE_ATTRIBUTE_VIRTUAL_MEMORY_MANAGEMENT_SUPPORTED hipDeviceAttributeVirtualMemoryManagementSupported
|
||||
#define CU_MEM_ALLOC_GRANULARITY_RECOMMENDED hipMemAllocationGranularityRecommended
|
||||
#define CU_MEM_ALLOCATION_TYPE_PINNED hipMemAllocationTypePinned
|
||||
#define CU_MEM_LOCATION_TYPE_DEVICE hipMemLocationTypeDevice
|
||||
#define CU_MEM_ACCESS_FLAGS_PROT_READWRITE hipMemAccessFlagsProtReadWrite
|
||||
#define CU_CHECK(fn) {hipError_t err = fn; if(err != hipSuccess) { GGML_ABORT("HipVMM Failure: %s\n", hipGetErrorString(err)); }}
|
||||
#define __shfl_sync(mask, var, laneMask, width) __shfl(var, laneMask, width)
|
||||
#define __shfl_up_sync(mask, var, laneMask, width) __shfl_up(var, laneMask, width)
|
||||
#define __shfl_down_sync(mask, var, laneMask, ...) __shfl_down(var, laneMask, ##__VA_ARGS__)
|
||||
#define __shfl_xor_sync(mask, var, laneMask, width) __shfl_xor(var, laneMask, width)
|
||||
#define __all_sync(mask, var) __all(var)
|
||||
#define __any_sync(mask, var) __any(var)
|
||||
#define cublasStrsmBatched hipblasStrsmBatched
|
||||
#define cublasCreate hipblasCreate
|
||||
#define cublasDestroy hipblasDestroy
|
||||
#define cublasGemmEx hipblasGemmEx
|
||||
#define cublasGemmBatchedEx hipblasGemmBatchedEx
|
||||
#define cublasGemmStridedBatchedEx hipblasGemmStridedBatchedEx
|
||||
#define cublasHandle_t hipblasHandle_t
|
||||
#define cublasSetMathMode(handle, mode) CUBLAS_STATUS_SUCCESS
|
||||
#define cublasSetStream hipblasSetStream
|
||||
#define cublasSgemm hipblasSgemm
|
||||
#define cublasSgemmBatched hipblasSgemmBatched
|
||||
#define cublasSgemmStridedBatched hipblasSgemmStridedBatched
|
||||
#define cublasStatus_t hipblasStatus_t
|
||||
#define cublasOperation_t hipblasOperation_t
|
||||
#define cudaDevAttrCooperativeLaunch hipDeviceAttributeCooperativeLaunch
|
||||
#define cudaDeviceCanAccessPeer hipDeviceCanAccessPeer
|
||||
#define cudaDeviceDisablePeerAccess hipDeviceDisablePeerAccess
|
||||
#define cudaDeviceEnablePeerAccess hipDeviceEnablePeerAccess
|
||||
#define cudaDeviceGetAttribute hipDeviceGetAttribute
|
||||
#define cudaDeviceGetPCIBusId hipDeviceGetPCIBusId
|
||||
#define cudaDeviceProp hipDeviceProp_t
|
||||
#define cudaDeviceSynchronize hipDeviceSynchronize
|
||||
#define cudaError_t hipError_t
|
||||
#define cudaErrorMemoryAllocation hipErrorOutOfMemory
|
||||
#define cudaErrorPeerAccessAlreadyEnabled hipErrorPeerAccessAlreadyEnabled
|
||||
#define cudaErrorPeerAccessNotEnabled hipErrorPeerAccessNotEnabled
|
||||
#define cudaEventCreateWithFlags hipEventCreateWithFlags
|
||||
#define cudaEventDisableTiming hipEventDisableTiming
|
||||
#define cudaEventRecord hipEventRecord
|
||||
#define cudaEventSynchronize hipEventSynchronize
|
||||
#define cudaEvent_t hipEvent_t
|
||||
#define cudaEventDestroy hipEventDestroy
|
||||
#define cudaFree hipFree
|
||||
#define cudaFreeAsync hipFreeAsync
|
||||
#define cudaFreeHost hipHostFree
|
||||
#define cudaGetDevice hipGetDevice
|
||||
#define cudaGetDeviceCount hipGetDeviceCount
|
||||
#define cudaGetDeviceProperties hipGetDeviceProperties
|
||||
#define cudaGetErrorString hipGetErrorString
|
||||
#define cudaGetLastError hipGetLastError
|
||||
#define cudaHostRegister hipHostRegister
|
||||
#define cudaHostRegisterPortable hipHostRegisterPortable
|
||||
#define cudaHostRegisterReadOnly hipHostRegisterReadOnly
|
||||
#define cudaHostUnregister hipHostUnregister
|
||||
#define cudaLaunchCooperativeKernel hipLaunchCooperativeKernel
|
||||
#define cudaLaunchHostFunc hipLaunchHostFunc
|
||||
#define cudaMalloc hipMalloc
|
||||
#define cudaMallocAsync hipMallocAsync
|
||||
#define cudaMallocHost(ptr, size) hipHostMalloc(ptr, size, hipHostMallocDefault)
|
||||
#define cudaMallocManaged hipMallocManaged
|
||||
#define cudaMemAdvise hipMemAdvise
|
||||
#define cudaMemcpy hipMemcpy
|
||||
#define cudaMemcpyAsync hipMemcpyAsync
|
||||
#define cudaMemcpyPeerAsync hipMemcpyPeerAsync
|
||||
#define cudaMemcpy2DAsync hipMemcpy2DAsync
|
||||
#define cudaMemcpyDeviceToDevice hipMemcpyDeviceToDevice
|
||||
#define cudaMemcpyDeviceToHost hipMemcpyDeviceToHost
|
||||
#define cudaMemcpyHostToDevice hipMemcpyHostToDevice
|
||||
#define cudaMemcpyKind hipMemcpyKind
|
||||
#define cudaMemset hipMemset
|
||||
#define cudaMemsetAsync hipMemsetAsync
|
||||
#define cudaMemGetInfo hipMemGetInfo
|
||||
#define cudaOccupancyMaxPotentialBlockSize hipOccupancyMaxPotentialBlockSize
|
||||
#define cudaSetDevice hipSetDevice
|
||||
#define cuDeviceGet hipDeviceGet
|
||||
#define CUdevice hipDevice_t
|
||||
#define CUdeviceptr hipDeviceptr_t
|
||||
#define cuMemUnmap hipMemUnmap
|
||||
#define CUmemAccessDesc hipMemAccessDesc
|
||||
#define cuMemAddressFree hipMemAddressFree
|
||||
#define cuMemRelease hipMemRelease
|
||||
#define CUmemGenericAllocationHandle hipMemGenericAllocationHandle_t
|
||||
#define cuMemCreate hipMemCreate
|
||||
#define cuMemAddressReserve hipMemAddressReserve
|
||||
#define cuMemMap hipMemMap
|
||||
#define cuMemSetAccess hipMemSetAccess
|
||||
#define cuMemGetAllocationGranularity hipMemGetAllocationGranularity
|
||||
#define CUmemAllocationProp hipMemAllocationProp
|
||||
#define cuDeviceGetAttribute hipDeviceGetAttribute
|
||||
#define cudaStreamCreateWithFlags hipStreamCreateWithFlags
|
||||
#define cudaStreamCreate hipStreamCreate
|
||||
#define cudaStreamDestroy hipStreamDestroy
|
||||
#define cudaStreamFireAndForget hipStreamFireAndForget
|
||||
#define cudaStreamNonBlocking hipStreamNonBlocking
|
||||
#define cudaStreamPerThread hipStreamPerThread
|
||||
#define cudaStreamSynchronize hipStreamSynchronize
|
||||
#define cudaStreamCaptureStatus hipStreamCaptureStatus
|
||||
#define cudaStreamCaptureStatusNone hipStreamCaptureStatusNone
|
||||
#define cudaStreamIsCapturing hipStreamIsCapturing
|
||||
#define cudaStreamWaitEvent hipStreamWaitEvent
|
||||
#define cudaGraphExec_t hipGraphExec_t
|
||||
#define cudaGraphNode_t hipGraphNode_t
|
||||
#define cudaKernelNodeParams hipKernelNodeParams
|
||||
#define cudaKernelNodeParams hipKernelNodeParams
|
||||
#define cudaGraphExecDestroy hipGraphExecDestroy
|
||||
#define cudaGraphLaunch hipGraphLaunch
|
||||
#define cudaErrorGraphExecUpdateFailure hipErrorGraphExecUpdateFailure
|
||||
#define cudaGraphExecUpdateResult hipGraphExecUpdateResult
|
||||
#define cudaGraphNodeType hipGraphNodeType
|
||||
#define cudaGraphNodeTypeKernel hipGraphNodeTypeKernel
|
||||
#define cudaGraphInstantiate hipGraphInstantiate
|
||||
#define cudaStreamEndCapture hipStreamEndCapture
|
||||
#define cudaGraphDestroy hipGraphDestroy
|
||||
#define cudaGraphKernelNodeSetParams hipGraphKernelNodeSetParams
|
||||
#define cudaErrorInvalidDeviceFunction hipErrorInvalidDeviceFunction
|
||||
#define cudaGraphKernelNodeGetParams hipGraphKernelNodeGetParams
|
||||
#define cudaGraphNodeGetType hipGraphNodeGetType
|
||||
#define cudaGraphGetNodes hipGraphGetNodes
|
||||
#define cudaGraphExecUpdate hipGraphExecUpdate
|
||||
#define cudaStreamCaptureModeRelaxed hipStreamCaptureModeRelaxed
|
||||
#define cudaStreamBeginCapture hipStreamBeginCapture
|
||||
#define cudaGraph_t hipGraph_t
|
||||
#define cudaStream_t hipStream_t
|
||||
#define cudaSuccess hipSuccess
|
||||
#define cudaOccupancyMaxActiveBlocksPerMultiprocessor hipOccupancyMaxActiveBlocksPerMultiprocessor
|
||||
#define cudaFuncSetAttribute hipFuncSetAttribute
|
||||
#define cudaFuncAttributeMaxDynamicSharedMemorySize hipFuncAttributeMaxDynamicSharedMemorySize
|
||||
#define __trap() do { abort(); __builtin_unreachable(); } while(0)
|
||||
#define CUBLAS_STATUS_SUCCESS HIPBLAS_STATUS_SUCCESS
|
||||
#define CUBLAS_STATUS_NOT_INITIALIZED HIPBLAS_STATUS_NOT_INITIALIZED
|
||||
#define CUBLAS_STATUS_ALLOC_FAILED HIPBLAS_STATUS_ALLOC_FAILED
|
||||
#define CUBLAS_STATUS_INVALID_VALUE HIPBLAS_STATUS_INVALID_VALUE
|
||||
#define CUBLAS_STATUS_ARCH_MISMATCH HIPBLAS_STATUS_ARCH_MISMATCH
|
||||
#define CUBLAS_STATUS_MAPPING_ERROR HIPBLAS_STATUS_MAPPING_ERROR
|
||||
#define CUBLAS_STATUS_EXECUTION_FAILED HIPBLAS_STATUS_EXECUTION_FAILED
|
||||
#define CUBLAS_STATUS_INTERNAL_ERROR HIPBLAS_STATUS_INTERNAL_ERROR
|
||||
#define CUBLAS_STATUS_NOT_SUPPORTED HIPBLAS_STATUS_NOT_SUPPORTED
|
||||
|
||||
#if HIP_VERSION >= 60500000
|
||||
#define CUBLAS_COMPUTE_16F HIPBLAS_COMPUTE_16F
|
||||
#define CUBLAS_COMPUTE_32F HIPBLAS_COMPUTE_32F
|
||||
#define CUBLAS_COMPUTE_32F_FAST_16F HIPBLAS_COMPUTE_32F_FAST_16F
|
||||
#define cublasComputeType_t hipblasComputeType_t
|
||||
#define cudaDataType_t hipDataType
|
||||
#else
|
||||
#define CUBLAS_COMPUTE_16F HIPBLAS_R_16F
|
||||
#define CUBLAS_COMPUTE_32F HIPBLAS_R_32F
|
||||
#define CUBLAS_COMPUTE_32F_FAST_16F HIPBLAS_R_32F
|
||||
#define cublasComputeType_t hipblasDatatype_t
|
||||
#define cudaDataType_t hipblasDatatype_t
|
||||
#endif // HIP_VERSION >= 6050000
|
||||
|
||||
#if !defined(__HIP_PLATFORM_AMD__)
|
||||
#error "The HIP backend supports only AMD targets"
|
||||
#endif // !defined(__HIP_PLATFORM_AMD__)
|
||||
|
||||
#define __CUDA_ARCH__ 1300
|
||||
|
||||
#if defined(__gfx900__) || defined(__gfx906__)
|
||||
#define GCN5
|
||||
#endif // defined(__gfx900__) || defined(__gfx906__)
|
||||
|
||||
#if defined(__gfx803__)
|
||||
#define GCN4
|
||||
#endif // defined(__gfx803__)
|
||||
|
||||
#if defined(GCN5) || defined(GCN4)
|
||||
#define GCN
|
||||
#endif // defined(GCN5) || defined(GCN4)
|
||||
|
||||
#if defined(__gfx950__)
|
||||
#define CDNA4
|
||||
#endif // defined(__gfx950__)
|
||||
|
||||
#if defined(__gfx942__)
|
||||
#define CDNA3
|
||||
#endif // defined(__gfx942__)
|
||||
|
||||
#if defined(__gfx90a__)
|
||||
#define CDNA2
|
||||
#endif // defined(__gfx90a__)
|
||||
|
||||
#if defined(__gfx908__)
|
||||
#define CDNA1
|
||||
#endif // defined(__gfx908__)
|
||||
|
||||
#if defined(CDNA4) || defined(CDNA3) || defined(CDNA2) || defined(CDNA1)
|
||||
#define CDNA // For the entire family
|
||||
#endif // defined(CDNA4) || defined(CDNA3) || defined(CDNA2) || defined(CDNA1)
|
||||
|
||||
#if defined(__GFX12__)
|
||||
#define RDNA4
|
||||
#endif // defined(__GFX12__)
|
||||
|
||||
#if defined(__GFX11__)
|
||||
#define RDNA3
|
||||
#endif // defined(__GFX11__)
|
||||
|
||||
#if defined(__gfx1150__) || defined(__gfx1151__) || defined(__gfx1152__) || defined(__gfx1153__)
|
||||
#define RDNA3_5
|
||||
#endif // defined(__gfx1150__) || defined(__gfx1151__) || defined(__gfx1152__) || defined(__gfx1153__)
|
||||
|
||||
#if defined(RDNA3) && !defined(RDNA3_5)
|
||||
#define RDNA3_0
|
||||
#endif // defined(RDNA3) && !defined(RDNA3_5)
|
||||
|
||||
#if defined(__gfx1030__) || defined(__gfx1031__) || defined(__gfx1032__) || defined(__gfx1033__) || \
|
||||
defined(__gfx1034__) || defined(__gfx1035__) || defined(__gfx1036__) || defined(__gfx1037__)
|
||||
#define RDNA2
|
||||
#endif
|
||||
|
||||
#if defined(__gfx1010__) || defined(__gfx1012__)
|
||||
#define RDNA1
|
||||
#endif // defined(__gfx1010__) || defined(__gfx1012__)
|
||||
|
||||
#if defined(RDNA4) || defined(RDNA3) || defined(RDNA2) || defined(RDNA1)
|
||||
#define RDNA // For the entire family
|
||||
#endif // defined(RDNA4) || defined(RDNA3) || defined(RDNA2) || defined(RDNA1)
|
||||
|
||||
#ifndef __has_builtin
|
||||
#define __has_builtin(x) 0
|
||||
#endif
|
||||
|
||||
typedef __hip_bfloat16 nv_bfloat16;
|
||||
typedef __hip_bfloat162 nv_bfloat162;
|
||||
|
||||
#if HIP_VERSION >= 60200000
|
||||
#include <hip/hip_fp8.h>
|
||||
typedef __hip_fp8_e4m3 __nv_fp8_e4m3;
|
||||
#define FP8_AVAILABLE
|
||||
#endif // HIP_VERSION >= 60200000
|
||||
|
||||
typedef int8_t int8x4_t __attribute__((ext_vector_type(4)));
|
||||
typedef uint8_t uint8x4_t __attribute__((ext_vector_type(4)));
|
||||
static __device__ __forceinline__ int __vsubss4(const int a, const int b) {
|
||||
const int8x4_t va = reinterpret_cast<const int8x4_t&>(a);
|
||||
const int8x4_t vb = reinterpret_cast<const int8x4_t&>(b);
|
||||
#if __has_builtin(__builtin_elementwise_sub_sat)
|
||||
const int8x4_t c = __builtin_elementwise_sub_sat(va, vb);
|
||||
return reinterpret_cast<const int &>(c);
|
||||
#else
|
||||
int8x4_t c;
|
||||
int16_t tmp;
|
||||
#pragma unroll
|
||||
for (int i = 0; i < 4; i++) {
|
||||
tmp = va[i] - vb[i];
|
||||
if(tmp > std::numeric_limits<int8_t>::max()) tmp = std::numeric_limits<int8_t>::max();
|
||||
if(tmp < std::numeric_limits<int8_t>::min()) tmp = std::numeric_limits<int8_t>::min();
|
||||
c[i] = tmp;
|
||||
}
|
||||
return reinterpret_cast<int &>(c);
|
||||
#endif // __has_builtin(__builtin_elementwise_sub_sat)
|
||||
}
|
||||
|
||||
static __device__ __forceinline__ int __vsub4(const int a, const int b) {
|
||||
return __vsubss4(a, b);
|
||||
}
|
||||
|
||||
static __device__ __forceinline__ unsigned int __vcmpeq4(unsigned int a, unsigned int b) {
|
||||
const uint8x4_t& va = reinterpret_cast<const uint8x4_t&>(a);
|
||||
const uint8x4_t& vb = reinterpret_cast<const uint8x4_t&>(b);
|
||||
unsigned int c;
|
||||
uint8x4_t& vc = reinterpret_cast<uint8x4_t&>(c);
|
||||
#pragma unroll
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
vc[i] = va[i] == vb[i] ? 0xff : 0x00;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static __device__ __forceinline__ unsigned int __vcmpne4(unsigned int a, unsigned int b) {
|
||||
const uint8x4_t& va = reinterpret_cast<const uint8x4_t&>(a);
|
||||
const uint8x4_t& vb = reinterpret_cast<const uint8x4_t&>(b);
|
||||
unsigned int c;
|
||||
uint8x4_t& vc = reinterpret_cast<uint8x4_t&>(c);
|
||||
#pragma unroll
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
vc[i] = va[i] == vb[i] ? 0x00 : 0xff;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
@@ -16,6 +16,9 @@ The file shape depends on the model:
|
||||
|
||||
- DeepSeek V4 Flash: `43 x 4096`.
|
||||
- GLM 5.3 Flash: `45 x 4096`. The separate MTP predictor layer is omitted.
|
||||
- Qwen3.8 Flash Next: `48 x 2560`. FFN steering is applied to each
|
||||
hyper-connection branch of the residual; dumps average those branches
|
||||
at the last prompt token.
|
||||
|
||||
GLM 5.2 steering is not implemented.
|
||||
|
||||
@@ -179,3 +182,28 @@ Style control:
|
||||
The method is not a fine-tune. It is a low-rank runtime edit, so it works best
|
||||
for coarse behavior, topic, or style directions that are consistently present in
|
||||
the activation captures.
|
||||
|
||||
## Qwen3.8 Flash Next
|
||||
|
||||
Capture uses `--think` / `--nothink`
|
||||
(not `--think-high`). Dumps track the prompt phase explicitly, including
|
||||
one-token tails, and retain the last prompt token during ordinary and MTP
|
||||
decode. `attn_out` captures the output projection of both GDN and full-attention
|
||||
layers, giving one row for each of the 48 trunk layers:
|
||||
|
||||
```sh
|
||||
python3 dir-steering/tools/build_direction.py \
|
||||
--profile qwen3.8-flash-next \
|
||||
--ds4 ./ds4 \
|
||||
--model gguf/Qwen3.8-Flash-Next-Q4.gguf \
|
||||
--good-file /path/to/target-prompts.txt \
|
||||
--bad-file /path/to/control-prompts.txt \
|
||||
--out dir-steering/out/qwen38-direction.json \
|
||||
--component ffn_out \
|
||||
--ctx 512
|
||||
```
|
||||
|
||||
Qwen steering is Metal-only. `--mtp-model`, SSD streaming, and `--power`
|
||||
remain unsupported for this graph. The bank contains only the 48 trunk layers;
|
||||
the embedded MTP predictor remains unsteered. Its drafts are verified by the
|
||||
steered target trunk, so `--mtp` remains supported.
|
||||
@@ -10,6 +10,8 @@ At runtime ds4 applies:
|
||||
Positive scale suppresses the target direction. Negative scale amplifies it.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import array
|
||||
import json
|
||||
@@ -23,6 +25,7 @@ from pathlib import Path
|
||||
MODEL_PROFILES = {
|
||||
"deepseek-v4-flash": (43, 4096),
|
||||
"glm-5.3-flash": (45, 4096),
|
||||
"qwen3.8-flash-next": (48, 2560),
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +75,7 @@ def run_capture(
|
||||
env["DS4_METAL_GRAPH_DUMP_PREFIX"] = str(dump_prefix)
|
||||
env["DS4_METAL_GRAPH_DUMP_NAME"] = component
|
||||
env["DS4_METAL_GRAPH_DUMP_POS"] = "0"
|
||||
env["DS4_QWEN4_PREFILL_CHUNK"] = str(max(ctx, 1024))
|
||||
|
||||
cmd = [
|
||||
str(ds4),
|
||||
@@ -79,10 +83,11 @@ def run_capture(
|
||||
"--ctx", str(ctx),
|
||||
"--prompt-file", str(prompt_path),
|
||||
"-n", "1",
|
||||
"--prefill-chunk", str(max(ctx, 1024)),
|
||||
]
|
||||
if system:
|
||||
cmd += ["--system", system]
|
||||
cmd.append("--think-high" if think else "--nothink")
|
||||
cmd.append("--think" if think else "--nothink")
|
||||
result = subprocess.run(cmd, cwd=ds4.parent, env=env, check=False,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
||||
if result.returncode != 0:
|
||||
@@ -128,7 +133,7 @@ def main() -> None:
|
||||
ap.add_argument("--system", default="You are a helpful assistant.")
|
||||
ap.add_argument("--component", default="ffn_out",
|
||||
choices=("ffn_out", "attn_out"),
|
||||
help="runtime-editable 4096-wide activation stream")
|
||||
help="runtime-editable activation stream at the profile's embedding width")
|
||||
ap.add_argument("--think", action="store_true",
|
||||
help="capture after <think>; default captures direct answers")
|
||||
ap.add_argument("--pair-normalize", action="store_true",
|
||||
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
# Coding Agent Clients
|
||||
|
||||
[README](../README.md) | [Server](SERVER.md)
|
||||
|
||||
Start the server first:
|
||||
|
||||
```sh
|
||||
./ds4-server --ctx 100000 --kv-disk-dir /tmp/ds4-kv --kv-disk-space-mb 8192
|
||||
```
|
||||
|
||||
Set the client's context limit no higher than the server's. Output tokens also
|
||||
consume that context; a client limit does not enlarge the server allocation.
|
||||
The examples use Flash and localhost. Change the address for a trusted remote
|
||||
server. The `dsv4-local` strings are placeholders, not server authentication.
|
||||
|
||||
## Pi
|
||||
|
||||
Add this provider to `~/.pi/agent/models.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"providers": {
|
||||
"ds4": {
|
||||
"baseUrl": "http://127.0.0.1:8000/v1",
|
||||
"api": "openai-completions",
|
||||
"apiKey": "dsv4-local",
|
||||
"compat": {
|
||||
"supportsStore": false,
|
||||
"supportsDeveloperRole": false,
|
||||
"supportsReasoningEffort": true,
|
||||
"supportsUsageInStreaming": true,
|
||||
"maxTokensField": "max_tokens",
|
||||
"supportsStrictMode": false,
|
||||
"thinkingFormat": "deepseek",
|
||||
"requiresReasoningContentOnAssistantMessages": true
|
||||
},
|
||||
"models": [{
|
||||
"id": "deepseek-v4-flash",
|
||||
"name": "DwarfStar Flash",
|
||||
"reasoning": true,
|
||||
"thinkingLevelMap": {
|
||||
"off": null, "minimal": "low", "low": "low",
|
||||
"medium": "medium", "high": "high", "xhigh": "xhigh"
|
||||
},
|
||||
"input": ["text"],
|
||||
"contextWindow": 100000,
|
||||
"maxTokens": 16384,
|
||||
"cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Select `ds4/deepseek-v4-flash` in Pi. This is a text-only client configuration;
|
||||
image use also requires the appropriate client input declaration and server
|
||||
vision encoder.
|
||||
|
||||
## OpenCode
|
||||
|
||||
Merge the provider into `~/.config/opencode/opencode.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": {
|
||||
"ds4": {
|
||||
"name": "DwarfStar",
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"options": {
|
||||
"baseURL": "http://127.0.0.1:8000/v1",
|
||||
"apiKey": "dsv4-local"
|
||||
},
|
||||
"models": {
|
||||
"deepseek-v4-flash": {
|
||||
"name": "DwarfStar Flash",
|
||||
"limit": {"context": 100000, "output": 16384}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Select `ds4/deepseek-v4-flash` as the model.
|
||||
|
||||
## Codex CLI
|
||||
|
||||
Use the Responses API. Add a provider to the Codex configuration:
|
||||
|
||||
```toml
|
||||
[model_providers.ds4]
|
||||
name = "DwarfStar"
|
||||
base_url = "http://127.0.0.1:8000/v1"
|
||||
wire_api = "responses"
|
||||
stream_idle_timeout_ms = 1000000
|
||||
```
|
||||
|
||||
```sh
|
||||
codex --model deepseek-v4-flash -c model_provider=ds4
|
||||
```
|
||||
|
||||
## Claude Code
|
||||
|
||||
Use the Anthropic-compatible endpoint. A shell wrapper can select the local
|
||||
model for the main agent and its secondary model roles:
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
unset ANTHROPIC_API_KEY
|
||||
export ANTHROPIC_BASE_URL="http://127.0.0.1:8000"
|
||||
export ANTHROPIC_AUTH_TOKEN="dsv4-local"
|
||||
export ANTHROPIC_MODEL="deepseek-v4-flash"
|
||||
export ANTHROPIC_DEFAULT_SONNET_MODEL="deepseek-v4-flash"
|
||||
export ANTHROPIC_DEFAULT_HAIKU_MODEL="deepseek-v4-flash"
|
||||
export ANTHROPIC_DEFAULT_OPUS_MODEL="deepseek-v4-flash"
|
||||
export CLAUDE_CODE_SUBAGENT_MODEL="deepseek-v4-flash"
|
||||
export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
|
||||
export CLAUDE_STREAM_IDLE_TIMEOUT_MS=600000
|
||||
exec claude "$@"
|
||||
```
|
||||
|
||||
Give the wrapper a different name from `claude`. Agent clients can send large
|
||||
initial prompts, so the first prefill may take time. Disk caching helps reuse
|
||||
compatible prefixes on later sessions.
|
||||
@@ -0,0 +1,90 @@
|
||||
# NVIDIA CUDA GPUs
|
||||
|
||||
[README](../README.md) | [Getting started](../README.md#start-here)
|
||||
|
||||
**Ada Lovelace is supported, including 48 GB L40S cards.** This is an important
|
||||
use case: a model whose other inference implementations require newer GPUs
|
||||
can still run here when DwarfStar supports its GGUF layout. The Flash kernels
|
||||
do not require Blackwell's native FP4 instructions; Ada uses an appropriate
|
||||
CUDA path. Support remains model-specific, not a promise to run arbitrary GGUFs.
|
||||
|
||||
## Build
|
||||
|
||||
Install the NVIDIA driver and CUDA toolkit, including `nvcc` and cuBLAS.
|
||||
Use the local GPU architecture, or select Ada explicitly:
|
||||
|
||||
```sh
|
||||
make cuda-generic
|
||||
# For an Ada build:
|
||||
make cuda CUDA_ARCH=sm_89
|
||||
```
|
||||
|
||||
For a GB10 machine use the [DGX Spark](DGX_SPARK.md) target instead.
|
||||
|
||||
## Normal layer placement
|
||||
|
||||
Without tensor parallelism, DwarfStar places complete layers on the selected
|
||||
GPUs. `--gpu-vram auto` uses reported free VRAM, reserving space for the graph
|
||||
and context. Explicit budgets are comma-separated GiB values, one per device.
|
||||
|
||||
```sh
|
||||
./download_model.sh ds4f-q2
|
||||
./ds4 --cuda --gpu-devices 0,1,2,3 --gpu-vram auto --ctx 32768
|
||||
```
|
||||
|
||||
This is also the multi-GPU placement mode for GLM. Startup refuses a layout
|
||||
that would require unsupported CPU execution; reduce context or choose a
|
||||
smaller model if necessary.
|
||||
|
||||
## Flash tensor parallelism
|
||||
|
||||
`--cuda-tensor-parallel` pairs GPUs and divides routed-expert work inside
|
||||
each pair. Pairs also own successive layer ranges. This is an in-process
|
||||
configuration, not the network `--role coordinator` / `--role worker` mode.
|
||||
|
||||
The device order matters. List all layer-home GPUs first, then their partners.
|
||||
For physical pairs `(0,1)`, `(2,3)`, `(4,5)`, `(6,7)`, use:
|
||||
|
||||
```text
|
||||
0,2,4,6,1,3,5,7
|
||||
```
|
||||
|
||||
Check your machine's topology with `nvidia-smi topo -m`; do not assume this
|
||||
ordering gives the best pairs on another server. Each pair stores one half
|
||||
of the routed experts per GPU. Dense attention, routers, and shared experts
|
||||
are replicated within the pair; the output head is vocabulary-sharded.
|
||||
|
||||
For the tested eight-L40S setup:
|
||||
|
||||
```sh
|
||||
./download_model.sh ds4f-q4
|
||||
./ds4-agent --cuda --cuda-tensor-parallel \
|
||||
--gpu-devices 0,2,4,6,1,3,5,7 --gpu-vram auto --ctx 100000
|
||||
```
|
||||
|
||||
Q4 has native grouped routed kernels for multi-user throughput. Q2 needs less
|
||||
memory, but unsupported grouped shapes use a slower, correct fallback.
|
||||
Four 48 GB cards with Q2 and eight with Q4 are tested configurations. An even
|
||||
device count alone does not guarantee memory fit.
|
||||
|
||||
## Serve multiple users
|
||||
|
||||
```sh
|
||||
./ds4-server --cuda --cuda-tensor-parallel \
|
||||
--gpu-devices 0,2,4,6,1,3,5,7 --gpu-vram auto \
|
||||
--ctx 100000 --batched-session 16 --host 0.0.0.0
|
||||
```
|
||||
|
||||
The supported eight-L40S Flash configuration has reached roughly 126 aggregate
|
||||
generation tokens/s with 16 decode rows. This is total throughput, not the
|
||||
speed of one user. The recorded conditions and regression requirements are in
|
||||
[the QA guide](../QA_BEFORE_RELEASES.md).
|
||||
|
||||
CUDA TP defaults to 2048-token prefill chunks. Keep that default for the
|
||||
16-session, 100k-context setup; larger chunks need more scratch memory.
|
||||
Reduce session count or context if all KV states do not fit.
|
||||
|
||||
The repository's [server launcher](../run-nvidia-tp-server.sh) is an example
|
||||
from the L40S deployment, not a portable default: it uses host-specific model
|
||||
and cache paths and defaults to MXFP4. The commands above need no launcher or
|
||||
environment tuning. See [serving](SERVER.md) for disk caches and API access.
|
||||
@@ -0,0 +1,191 @@
|
||||
# DGX Spark
|
||||
|
||||
[README](../README.md) | [Getting started](../README.md#start-here)
|
||||
|
||||
Use the NVIDIA driver and CUDA development
|
||||
toolkit for the machine; the build needs `nvcc` and cuBLAS.
|
||||
Check that `nvidia-smi` sees the GPU before building.
|
||||
|
||||
## Build and run
|
||||
|
||||
```sh
|
||||
make cuda-spark
|
||||
./download_model.sh ds4f-q2
|
||||
./ds4 --cuda
|
||||
```
|
||||
|
||||
The build targets GB10 (`sm_121a`) and enables the Blackwell-specific kernels.
|
||||
Do not use `--cuda-tensor-parallel` on a single Spark.
|
||||
Stop other inference services before loading a model so they do not compete
|
||||
for memory. Restore any services you stopped when finished.
|
||||
|
||||
## DeepSeek V4.1 Flash
|
||||
|
||||
Q2 needs SSD streaming on one 128 GB Spark:
|
||||
|
||||
```sh
|
||||
./download_model.sh ds41f-q2
|
||||
./ds4 --cuda -m gguf/DeepSeek-V4.1-Flash-Q2.gguf \
|
||||
--ssd-streaming --ctx 32768
|
||||
```
|
||||
|
||||
The file is 341 GiB, including 189 GiB of disk-only Engram tables. Keep it on
|
||||
a fast local SSD. The expert cache is sized automatically; leave memory for
|
||||
other programs and the context. Use the same options with `ds4-agent` or
|
||||
`ds4-server`.
|
||||
|
||||
Two Sparks can instead keep half the experts each, using
|
||||
[network tensor parallelism](DISTRIBUTED.md#tensor-parallelism-between-two-sparks).
|
||||
Both need the complete GGUF on their local SSD. Single-host V4.1
|
||||
[DSpark](SPECULATIVE_DECODING.md#deepseek-v41-flash-dspark) also works with SSD
|
||||
streaming; TP speculation and CUDA vision are not supported for V4.1.
|
||||
The [model guide](MODELS.md#deepseek-v41-flash) covers
|
||||
thinking levels and the separate Metal configurations.
|
||||
|
||||
September 13-14, 2026, Q2 SSD with automatic cache sizing: a 3,241-token
|
||||
`/read README.md` prefill reached 93-96 t/s, up from 49-54 t/s, with 32K
|
||||
allocated context. No extra flag is needed. A separate 64 GiB cache-budget
|
||||
run with 64K allocated context reached 384 t/s for a 32K initial prefill and
|
||||
88 t/s for a 3.2K append. These are SSD-streamed V4.1 results, not resident
|
||||
V4 Flash numbers; speed depends on prompt length and the expert cache.
|
||||
The automatic-cache 256-token reply test decoded about 5% slower, but
|
||||
prefill plus the reply fell from about 87 to 59 seconds.
|
||||
See the [QA record](../QA_BEFORE_RELEASES.md#cuda-ssd-streaming) for conditions
|
||||
and longer-context measurements.
|
||||
|
||||
On two Sparks over the direct RoCE link, Q2 measured **21.8 t/s** for one
|
||||
session on October 2, 2026: a 1K prompt, 64K allocated context and 2,048
|
||||
teacher-forced decode tokens, without speculation.
|
||||
|
||||
| Work | Speed |
|
||||
| --- | ---: |
|
||||
| Fresh 32K prefill | 411 t/s |
|
||||
| Add 8K after 32K | 314 t/s |
|
||||
|
||||
Both runs used 64K allocated context and 512 decode tokens per frontier;
|
||||
decode at these longer contexts was about 18 t/s. Engram tables stayed on disk.
|
||||
|
||||
Decode batching starts at five ready sessions; smaller groups run in order.
|
||||
The earlier eight-session reference is about **28 aggregate t/s**, not per
|
||||
client. See the [network TP QA record](../QA_BEFORE_RELEASES.md#cuda-network-tensor-parallelism)
|
||||
for the workloads and memory limits.
|
||||
|
||||
## Flash 0731 on two Sparks
|
||||
|
||||
[Network tensor parallelism](DISTRIBUTED.md#tensor-parallelism-between-two-sparks)
|
||||
also supports Flash 0731 Q2 and MXFP4, including DSpark with the matching
|
||||
support file. Each host holds about 45 GiB of Q2 weights or 77 GiB of MXFP4,
|
||||
plus context and runtime buffers. Download `ds4f-mxfp4` on both for MXFP4.
|
||||
|
||||
October 2, 2026, Q2, direct 200 Gb/s RoCE, 64K allocated context: a 105-token
|
||||
English prose prompt decoded 512 tokens at **26.0 t/s** without speculation.
|
||||
A fresh 32K prefill reached **618 t/s**; appending 8K reached **610 t/s**.
|
||||
Ordinary decode at those longer frontiers was about 19 t/s. These are single
|
||||
measurements, not release medians.
|
||||
|
||||
October 4, 2026, matching 0731 DSpark support, default speculation settings,
|
||||
temperature zero, 64K allocated context and 512 generated tokens. Both formats
|
||||
use the drafter on both ranks over RDMA. These are medians of two runs per case
|
||||
(four for MXFP4 prose), including the first token or block. Benchmark processes
|
||||
ran on the performance cores (`taskset -c 5-9,15-19`):
|
||||
|
||||
| Weights | Prompt | DSpark |
|
||||
| --- | --- | ---: |
|
||||
| Q2 | English prose, 105 tokens | 44.0 t/s |
|
||||
| Q2 | C hash table, 31 tokens | 83.3 t/s |
|
||||
| MXFP4 | English prose, 105 tokens | 41.7 t/s |
|
||||
| MXFP4 | C hash table, 31 tokens | 75.6 t/s |
|
||||
|
||||
On one Spark, the same Q2 prose and coding prompts reached 23.4 and 44.0 t/s
|
||||
with DSpark. These are workload-specific measurements, not a promised speedup;
|
||||
poor draft acceptance can still make it slower. The measured settings are
|
||||
automatic once DSpark is enabled; speculation itself remains opt-in.
|
||||
|
||||
## Qwen3.8 Flash Next
|
||||
|
||||
Both Q2 and Q4 fit on one Spark. After building:
|
||||
|
||||
```sh
|
||||
./download_model.sh qwen38-q2
|
||||
./ds4 --mtp --ctx 32768
|
||||
```
|
||||
|
||||
Q2 keeps 41.73 GiB of weights resident; Q4 keeps 69.74 GiB. Both GGUFs also
|
||||
contain 95.37 GiB of BF16 n-grams, read directly from the SSD. Leave room
|
||||
for context and runtime buffers. Vision, the native agent, server APIs and
|
||||
text-session checkpoints work on CUDA; see [Qwen setup](QWEN38_FLASH_NEXT.md).
|
||||
|
||||
September 15, 2026, Q2 on one Spark: a 16K prefill reached 250 t/s, extending
|
||||
it to 30K reached 243 t/s, and ordinary decode stayed around 17.3 t/s.
|
||||
These are single runs with 8,192-token prefill chunks and 32 decode tokens.
|
||||
A separate 256-token prose run measured 17.1 t/s ordinarily and 19.9 t/s with
|
||||
MTP, at temperature zero and 8K allocated context. Speculation is opt-in;
|
||||
its benefit depends on the prompt.
|
||||
|
||||
## GLM 5.3 Flash
|
||||
|
||||
Q2 is the resident target for one Spark:
|
||||
|
||||
```sh
|
||||
./download_model.sh glm53-q2
|
||||
./ds4 --cuda -m gguf/GLM-5.3-Flash-Q2.gguf --ctx 16384
|
||||
```
|
||||
|
||||
Q4 does not fit resident. GLM Spark-to-Spark tensor parallelism is not
|
||||
implemented; the two-Mac RDMA instructions do not apply to GLM on CUDA.
|
||||
|
||||
## Vision and speculative decoding
|
||||
|
||||
DeepSeek Vision Experimental needs its matching text model and encoder:
|
||||
|
||||
```sh
|
||||
./download_model.sh ds4f-vision-q2
|
||||
./ds4 --cuda --vision gguf/DeepSeek-V4-Flash-Vision-Encoder.gguf
|
||||
```
|
||||
|
||||
Use `/read image.png` in the CLI. GLM vision also works on this backend; see
|
||||
[models and vision](MODELS.md#vision).
|
||||
|
||||
For Flash 0731, [DSpark](SPECULATIVE_DECODING.md) uses the separate 0731 support
|
||||
file. Vision Experimental has a different drafter. GLM uses its built-in MTP
|
||||
block with `--mtp`. None is enabled by default.
|
||||
|
||||
## Flash Q2 with DSpark
|
||||
|
||||
September 6, 2026, fully resident Flash 0731: median generation speed from
|
||||
three runs of 256 tokens, 4K allocated context and a 512-token prefill chunk.
|
||||
|
||||
| Prompt | Temperature | Ordinary decode | DSpark |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| C hash table | 0 | 19.72 t/s | 31.41 t/s |
|
||||
| C hash table | 1 | 19.53 t/s | 29.98 t/s |
|
||||
| Unpredictable prose | 1 | 19.53 t/s | 18.81 t/s |
|
||||
|
||||
Longer coding runs reached about 33 t/s. Poor draft acceptance can still make
|
||||
DSpark slower. Temperature 1 uses the default opportunistic policy; these are
|
||||
not exact-sampling results. DSpark does not accelerate prefill.
|
||||
|
||||
To reproduce the greedy coding case after downloading `ds4f-q2` and
|
||||
`ds4f-dspark`:
|
||||
|
||||
```sh
|
||||
./ds4 --cuda -m ds4flash.gguf \
|
||||
--dspark --mtp-model gguf/DeepSeek-V4-Flash-DSpark-support-0731.gguf \
|
||||
--ctx 4096 --prefill-chunk 512 --nothink --temp 0 --seed 12345 -n 256 \
|
||||
-p 'Write a complete C hash table implementation with string keys, insert, find, delete, and a test main. Output only C code.'
|
||||
```
|
||||
|
||||
For the temperature-1 rows, use `--temp 1 --top-p 0.95 --min-p 0.05`.
|
||||
The [QA record](../QA_BEFORE_RELEASES.md#16-speed-regression) includes the previous
|
||||
implementation, continued-context checks and quality comparisons.
|
||||
|
||||
## Larger models and serving
|
||||
|
||||
CUDA also has [SSD streaming](SSD_STREAMING.md) paths for larger weights.
|
||||
Memory fit and speed depend on the model layout; start with Q2 for normal use.
|
||||
|
||||
V4.1 Q2 SSD serving batches up to eight decode rows together. An eight-session
|
||||
test reached 11.0 aggregate t/s versus 8.4 with ordered execution; this is
|
||||
total throughput, not speed per client. Other single-Spark model paths retain
|
||||
their existing scheduling.
|
||||
See [serving](SERVER.md) and the recorded [benchmarks](PERFORMANCE.md).
|
||||
@@ -0,0 +1,208 @@
|
||||
# Inference Across Machines
|
||||
|
||||
[README](../README.md)
|
||||
|
||||
There are two modes:
|
||||
|
||||
| Mode | Split | Main use |
|
||||
| --- | --- | --- |
|
||||
| Tensor parallelism | Routed experts and per-layer work across two Macs or two Sparks | Resident inference with lower per-token work on each GPU |
|
||||
| Pipeline parallelism | Complete layer ranges across several machines | Fit larger models and overlap long prefills |
|
||||
|
||||
These are separate from [tensor parallelism across CUDA cards](CUDA_MULTI_GPU.md).
|
||||
Network protocols have no authentication or encryption. Use trusted machines
|
||||
and a trusted network; run the same commit on every peer. Model paths and
|
||||
artifacts must agree. Update all TP peers together when changing versions.
|
||||
|
||||
## Tensor parallelism between two Macs
|
||||
|
||||
This is a 50/50 split with exactly one worker. Do not pass `--layers`.
|
||||
Routed experts are sharded; attention partitioning depends on the model. Both
|
||||
GPUs work on the same token and exchange partial results. This can reduce generation
|
||||
latency, but the gain depends on the model, link, and comparison setup.
|
||||
|
||||
Two 128 GB Macs are useful for V4 Flash Q4/MXFP4 or GLM 5.3 Flash Q4.
|
||||
GLM 5.2 IQ2_XXS is another tested capacity setup. V4.1 Flash Q2 also runs
|
||||
on two 128 GB Macs, with disk-only Engram tables.
|
||||
A larger quant may need larger machines even though its tensor layout is supported.
|
||||
|
||||
### Link setup
|
||||
|
||||
Use a Thunderbolt cable. RDMA requires an active verbs device with an
|
||||
IPv4-mapped GID; a working ping alone does not establish that.
|
||||
|
||||
```sh
|
||||
rdma_ctl status
|
||||
ibv_devinfo -v
|
||||
```
|
||||
|
||||
Addresses must be on the cabled member interfaces, not only the Thunderbolt
|
||||
bridge. For example, after checking which interfaces are active:
|
||||
|
||||
```sh
|
||||
# Machine A, example member interface en1.
|
||||
sudo ifconfig en1 inet 10.99.0.2/30 alias
|
||||
# Machine B, example member interface en6.
|
||||
sudo ifconfig en6 inet 10.99.0.1/30 alias
|
||||
```
|
||||
|
||||
For the large tested shards on otherwise idle 128 GB Macs, the setup raised
|
||||
the per-boot GPU wired-memory limit on both machines:
|
||||
|
||||
```sh
|
||||
sudo sysctl iogpu.wired_limit_mb=120000
|
||||
```
|
||||
|
||||
This is specific to that memory configuration. It grants a larger GPU budget;
|
||||
it does not create more RAM. Check model, context, and system headroom before
|
||||
raising a limit on your machine.
|
||||
|
||||
### Start the pair
|
||||
|
||||
Download the same model on both machines. For GLM 5.3 Flash Q4:
|
||||
|
||||
```sh
|
||||
./download_model.sh glm53-q4
|
||||
```
|
||||
|
||||
Start the worker first; it retries while the coordinator loads:
|
||||
|
||||
```sh
|
||||
# Machine B.
|
||||
./ds4 --tensor-parallel --role worker \
|
||||
--coordinator 10.99.0.2 9911 --transport rdma --ctx 8192
|
||||
|
||||
# Machine A.
|
||||
./ds4 --tensor-parallel --role coordinator \
|
||||
--listen 10.99.0.2 9911 --transport rdma --ctx 8192
|
||||
```
|
||||
|
||||
The verbs device and GID are selected automatically. If ambiguous, specify
|
||||
`--rdma-device` and `--rdma-gid-index` from `ibv_devinfo`. Use `--transport tcp`
|
||||
on both peers when RDMA is unavailable. Do not probe the waiting coordinator
|
||||
with `curl` or `nc`: it may treat the connection as a worker handshake.
|
||||
|
||||
Keep workers running in a terminal or managed session and retain both logs.
|
||||
Do not treat repeated handshake or RDMA timeouts as successful QA merely
|
||||
because a retry works.
|
||||
|
||||
The coordinator can be `ds4`, `ds4-agent`, `ds4-server`, or `ds4-bench`;
|
||||
workers run `ds4`. For models with vision support, pass the same `--vision FILE`
|
||||
to both for image input.
|
||||
For GLM MTP, enable `--mtp` on both. For DeepSeek DSpark, both need the
|
||||
matching support model and DSpark options. V4.1 supports vision but not
|
||||
speculative decoding.
|
||||
|
||||
TP disk-cache restore currently rebuilds the exact saved token prefix on both
|
||||
ranks rather than restoring the coordinator alone. Expect prefill on restore.
|
||||
See [speculation](SPECULATIVE_DECODING.md) and [serving](SERVER.md).
|
||||
|
||||
## Tensor parallelism between two Sparks
|
||||
|
||||
V4 Flash 0731 Q2/MXFP4 and V4.1 Flash Q2 text inference support one GPU per rank,
|
||||
with a 50/50 expert split. Each Spark holds about 45 GiB of Flash 0731 Q2
|
||||
weights, 77 GiB of Flash MXFP4, or 81 GiB for V4.1, plus context and runtime
|
||||
buffers. V4.1 Engram tables stay on disk. Do not add `--ssd-streaming` or
|
||||
`--cuda-tensor-parallel`: those select different memory/execution modes.
|
||||
|
||||
Build the same commit with `make cuda-spark` on both machines and download
|
||||
`ds41f-q2` on both. RDMA needs the libibverbs development headers at build time,
|
||||
its runtime library, and an active RoCEv2 link. Check `ibv_devinfo -v` and use
|
||||
the addresses of the directly connected ports, not the management network.
|
||||
|
||||
For example, with the coordinator at `172.31.250.1` on the direct link:
|
||||
|
||||
```sh
|
||||
# Worker.
|
||||
./ds4 --cuda -m gguf/DeepSeek-V4.1-Flash-Q2.gguf --ctx 32768 \
|
||||
--tensor-parallel --role worker --coordinator 172.31.250.1 9911 \
|
||||
--transport rdma
|
||||
|
||||
# Coordinator.
|
||||
./ds4 --cuda -m gguf/DeepSeek-V4.1-Flash-Q2.gguf --ctx 32768 \
|
||||
--tensor-parallel --role coordinator --listen 172.31.250.1 9911 \
|
||||
--transport rdma
|
||||
```
|
||||
|
||||
The link address selects the matching verbs device and GID. If selection is
|
||||
ambiguous, use `--rdma-device` and `--rdma-gid-index`. TCP is also available
|
||||
with `--transport tcp` on both peers.
|
||||
|
||||
For Flash 0731, download `ds4f-q2` or `ds4f-mxfp4` on both hosts and use its
|
||||
GGUF path in the same commands. For DSpark, download `ds4f-dspark` on both and
|
||||
add `--dspark --mtp-model PATH` to both commands. With MXFP4 over RDMA, both
|
||||
GPUs also share the drafter work. This needs about 2.5 GiB extra per rank,
|
||||
beside the support model and context. If the worker has no drafter, or the
|
||||
configuration cannot use the split, drafting stays on the coordinator.
|
||||
Speed depends on the prompt.
|
||||
|
||||
The coordinator can be `ds4-agent`, `ds4-server` or `ds4-bench`. Match context
|
||||
sizes and any `--prefill-chunk` overrides on both sides. For V4.1, five or more
|
||||
ready sessions use native batched decode; smaller groups run in order.
|
||||
Use `--batched-session 8` on the server and allow memory for all eight contexts.
|
||||
Flash 0731 network sessions currently run in order, without native batching.
|
||||
CUDA network TP does not support vision, V4.1 DSpark or other model/quant layouts.
|
||||
|
||||
## Pipeline parallelism
|
||||
|
||||
Each process maps only its assigned layers, retaining that slice of the KV
|
||||
state. Layer ranges are inclusive. `N:output` includes the final layer and
|
||||
output head. Activations travel from one stage to the next over TCP.
|
||||
|
||||
For Flash Q4 on two machines, download `ds4f-q4` on both, then start each side.
|
||||
Replace the example address with your coordinator's reachable address:
|
||||
|
||||
```sh
|
||||
# Machine A.
|
||||
./ds4 --role coordinator --layers 0:19 --listen 10.99.0.2 9911
|
||||
|
||||
# Machine B.
|
||||
./ds4 --role worker --layers 20:output --coordinator 10.99.0.2 9911
|
||||
```
|
||||
|
||||
Normally give the output head to the final worker. With several workers,
|
||||
choose non-overlapping ranges covering the entire model. Workers register
|
||||
their ranges with the coordinator; intermediate workers forward activations
|
||||
directly to the next stage.
|
||||
|
||||
Long prefill chunks can occupy different stages simultaneously. A single
|
||||
generation stream cannot use that overlap: each token must finish the route
|
||||
before the next one is sampled. Use pipeline mode primarily for capacity and
|
||||
long-prefill throughput, not as a guaranteed decode speedup.
|
||||
|
||||
### Full PRO Q4
|
||||
|
||||
For two 512 GB Mac Studios, use the split artifacts:
|
||||
|
||||
```sh
|
||||
# Machine A.
|
||||
./download_model.sh pro-q4-layers00-30
|
||||
./ds4 -m gguf/DeepSeek-V4-Pro-Q4K-Layers00-30.gguf \
|
||||
--role coordinator --layers 0:30 --listen 10.99.0.2 9911
|
||||
|
||||
# Machine B.
|
||||
./download_model.sh pro-q4-layers31-output
|
||||
./ds4 -m gguf/DeepSeek-V4-Pro-Q4K-Layers-31-output.gguf \
|
||||
--role worker --layers 31:output --coordinator 10.99.0.2 9911
|
||||
```
|
||||
|
||||
These downloads do not change `ds4flash.gguf`. Startup is expensive because
|
||||
each side must make its model slice resident.
|
||||
|
||||
### Tuning and recovery
|
||||
|
||||
Keep default chunk sizes first. `--dist-prefill-window N` controls the number
|
||||
of chunks in flight; `--dist-prefill-chunk N` overrides the session-derived
|
||||
chunk size. `--debug` shows route and per-hop timings.
|
||||
|
||||
Activations use 32-bit transport by default. `--dist-activation-bits 16` halves
|
||||
the payload; `8` is more aggressive. These change numerical precision on the
|
||||
wire, not weights or KV storage. Validate output when changing them.
|
||||
|
||||
A disconnected worker invalidates the route. In-flight work can fail; later
|
||||
requests need a complete route before proceeding, and the coordinator can
|
||||
replay the saved token prefix to rebuild worker state. Pipeline snapshots
|
||||
serialize all layer slices into one payload and redistribute them when loaded.
|
||||
|
||||
For protocol details, see [ds4_distributed.c](../ds4_distributed.c)
|
||||
and [ds4_tp.c](../ds4_tp.c).
|
||||
@@ -0,0 +1,71 @@
|
||||
# Metal on Apple Silicon
|
||||
|
||||
[README](../README.md) | [Getting started](../README.md#start-here)
|
||||
|
||||
## Build and run
|
||||
|
||||
Install Apple's command-line developer tools if needed:
|
||||
|
||||
```sh
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
make
|
||||
./download_model.sh ds4f-q2
|
||||
./ds4
|
||||
```
|
||||
|
||||
The same build supports M3 and M5 Macs. Hardware-specific fast paths are
|
||||
selected automatically; no environment variable is needed to enable them.
|
||||
Leave other GPU and memory-heavy applications idle when comparing performance.
|
||||
|
||||
## Choose a model
|
||||
|
||||
| Memory | Starting point |
|
||||
| --- | --- |
|
||||
| 64 GB | Flash Q2 with `--ssd-streaming` |
|
||||
| 96 GB | Flash Q2; leave room for the context and other applications |
|
||||
| 128 GB | Flash Q2, or GLM 5.3 Flash Q2 with modest initial context |
|
||||
| 256 GB | Flash Q4/MXFP4 or GLM 5.3 Flash Q4 |
|
||||
| 512 GB | Larger models, including PRO Q2 |
|
||||
|
||||
Here, Flash means DeepSeek V4 Flash. V4.1 Flash has different memory
|
||||
requirements; see its [model guide](MODELS.md#deepseek-v41-flash).
|
||||
|
||||
These are starting points, not guarantees that every context or session count
|
||||
will fit. GLM 5.3 Flash Q2 is about 90 GiB before runtime allocations.
|
||||
Stop other memory-heavy workloads before loading it resident.
|
||||
|
||||
```sh
|
||||
./download_model.sh glm53-q2
|
||||
./ds4 -m gguf/GLM-5.3-Flash-Q2.gguf --ctx 32768
|
||||
```
|
||||
|
||||
For a model larger than RAM, start with automatic cache sizing:
|
||||
|
||||
```sh
|
||||
./ds4 --ssd-streaming
|
||||
```
|
||||
|
||||
See [SSD streaming](SSD_STREAMING.md) before increasing the expert cache.
|
||||
Do not bypass the memory guard just to make an oversized resident model start.
|
||||
|
||||
## Two Macs
|
||||
|
||||
Two 128 GB Macs can run a larger model fully resident with a 50/50 routed-expert
|
||||
split. Thunderbolt RDMA is the low-latency option; TCP is also supported.
|
||||
Follow [tensor parallel setup](DISTRIBUTED.md#tensor-parallelism-between-two-macs).
|
||||
For more than two machines, use [pipeline parallelism](DISTRIBUTED.md#pipeline-parallelism).
|
||||
|
||||
## Next steps
|
||||
|
||||
- [Vision and GLM models](MODELS.md)
|
||||
- [DSpark and GLM MTP](SPECULATIVE_DECODING.md)
|
||||
- [Batched serving](SERVER.md#multiple-sessions)
|
||||
- [Benchmarking](PERFORMANCE.md)
|
||||
|
||||
For DeepSeek V4 Flash and PRO, `--power 70` trades throughput for lower
|
||||
sustained GPU load. V4.1 and GLM currently require `--power 100`.
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
# Models and Vision
|
||||
|
||||
[README](../README.md) | [Getting started](../README.md#start-here)
|
||||
|
||||
DwarfStar is not a general GGUF runner. Use the download targets below: other
|
||||
GGUFs may have unsupported tensor layouts, metadata, or quantization mixes.
|
||||
Run `./download_model.sh --help` for filenames and all available targets.
|
||||
|
||||
Main-model downloads update `ds4flash.gguf`. Encoders, draft models, packaged
|
||||
FP8 weights, and split PRO pieces do not. Pass `-m FILE` to avoid depending on
|
||||
which model was downloaded last.
|
||||
|
||||
Some downloads require the Hugging Face CLI; the script prints installation
|
||||
instructions when needed. Authentication is optional for public weights;
|
||||
your cached Hugging Face token or `HF_TOKEN` is used when present.
|
||||
|
||||
## DeepSeek V4
|
||||
|
||||
| Target | Use |
|
||||
| --- | --- |
|
||||
| `ds4f-q2` | Flash 0731, about 81 GiB; starting point for 96/128 GB systems |
|
||||
| `ds4f-q2-q4` | Mostly Q2, with the last six routed-expert layers at Q4; needs more memory |
|
||||
| `ds4f-q4` | Flash 0731 Q4; larger-memory or distributed systems |
|
||||
| `ds4f-mxfp4` | Native MXFP4 routed experts; larger-memory or distributed systems |
|
||||
| `pro-q2-imatrix` | PRO 0813; 512 GB resident target, or SSD streaming |
|
||||
| `pro-q4-split` | Both PRO Q4 pieces for pipeline execution |
|
||||
|
||||
```sh
|
||||
./download_model.sh ds4f-q2
|
||||
./ds4
|
||||
```
|
||||
|
||||
The Flash Q2 recipe spends most of its compression on routed experts:
|
||||
IQ2_XXS gate/up and Q2_K down. Other components use higher precision, including
|
||||
Q8 projections, shared experts and output, plus F16/F32 tensors. They are not
|
||||
all untouched source weights. The imatrix guides the routed quantization.
|
||||
|
||||
The MXFP4 recipe preserves DeepSeek's released MXFP4 routed experts without
|
||||
requantizing them. Metal and CUDA support it. Blackwell uses native FP4 matrix
|
||||
instructions and FP4 activations for batched expert work; CUDA decode and
|
||||
other CUDA architectures use Q8 activations. ROCm also has a resident MXFP4
|
||||
path, including pipeline execution for models too large for one host.
|
||||
|
||||
To build weights rather than download them, see [GGUF tools](../gguf-tools/README.md).
|
||||
|
||||
## DeepSeek V4.1 Flash
|
||||
|
||||
V4.1 Flash text and vision inference work on Metal. CUDA supports text with
|
||||
Q2 SSD streaming on one Spark or resident shards across two Sparks. ROCm
|
||||
supports single-host text inference with SSD streaming. It needs its own
|
||||
GGUF, tokenizer and inference graph; V4 Flash weights and DSpark support files
|
||||
are not interchangeable with it.
|
||||
|
||||
| Target | File size | Main weights |
|
||||
| --- | ---: | ---: |
|
||||
| `ds41f-q2` | 341 GiB | 152 GiB |
|
||||
| `ds41f-q4` | 483 GiB | 294 GiB |
|
||||
| `ds41f-mxfp4` | 467 GiB | 278 GiB |
|
||||
|
||||
Q2 and Q4 use imatrix-calibrated routed experts; MXFP4 uses OCP MXFP4 routed
|
||||
experts with higher-precision attention, shared experts and output weights.
|
||||
All include 189 GiB of Engram tables. Engram rows are read directly from the
|
||||
file as needed in every mode, never loaded as a resident table. Keep the GGUF
|
||||
on a fast local SSD.
|
||||
|
||||
On one 128 GB Mac, use SSD streaming. Leave the expert cache budget automatic:
|
||||
|
||||
```sh
|
||||
./download_model.sh ds41f-q2
|
||||
./ds4 -m gguf/DeepSeek-V4.1-Flash-1017-IQ2_XXS-Q2_K-imatrix.gguf \
|
||||
--ssd-streaming --ctx 32768
|
||||
```
|
||||
|
||||
Use `ds4-agent` or `ds4-server` with the same model and memory options.
|
||||
On a DGX Spark, add `--cuda`; see the [Spark guide](DGX_SPARK.md#deepseek-v41-flash).
|
||||
`--think-level 25` sets reasoning effort explicitly; the range is 1 to 100,
|
||||
with 0 disabling thinking. `/think 25` changes it in the CLI or native agent.
|
||||
`--think` selects 75 and `--think-max` selects 100.
|
||||
|
||||
For two 128 GB Macs or Sparks, follow the [TP/RDMA setup](DISTRIBUTED.md), passing this
|
||||
GGUF with `-m` on both ranks and omitting `--ssd-streaming`. Each rank holds
|
||||
about 81 GiB of main weights, plus context and runtime buffers. Both machines
|
||||
need the complete GGUF on disk. A 256 GB or larger Mac can instead hold all
|
||||
main weights; full residency has been tested on an M3 Ultra with 512 GB.
|
||||
|
||||
Q4 needs SSD streaming on smaller Macs, or a 512 GB Mac for full residency.
|
||||
It does not fit resident TP across two 128 GB Macs. Download it with
|
||||
`./download_model.sh ds41f-q4` and select `gguf/DeepSeek-V4.1-Flash-Q4.gguf`.
|
||||
The download comes in two parts; the script joins and verifies them automatically.
|
||||
Allow another 37 GiB of free disk space while joining. Rerun the command to resume an
|
||||
interrupted download or join.
|
||||
|
||||
For the 1017 MXFP4 build, run:
|
||||
|
||||
```sh
|
||||
./download_model.sh ds41f-mxfp4
|
||||
```
|
||||
|
||||
The target downloads all nine standard GGUF shards, verifies each size and
|
||||
SHA-256, and merges them into `gguf/DeepSeek-V4.1-Flash-1017-MXFP4-experts.gguf`.
|
||||
DwarfStar's V4.1 loader needs this single file with page-aligned Engram tables;
|
||||
the script restores the original 16 KiB alignment and verifies the original
|
||||
whole-file checksum before updating `ds4flash.gguf`. The shard files are kept,
|
||||
so allow about **935 GiB** for shards plus the merged output. Free space is
|
||||
checked before downloading. Rerun the same command to resume downloads or an
|
||||
interrupted merge; existing files and the previous default model are preserved
|
||||
if verification fails. Q2 and MXFP4 use the 1017 checkpoint; Q4 remains the
|
||||
previous release.
|
||||
|
||||
Large SSD prefills process layers in wide batches. Metal overlaps computation
|
||||
with the next layer's reads; CUDA stages experts into its bounded device cache.
|
||||
Short appends keep using the expert cache.
|
||||
Resident and TP inference also batch continued prefills automatically.
|
||||
|
||||
For concurrent serving, see [session batching](SERVER.md#multiple-sessions).
|
||||
Each slot needs its own context memory; start with `--ctx 4096` before
|
||||
increasing both context and slot count. CUDA Q2 SSD mode batches up to eight
|
||||
decode rows; CUDA network TP currently serves sessions in order.
|
||||
[DSpark](SPECULATIVE_DECODING.md#deepseek-v41-flash-dspark) uses a
|
||||
separate V4.1 support GGUF and currently requires one host. Pipeline execution
|
||||
is not implemented for V4.1; vision requires Metal.
|
||||
|
||||
Scalar, batched and tensor-parallel execution are not numerically identical.
|
||||
Q4 batched prefill shows a small probability-score loss on the short official
|
||||
continuation test, with unchanged overall top-token agreement. See the
|
||||
[QA results](../QA_BEFORE_RELEASES.md#17-deepseek-v41-flash) for details
|
||||
and remaining differences.
|
||||
|
||||
For images, download the matching encoder and add it to the same command:
|
||||
|
||||
```sh
|
||||
./download_model.sh ds41f-vision
|
||||
./ds4-agent -m gguf/DeepSeek-V4.1-Flash-1017-IQ2_XXS-Q2_K-imatrix.gguf \
|
||||
--ssd-streaming --vision gguf/DeepSeek-V4.1-Flash-Vision.gguf
|
||||
```
|
||||
|
||||
Vision works with SSD streaming, full residency and two-Mac TP. Pass the encoder
|
||||
on both TP ranks. Use `/read image.png` in `ds4`, `view_image` in `ds4-agent`,
|
||||
or the [server image API](SERVER.md#images). V4 Flash vision encoders do not
|
||||
work with V4.1. See [conversion](../gguf-tools/README.md#convert-deepseek-v41-flash)
|
||||
to build the GGUFs from safetensors.
|
||||
|
||||
## Qwen3.8 Flash Next
|
||||
|
||||
`./download_model.sh qwen38-q2` downloads one **137.10 GiB** GGUF: 41.73 GiB
|
||||
of main/MTP weights and 95.37 GiB of original BF16 n-grams kept on disk.
|
||||
Its gate/up experts use IQ2_XXS; down experts use Q2_K with 640 logical inputs
|
||||
padded to 768 in the weight file. It replaces the larger MXFP4-down IQ2 release.
|
||||
For 64 GB Macs, start at 8K context with a 1,024-token prefill chunk; runtime
|
||||
allocations add to the main weights, but the n-gram table is not mapped or
|
||||
preloaded. Keep the GGUF on a fast local SSD.
|
||||
The larger `qwen38-q4k` target uses 165.11 GiB on disk and 69.74 GiB for
|
||||
resident weights, before runtime buffers.
|
||||
This model runs on Metal and single-GPU CUDA, including DGX Spark.
|
||||
The script links `ds4flash.gguf` to the combined GGUF:
|
||||
|
||||
```sh
|
||||
./ds4 --ctx 8192 --prefill-chunk 1024
|
||||
```
|
||||
|
||||
Add `--mtp` for speculation; no second file is needed.
|
||||
See [Qwen setup](QWEN38_FLASH_NEXT.md)
|
||||
for memory, conversion, vision, and sampling details.
|
||||
|
||||
Vision uses a separate encoder. `./download_model.sh qwen38-vision` downloads
|
||||
llama.cpp's Q8_0 mmproj from
|
||||
[ggml-org/Qwen3.8-Flash-Next-GGUF](https://huggingface.co/ggml-org/Qwen3.8-Flash-Next-GGUF);
|
||||
pass it at runtime with `--vision`.
|
||||
|
||||
## GLM 5.3 Flash
|
||||
|
||||
| Target | Approximate file size | Use |
|
||||
| --- | ---: | --- |
|
||||
| `glm53-q2` | 90 GiB | One 128 GB Mac or DGX Spark; ROCm also supported |
|
||||
| `glm53-q4` | 178 GiB | Larger Mac, two 128 GB Macs, or SSD streaming |
|
||||
| `glm53-fp8` | 305 GiB | Packaged native weights only; inference not implemented |
|
||||
|
||||
```sh
|
||||
./download_model.sh glm53-q2
|
||||
./ds4 -m gguf/GLM-5.3-Flash-Q2.gguf --ctx 32768
|
||||
```
|
||||
|
||||
GLM 5.3 Flash has recurrent KDA layers, sparse DSA attention, hyper-connections,
|
||||
and a built-in MTP block. The Q2 file uses imatrix-guided IQ2_XXS gate/up and
|
||||
Q2_K down experts. Q4 is the higher-precision alternative.
|
||||
|
||||
Q2 is close enough to a 128 GB machine's memory budget that other workloads
|
||||
and context size matter. Follow the [Metal](METAL.md), [Spark](DGX_SPARK.md),
|
||||
or [Strix Halo](STRIX_HALO.md) starting configuration for your host.
|
||||
|
||||
Ordinary decode is the default. Enable the embedded draft block with `--mtp`:
|
||||
|
||||
```sh
|
||||
./ds4-agent -m gguf/GLM-5.3-Flash-Q2.gguf --mtp --ctx 50000
|
||||
```
|
||||
|
||||
No second model file is needed. See [sampling behavior](SPECULATIVE_DECODING.md)
|
||||
before choosing between the default opportunistic mode and exact sampling.
|
||||
|
||||
## Full GLM 5.3 and GLM 5.2
|
||||
|
||||
Full GLM 5.3 Q2 is about 197 GiB. Use a sufficiently large machine or streaming:
|
||||
|
||||
```sh
|
||||
./download_model.sh glm53-full-q2
|
||||
./ds4 --ssd-streaming
|
||||
```
|
||||
|
||||
GLM 5.2 downloads are `glm-antirez-iq2xxs`, `glm-antirez-q2`,
|
||||
`glm-antirez-q4`, and the 11-shard `glm-unsloth-q4`. They are much larger
|
||||
than GLM 5.3 Flash; choose memory capacity before choosing the quantization.
|
||||
|
||||
GLM runs on Metal, CUDA and ROCm. The routed paths include IQ2_XXS, Q2_K, and
|
||||
Q4_K, with additional mixed layouts supported by the tested GGUFs. Two-Mac
|
||||
ownership-aware TP accepts IQ2_XXS, Q2_K, and Q4_K gate/up layouts; this does
|
||||
not mean every Q4 model fits two 128 GB machines. Use a tested artifact, not
|
||||
an arbitrary combination of supported tensor types.
|
||||
|
||||
GLM uses graph-selected prefill chunks and does not accept `--prefill-chunk`
|
||||
or an external `--mtp-model`. It currently requires `--power 100`.
|
||||
Directional steering is supported for GLM 5.3, not GLM 5.2.
|
||||
|
||||
## Vision
|
||||
|
||||
PNG and JPEG input works in the CLI, native agent, and HTTP server on Metal,
|
||||
single-GPU CUDA, and ROCm. The encoder must match the model.
|
||||
V4.1 Flash vision is currently Metal-only; its setup is [above](#deepseek-v41-flash).
|
||||
|
||||
### DeepSeek Flash Vision Experimental
|
||||
|
||||
Vision Experimental is a different language checkpoint from Flash 0731.
|
||||
The main download includes its encoder:
|
||||
|
||||
```sh
|
||||
./download_model.sh ds4f-vision-q2
|
||||
./ds4 --vision gguf/DeepSeek-V4-Flash-Vision-Encoder.gguf
|
||||
```
|
||||
|
||||
Larger targets are `ds4f-vision-q2-q4` and `ds4f-vision-mxfp4`.
|
||||
`ds4f-vision-encoder` downloads just the encoder when the language GGUF is
|
||||
already present. Vision Experimental has its own [DSpark drafter](SPECULATIVE_DECODING.md).
|
||||
|
||||
### GLM 5.3 Flash
|
||||
|
||||
The text GGUF stays the same. Download and add the encoder explicitly:
|
||||
|
||||
```sh
|
||||
./download_model.sh glm53-vision
|
||||
./ds4 -m gguf/GLM-5.3-Flash-Q2.gguf \
|
||||
--vision gguf/GLM-5.3-Flash-Vision-Encoder.gguf
|
||||
```
|
||||
|
||||
Use `/read image.png` in `ds4`, or start `ds4-agent` with the same `--vision`
|
||||
argument to enable `view_image`. Agent sessions containing images cannot yet
|
||||
be saved with `/save`.
|
||||
|
||||
For two-Mac TP, pass the same encoder on both ranks. The coordinator encodes
|
||||
the image and sends the projected visual tokens to the worker.
|
||||
For HTTP image formats and limits, see [serving](SERVER.md#images).
|
||||
|
||||
### Qwen3.8 Flash Next
|
||||
|
||||
The text GGUF stays the same. Download and add the encoder explicitly:
|
||||
|
||||
```sh
|
||||
./download_model.sh qwen38-vision
|
||||
./ds4 --mtp \
|
||||
--vision gguf/mmproj-Qwen3.8-Flash-Next-Q8_0.gguf
|
||||
```
|
||||
|
||||
The encoder is llama.cpp's Q8_0 mmproj conversion of the model's Qwen3-VL
|
||||
tower. Use `/read image.png` in `ds4` or `image_url` parts over HTTP; see
|
||||
[Qwen setup](QWEN38_FLASH_NEXT.md) for image limits and resize behavior.
|
||||
@@ -0,0 +1,72 @@
|
||||
# Performance and Benchmarking
|
||||
|
||||
[README](../README.md)
|
||||
|
||||
Compare the same checkpoint, quantization, context, and sampling settings.
|
||||
Record the commit and whether weights were resident, streamed, or distributed.
|
||||
Keep other GPU workloads idle and repeat in alternating order: one favorable
|
||||
run is not a speed result.
|
||||
|
||||
## Context sweeps
|
||||
|
||||
`ds4-bench` measures prefill and generation at successive context frontiers:
|
||||
|
||||
```sh
|
||||
./ds4-bench -m ds4flash.gguf \
|
||||
--prompt-file speed-bench/promessi_sposi.txt \
|
||||
--ctx-start 2048 --ctx-max 65536 --step-incr 2048 --gen-tokens 128
|
||||
```
|
||||
|
||||
Each prefill number measures the newly added interval. Generation uses a fixed
|
||||
greedy, non-EOS probe. The benchmark normally restores a memory snapshot after
|
||||
each probe; network TP/pipeline runs and snapshots beyond its memory limit use
|
||||
prefix replay instead. Do not interpret replay time as continued-prefill speed.
|
||||
|
||||
Use `--step-mul F` for exponential context spacing. Output is CSV, including
|
||||
prefill throughput, generation throughput, and snapshot size when available.
|
||||
The prompt is the cleaned public-domain *I Promessi Sposi* text in
|
||||
[speed-bench](../speed-bench/README.md).
|
||||
|
||||
Prefill defaults are configuration-specific: ordinary DeepSeek uses 4096-token
|
||||
chunks, CUDA TP uses 2048, and long PRO prompts may use 8192. GLM chooses its
|
||||
own chunks and rejects `--prefill-chunk`. The strict DeepSeek API-vector test
|
||||
pins 2048; do not generalize that setting to every benchmark.
|
||||
|
||||
## Recorded Flash Q2 baseline
|
||||
|
||||
These existing sweeps use 2048-token intervals and 128 generation tokens per
|
||||
frontier. They are recorded baselines, not measurements of every subsequent
|
||||
commit. Full data: [M5 Max](../speed-bench/m5_max.csv) and
|
||||
[DGX Spark](../speed-bench/gb10.csv).
|
||||
|
||||
| Machine | Context | Prefill | Generation |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| M5 Max, 128 GB | 2048 | 790.18 t/s | 39.35 t/s |
|
||||
| M5 Max, 128 GB | 16384 | 572.53 t/s | 36.14 t/s |
|
||||
| M5 Max, 128 GB | 32768 | 557.04 t/s | 34.36 t/s |
|
||||
| M5 Max, 128 GB | 65536 | 398.50 t/s | 27.64 t/s |
|
||||
| DGX Spark, 128 GB | 2048 | 825.76 t/s | 18.05 t/s |
|
||||
| DGX Spark, 128 GB | 16384 | 872.44 t/s | 15.10 t/s |
|
||||
| DGX Spark, 128 GB | 32768 | 855.94 t/s | 14.43 t/s |
|
||||
| DGX Spark, 128 GB | 65536 | 822.98 t/s | 13.84 t/s |
|
||||
|
||||

|
||||
|
||||
Historical PRO Q2 measurements on the M3 Ultra are retained in this chart:
|
||||
|
||||

|
||||
|
||||
## What to compare next
|
||||
|
||||
- For SSD streaming, record the effective cache and distinguish cold startup
|
||||
from a warm cache.
|
||||
- For multiple sessions, report both individual latency and aggregate
|
||||
throughput. Ordered fallback is not native batching.
|
||||
- For DSpark/MTP, compare plain decode too. A faster drafter path can still be
|
||||
slower than ordinary decoding on an unpredictable prompt.
|
||||
- For TP, keep quantization and prompt equal; comparing resident Q4 on two
|
||||
machines with streamed Q4 on one measures capacity benefits as well as parallelism.
|
||||
|
||||
Recent focused TP and DSpark comparisons, including their limitations, live in
|
||||
[QA_BEFORE_RELEASES.md](../QA_BEFORE_RELEASES.md). Use its speed-regression
|
||||
procedure rather than accumulating one-off timings in the main README.
|
||||
@@ -0,0 +1,551 @@
|
||||
# Qwen3.8 Flash Next
|
||||
|
||||
[Back to README](../README.md)
|
||||
|
||||
Qwen3.8-Flash-Next uses the `qwen4exp` GGUF architecture and a dedicated
|
||||
Metal/CUDA graph with gated delta-net, gated GQA, block-sparse attention,
|
||||
hyper-connections, n-gram embeddings, MoE, and MTP.
|
||||
|
||||
## Download and run
|
||||
|
||||
```sh
|
||||
make
|
||||
./download_model.sh qwen38-q2
|
||||
./ds4 --ctx 8192 --prefill-chunk 1024
|
||||
```
|
||||
|
||||
The download installs one GGUF containing the main model, MTP and original
|
||||
BF16 n-grams, and updates `ds4flash.gguf`:
|
||||
|
||||
| Target | File size | Resident weights |
|
||||
| --- | ---: | ---: |
|
||||
| `qwen38-q2` | 137.10 GiB | 41.73 GiB |
|
||||
| `qwen38-q4k` | 165.11 GiB | 69.74 GiB |
|
||||
|
||||
The 95.37 GiB n-gram table stays on disk. The runtime reads selected rows
|
||||
directly from the GGUF, without mapping or preloading the table. Keep the
|
||||
file on a fast local SSD. No sidecar or n-gram option is needed.
|
||||
|
||||
The Q2 model uses IQ2_XXS gate/up experts and Q2_K down projections padded
|
||||
from 640 to 768 columns, calibrated with an imatrix. Q4 uses calibrated
|
||||
Q4_K gate/up and MXFP4 down experts. Context and runtime buffers still need
|
||||
RAM beyond the resident weights; start Q2 with the context and chunk above
|
||||
on a 64 GB machine. Q4 needs a larger machine.
|
||||
|
||||
On Metal, SSD streaming keeps a bounded cache of routed experts instead of
|
||||
requiring all of them in RAM:
|
||||
|
||||
```sh
|
||||
./ds4 --ssd-streaming --ctx 4096 --prefill-chunk 128
|
||||
```
|
||||
|
||||
The Q2 and Q4 packs support streaming during prefill, decode and MTP. Dense
|
||||
weights, shared experts, context and runtime buffers still need RAM; the
|
||||
n-gram table continues to use its existing disk reads. Missing routed experts
|
||||
are read into owned buffers. If a prefill batch needs more experts than the
|
||||
cache can hold, or an MTP layer uses a different expert size, it uses explicit
|
||||
temporary staging while retaining the normal kernel arithmetic. On devices
|
||||
with GPU buffer addresses, this staging contains only the distinct selected
|
||||
experts. Selecting every expert, or running without buffer-address support,
|
||||
retains the full-layer fallback.
|
||||
|
||||
During single-token decode, cache misses overlap with gate/up computation
|
||||
for cached experts and the shared expert. Once the reads finish, the missing
|
||||
gate/up slots are computed, followed by one down pass and the original ordered
|
||||
reduction. Private address snapshots keep the running GPU work independent of
|
||||
cache updates. Fully cached selections use one gate/up pass. Cached batched
|
||||
prefill uses overlap on every Metal device: resident experts run while missing
|
||||
weights load, then missing gate/up computation overlaps the down-weight reads.
|
||||
Temporary staging retains its existing schedule; an MTP layer whose
|
||||
expert size differs from the main model is staged again on each invocation,
|
||||
without replacing the main model's cache entries.
|
||||
|
||||
A local M1 Max 32 GiB check compared this compact split schedule with
|
||||
`85d37ae`, using the Q2 pack, automatic expert cache, `--ctx 4096`,
|
||||
`--prefill-chunk 128`, `-n 100`, `--temp 0 --nothink`, and the prompt
|
||||
`narrami la storia di roma`. Three alternating pairs produced:
|
||||
|
||||
| Decode | Runs (t/s) | Median (t/s) |
|
||||
| --- | --- | --- |
|
||||
| Before | 8.40, 8.25, 7.26 | 8.25 |
|
||||
| Compact split | 8.44, 8.51, 8.14 | 8.44 |
|
||||
|
||||
All generated output was identical. The observed median gain was 2.3%; the
|
||||
variation between runs does not establish a 10% improvement. Prefill medians
|
||||
were 9.98 and 10.05 t/s; its execution path is unchanged. These are local
|
||||
warm-file measurements, not a guarantee for other Macs or cold SSD reads.
|
||||
|
||||
Q4_K gate/up shares activation reads between the shared expert's Q8
|
||||
projections and uses packed activation/scale loads while preserving the
|
||||
original FP32 accumulation order. On M1 Max, the 640-to-2560 MXFP4 down
|
||||
projection also enables prefetch by default. `DS4_QWEN4_MOE_DOWN_PREFETCH=0`
|
||||
restores the plain down kernel for diagnostics. Safe math always retains that
|
||||
plain kernel, including when prefetch is explicitly requested, because its
|
||||
noncontracted arithmetic differs from the prefetched kernel's explicit FMA.
|
||||
|
||||
A local Q4 SSD check on M1 Max 32 GiB used `--ctx 32768`,
|
||||
`--prefill-chunk 1024`, `--temp 0 --nothink`, `-n 100`, and the prompt
|
||||
`Narrami in italiano la storia di Roma`. Three alternating pairs comparing
|
||||
the previous kernels with these changes produced:
|
||||
|
||||
| Decode | Runs (t/s) | Median (t/s) |
|
||||
| --- | --- | --- |
|
||||
| Before | 6.02, 6.07, 6.26 | 6.07 |
|
||||
| Q4 kernel updates | 6.42, 6.03, 7.27 | 6.42 |
|
||||
|
||||
All generated output was identical. The observed median gain was 5.8%; SSD
|
||||
and system variation remained substantial. Prefill medians were 6.94 and
|
||||
6.91 t/s. These changes retain the existing prefill and row-dispatch policies;
|
||||
Q2's smaller sparse-prefill tiles did not show a stable benefit for Q4 in
|
||||
local comparisons. The measurements do not establish gains on other devices.
|
||||
|
||||
MXFP4 prefill also decodes each 16-value half-block with one exponent read
|
||||
and packed byte loads, retaining the original multiplication and final
|
||||
half/float conversion. Compared with `46425263`, alternating resident-kernel
|
||||
benchmarks on M1 Max at 2048 tokens and 10 routed slots reduced median
|
||||
gate/up-plus-down time from 70.65 to 66.31 ms with 32 experts and from 81.57
|
||||
to 77.34 ms with 256 experts (6.1% and 5.2% less time).
|
||||
The staging oracle checks all exponent bytes, nibble codes and byte
|
||||
alignments against two original 8-value decoders in both math modes.
|
||||
|
||||
On M1 Max, batched MXFP4 down rows also reuse activations across two output
|
||||
rows. A function constant enables this only for multi-token NR2 dispatches;
|
||||
single-token decode and NR1/NR4 retain the original row loop. A warmed
|
||||
same-process comparison with 16 alternating samples per variant, 128 calls
|
||||
per sample, width 640, 2560 output rows and 10 routed slots measured:
|
||||
|
||||
| Two-token down kernel | Before GPU time | After GPU time |
|
||||
| --- | --- | --- |
|
||||
| Q8 shared expert | 176.62 us | 150.70 us |
|
||||
| MXFP4 shared expert | 216.20 us | 188.48 us |
|
||||
|
||||
Outputs were bit-identical, and single-token GPU timings did not regress.
|
||||
Full Q4 SSD runs remain sensitive to I/O: two alternating pairs at context
|
||||
32768 and chunk 1024 gave prefill medians of 22.94 and 23.41 t/s on a
|
||||
201-token prompt, with all 100 generated tokens unchanged. With MTP and two
|
||||
draft tokens on the Rome prompt, median generation remained 7.65 t/s despite
|
||||
the faster down kernel. These checks compare against `46425263`; they do
|
||||
not imply the same percentage gain for complete inference or other devices.
|
||||
|
||||
Leave the expert-cache budget automatic initially. A plain count passed to
|
||||
`--ssd-streaming-cache-experts` requests dynamic cache slots; an `NGB` budget
|
||||
also includes the reserved layer-staging space. Startup accounts for the
|
||||
context, prefill chunk, static weights, vision weights when present, and
|
||||
staging before choosing the cache size. Reduce the context or prefill chunk
|
||||
if those fixed requirements leave insufficient memory.
|
||||
The total budget is the smaller of 87.5% of physical RAM and Metal's
|
||||
recommended working set, with a further 2 GiB reserved for runtime overhead.
|
||||
The recommendation is not discounted again, and admitted static weights stay
|
||||
eligible for locking in RAM as the expert cache grows.
|
||||
|
||||
Qwen fills its expert cache on demand, so `--ssd-streaming-cold` does not
|
||||
change its preload behavior. Popularity preloading and the GLM-specific
|
||||
`--ssd-streaming-full-layers` option are not supported for Qwen.
|
||||
|
||||
On M1 Max, the shared half-operand MoE prefill path requires IQ2_XXS gate/up,
|
||||
Q2_K down and at least 8192 actual tokens in one streamed batch. The configured
|
||||
`--prefill-chunk` is only an upper limit: a 5760-token prompt with chunk 8192
|
||||
does not activate this path. Comparing that prompt at chunks 2048 and 8192
|
||||
measures the change in batching, not an isolated half-operand kernel gain.
|
||||
|
||||
On a DGX Spark, build with `make cuda-spark` instead of `make`. Both Q2 and
|
||||
Q4 fit resident on an otherwise idle 128 GB Spark; the n-grams stay on SSD.
|
||||
The same commands support text, vision, MTP, steering and text-session
|
||||
checkpoints in `ds4`, `ds4-agent` and `ds4-server`. No CUDA-specific model
|
||||
conversion or feature flags are needed.
|
||||
Leave `--prefill-chunk` unset on the Spark to use the faster 8192-token
|
||||
default. The 1024-token example above saves memory on smaller Macs.
|
||||
|
||||
Use the same model options with `ds4-agent` or `ds4-server`.
|
||||
Add `--mtp` for speculative decoding using the built-in MTP weights.
|
||||
`DS4_QWEN4_MTP_PREFILL` controls how prefill prepares the predictor’s prompt history.
|
||||
|
||||
| Value | Predictor history for new prompt rows |
|
||||
| --- | --- |
|
||||
| `auto` (default) | Prepare it only with SSD streaming |
|
||||
| `on` (also `1`) | Prepare it for both resident and SSD streaming runs |
|
||||
| `off` (also `0`) | Skip historical projections and initialize those cache rows to zero |
|
||||
|
||||
To prepare the full predictor history when the weights fit in RAM, run
|
||||
|
||||
```sh
|
||||
DS4_QWEN4_MTP_PREFILL=on ./ds4 -m gguf/Qwen3.8-Flash-Next-Q2.gguf --mtp --ctx 8192
|
||||
```
|
||||
|
||||
Use `on` for resident workloads where the decode
|
||||
benefit outweighs the prefill cost, and compare total wall time on your prompts.
|
||||
`off` and resident `auto` keep MTP drafts and target verification enabled, but
|
||||
the cold predictor history can change drafts and reduce their acceptance rate.
|
||||
Zero keys still participate in predictor attention. Target prefill caches and logits are unchanged.
|
||||
Decode continues to prepare real predictor rows. Each prefill chunk also
|
||||
retains its final row for real preparation at the next boundary, keeping
|
||||
checkpoint restore consistent with uninterrupted prefill.
|
||||
The setting applies to newly prefilled rows. Reused or restored checkpoints
|
||||
retain their existing predictor cache contents.
|
||||
Without `--mtp`, this setting has no effect.
|
||||
|
||||
`ds4-bench` benchmarks ordinary decoding; it does not accept `--mtp`.
|
||||
`--nothink` disables thinking. The server exposes
|
||||
`qwen3.8-flash-next`, `qwen3.8-flash-next-chat`, and
|
||||
`qwen3.8-flash-next-reasoner` aliases. Tool calls use the native
|
||||
`<tool_call><function=...><parameter=...>` format.
|
||||
|
||||
With resident weights on Metal, `ds4-server --batched-session N` decodes
|
||||
the slots together. Work that
|
||||
only reads weights runs once for the whole batch, while the delta-net
|
||||
recurrence, the attention caches and the n-gram convolution stay per session.
|
||||
The slots also share one prefill workspace, so an extra slot costs its caches
|
||||
rather than another few GiB of transients. As with the other natively batched
|
||||
models, grouping changes the order of floating point reductions, so a batched
|
||||
reply can differ from the same prompt decoded alone.
|
||||
Add `--mtp` to use batched speculative decoding as well. The server accepts
|
||||
matching greedy drafts by default, including at nonzero temperature. With
|
||||
`--mtp-exact-sampling`, sampled requests use ordinary batched decoding;
|
||||
temperature-zero requests can still speculate. Default greedy acceptance also
|
||||
means a seed need not reproduce a reply under a different batching schedule.
|
||||
Speculative batches above 16 sessions, images and steering use the ordered
|
||||
fallback. Metal SSD expert streaming also uses the ordered fallback and
|
||||
keeps each session's streaming workspace; it does not use the resident batch
|
||||
arena. CUDA currently decodes sessions in order.
|
||||
|
||||
Disk KV checkpoints include recurrent state. Rewinding to an earlier position
|
||||
replays the retained prefix on the next evaluation. The native context is
|
||||
262144 tokens; `DS4_QWEN4_YARN_FACTOR=2` or `=4` enables static YaRN for
|
||||
longer contexts, with a possible quality cost on shorter prompts.
|
||||
|
||||
## Metal tuning on M1 Max and M2-M4
|
||||
|
||||
The established M1 Max simdgroup optimizations also apply to Apple M2, M3
|
||||
and M4, including their Pro, Max and Ultra variants, within the same tensor
|
||||
shapes, batch sizes, math modes and diagnostic overrides:
|
||||
|
||||
- Wide Q8 decode projections, HC gate/mix reuse and short MTP Q8 projections.
|
||||
- IQ2 gate/up, Q2_K down-row reuse and the Q4 model's MXFP4 down prefetch.
|
||||
- Small HC-down and HC-normalization prefill batches, and large Q8 SSD prefill.
|
||||
- Sparse IQ2/Q2 SSD tiles, Q4/MXFP4 K32 tiles, optional half-operand staging
|
||||
for large SSD batches and release of that scratch before Q4 decode.
|
||||
|
||||
M3 Ultra retains its dedicated MoE specialization, row/group geometry and
|
||||
prefill tile policy; the other shared Q8, HC and MTP choices still apply.
|
||||
M5/M6 retain their existing policy. The selection does not expand to M1
|
||||
base/Pro/Ultra or to future generation names.
|
||||
|
||||
This widens device selection, not the kernel arithmetic or GGUF formats.
|
||||
Local validation uses M1 Max. An independent M4 Max Q2 resident test reported
|
||||
about 7% more decode throughput after widening the decode gates, with matching
|
||||
outputs; it did not validate M2/M3, other M4 variants, or SSD performance.
|
||||
See the [M4 Max test report](https://github.com/antirez/ds4/pull/1056#issuecomment-5922543210).
|
||||
Device eligibility therefore does not imply a measured speedup on every chip.
|
||||
|
||||
## DGX Spark performance
|
||||
|
||||
Measured on a single Spark with resident weights and disk-only n-grams:
|
||||
|
||||
| Model | First 1024 tokens | Next 7168 tokens | Decode at 8192 tokens |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| Q2 | 516 t/s | 745 t/s | 22.6 t/s |
|
||||
| Q4 | 513 t/s | 755 t/s | 21.2 t/s |
|
||||
|
||||
These are averages of two runs using `speed-bench/promessi_sposi.txt`,
|
||||
8192-token prefill chunks and 128 teacher-forced decode tokens, without MTP.
|
||||
Loading the model is excluded. Q2 also reached about 771 t/s on a fresh
|
||||
32K-token prefix with `--prefill-chunk 32768`. Small continuations have lower
|
||||
throughput: adding 32 tokens after an 8K Q4 prefix took about 273 ms.
|
||||
MTP speed depends on how often drafts are accepted; the table measures
|
||||
ordinary decoding.
|
||||
|
||||
## Conversion
|
||||
|
||||
See [GGUF conversion](../gguf-tools/README.md) for conversion from the HF
|
||||
checkpoint and calibrated low-bit expert quantization.
|
||||
`gguf-tools/qwen4_pack_to_qwen4exp.py` migrates old DS4 fast packs to the
|
||||
active `qwen4exp` schema. Finish older main-only conversions with
|
||||
`gguf-tools/qwen4_native_ngrams.py`, using the original HF n-gram shards.
|
||||
Expanding an old quantized table to BF16 does not restore its lost precision.
|
||||
|
||||
## Vision
|
||||
|
||||
Images go through the model's Qwen3-VL vision tower. Download the encoder
|
||||
with `./download_model.sh qwen38-vision`, then use
|
||||
`--vision gguf/mmproj-Qwen3.8-Flash-Next-Q8_0.gguf`. The CLI accepts
|
||||
`/read image.png`; the server accepts `image_url` parts. Each image is resized to
|
||||
multiples of 32 pixels within 64 to 1024 tokens (`DS4_QWEN4_IMAGE_MAX_TOKENS`
|
||||
raises the cap), encoded on the GPU, and takes the model's 3D rope positions;
|
||||
live KV reuse keys on the image fingerprints. `make test-qwen4-vision`
|
||||
compares the tower with the Hugging Face implementation using the same GGUF
|
||||
weights, dequantized to float32. It separately reports GGUF-versus-original
|
||||
checkpoint quality and GPU-versus-original agreement. Both comparisons use
|
||||
the unchanged minimum per-token cosine threshold of 0.99; implementation
|
||||
parity gates the default exit status. Add `--require-quality` to also fail on
|
||||
GGUF-versus-original quality loss. A passing implementation check alone does
|
||||
not mean the quantized encoder matches the original checkpoint.
|
||||
|
||||
For a small suite including OCR, diagrams, a photograph, and resizing:
|
||||
|
||||
```sh
|
||||
make tests/test_qwen4_vision
|
||||
uv run --with numpy --with torch --with torchvision --with pillow \
|
||||
--with safetensors --with transformers --with gguf tests/qwen4_vision_ref.py \
|
||||
--snapshot /path/to/HF-checkpoint \
|
||||
--mmproj gguf/mmproj-Qwen3.8-Flash-Next-Q8_0.gguf \
|
||||
--image tests/vision-fixtures/qwen38/orbit.png \
|
||||
--image tests/vision-fixtures/qwen38/maple.png \
|
||||
--image tests/vision-fixtures/glm53/diagram.png \
|
||||
--image tests/vision-fixtures/glm53/text.png \
|
||||
--image tests/vision-fixtures/glm53/earth.jpg \
|
||||
--image tests/vision-fixtures/glm53/screenshot.png \
|
||||
--json-report /tmp/qwen38-vision-parity.json
|
||||
```
|
||||
|
||||
Transformers must include `qwen4_exp`; the reference runs on CPU. Repeat
|
||||
`--image` to add cases. The original single-image `--out` embedding dump is
|
||||
still supported. Metric checks without model weights run with
|
||||
`uv run --with numpy python -m unittest discover -s tests -p test_qwen4_vision_ref.py`.
|
||||
JPEG decoder agreement with Pillow, including progressive scans, subsampling,
|
||||
and image edges, is checked separately with
|
||||
`uv run --with numpy --with pillow python -m unittest discover -s tests -p test_jpeg_decode.py`.
|
||||
|
||||
To check CLI `/read` image turns and text follow-ups with ordinary and MTP
|
||||
decode, use two different PNG or JPEG images with the model-backed regression:
|
||||
|
||||
```sh
|
||||
make ds4
|
||||
uv run tests/test_qwen4_cli_vision.py --model /path/to/main-with-mtp.gguf \
|
||||
--vision gguf/mmproj-Qwen3.8-Flash-Next-Q8_0.gguf \
|
||||
--image /path/to/first.png --image /path/to/second.png
|
||||
```
|
||||
|
||||
The test checks that all four turns complete in each mode, including errors
|
||||
that the interactive CLI can report without a nonzero process exit. It saves
|
||||
the responses and diagnostics for inspection; it does not grade image content.
|
||||
|
||||
The Metal and CUDA graphs accept Q8_0, Q4_0, F16, BF16 and F32
|
||||
dense weights, Q8_0/MXFP4/Q4_0/Q4_K/Q2_K/IQ2_XXS experts, F16/F32/Q8_0
|
||||
hyper-connection mixers and the original BF16 n-gram table.
|
||||
Tensor parallelism and pipeline execution are not implemented for this model
|
||||
yet. SSD expert streaming is supported on Metal only. ROCm is not supported.
|
||||
CPU code is a correctness reference, not a general inference backend.
|
||||
|
||||
|
||||
M6 uses the same Metal dispatch defaults as M5, including the Qwen indexer,
|
||||
attention merge, HC mixers and MoE scheduling. Existing M5 diagnostic rollback
|
||||
switches apply to both generations; tensor kernels still require runtime
|
||||
capability support. The F16 HC mixers explicitly preserve their accumulation
|
||||
order in both normal and safe shader math modes. This does not guarantee
|
||||
bit-identical logits across GPU generations: tensor matmuls retain their own
|
||||
accumulation order.
|
||||
|
||||
## Validation
|
||||
|
||||
```sh
|
||||
make test-qwen4-kernels test-qwen4-q2 test-qwen4-prefill-reuse test-q8-prefill-variants
|
||||
make test-qwen4-ssd-experts test-qwen4-memory
|
||||
make test-qwen4-hc-math test-metal-device-policy
|
||||
make test-frontends
|
||||
make test-qwen4-ngrams
|
||||
make tests/test_qwen4_ngram_state
|
||||
make -B -C gguf-tools quants-shared
|
||||
python3 -m unittest discover -s gguf-tools/tests -p test_qwen4_pack.py
|
||||
python3 -m unittest discover -s gguf-tools/tests -p test_qwen4_native_ngrams.py
|
||||
```
|
||||
|
||||
The kernel tests exercise the active Metal API. Vision and end-to-end model
|
||||
checks additionally require the checkpoints described above.
|
||||
The SSD test needs no GGUF: a sparse temporary file uses a 512-expert table,
|
||||
including indices 383, 384 and 511, with cold and warm selections, eviction,
|
||||
prefill staging, different-size MTP layers and failed reads followed by exact recovery. It
|
||||
compares routed intermediates, reduced outputs and hyper-connection residuals
|
||||
bit for bit against the resident kernels, including the padded Q2 down rows.
|
||||
Its test-only backend checks actual `pread` requests against selected expert
|
||||
ranges and counts returned bytes: ten experts for off-size MTP, 24 for cache
|
||||
overflow, zero reads for cache hits, and the full 512-expert fallback.
|
||||
Single-token tests also cover partially cached selections, duplicate IDs,
|
||||
optional shared experts and all 16 routed slots. A failed SSD read must leave
|
||||
the completed cached/shared gate/up slots exact, preserve the untouched slots,
|
||||
and allow an exact retry after draining GPU work.
|
||||
The memory-estimate test runs without a GPU or model.
|
||||
|
||||
To compare ordinary one-shot generation with the session path at identical
|
||||
prefill boundaries, including every vocabulary logit during continuation:
|
||||
|
||||
```sh
|
||||
make tests/test_qwen4_generation
|
||||
./tests/test_qwen4_generation gguf/Qwen3.8-Flash-Next-Q2.gguf --ssd-streaming
|
||||
# Exercise sparse attention and many chunk boundaries with a longer fixture:
|
||||
./tests/test_qwen4_generation gguf/Qwen3.8-Flash-Next-Q2.gguf \
|
||||
--ssd-streaming --chunk 128 --ctx 8192 --tokens 5760
|
||||
```
|
||||
|
||||
Resident and SSD inference use the same arithmetic for prompt projections,
|
||||
attention and shared experts. `make test-qwen4-resident-arithmetic` checks
|
||||
these operations against the resident reference without loading a GGUF.
|
||||
On a Mac that can hold the model, add `--prefill-reference` to the streamed
|
||||
generation test to compare every vocabulary score with a resident run.
|
||||
|
||||
Omit `--ssd-streaming` on a machine that can keep the model resident. The
|
||||
test checks explicit chunk precedence, progress frontiers, greedy tokens and
|
||||
full logits. Different chunk sizes can change reduction order and routed
|
||||
expert choices; compare the actual chunk boundaries before diagnosing token
|
||||
drift. In particular, upstream `8db1d1d` ignores `--prefill-chunk` in the
|
||||
ordinary one-shot Qwen path, although its sessions honor the option. For an
|
||||
A/B comparison with that revision, also set `DS4_QWEN4_PREFILL_CHUNK` to the
|
||||
same value in both binaries. This branch uses an explicit chunk in preference
|
||||
to that environment setting and clamps it to the context size.
|
||||
|
||||
On CUDA, use `make test-qwen4-cuda` for the kernel tests. They compare the
|
||||
active kernels with independent CPU references, without model weights.
|
||||
Vision and end-to-end checks additionally require the checkpoints above.
|
||||
Run `tests/test_qwen4_ngram_state MODEL.gguf` on either backend to check
|
||||
failed disk reads during prefill, decode and MTP, then exact recovery.
|
||||
For a smaller memory footprint, run
|
||||
`tests/test_qwen4_ngram_state MODEL.gguf --ssd-streaming`. This uses a
|
||||
256-token context, an eight-token prefill chunk and a 1024-expert cache request
|
||||
while keeping both comparison sessions and MTP enabled.
|
||||
|
||||
To compare the predictor's cache-only prefill with a complete MTP pass:
|
||||
|
||||
```sh
|
||||
make tests/test_qwen4_mtp_prefill
|
||||
./tests/test_qwen4_mtp_prefill gguf/Qwen3.8-Flash-Next-Q2.gguf --ssd-streaming
|
||||
```
|
||||
|
||||
This checks one-token prompts, chunk boundaries, non-argmax continuation,
|
||||
stale cache contents and payload restore, followed by greedy, depth-three
|
||||
and exact-sampling continuation checks. It uses a 128-token context and a
|
||||
1024-expert cache request. The small-buffer MTP CLI regression also accepts
|
||||
`--ssd-streaming`.
|
||||
|
||||
For the sparse-attention boundary and a 17408-token prefix, use the longer
|
||||
predictor regression:
|
||||
|
||||
```sh
|
||||
./tests/test_qwen4_mtp_prefill gguf/Qwen3.8-Flash-Next-Q2.gguf \
|
||||
--ssd-streaming --long-context --prefill-chunk 2048 \
|
||||
--dump-prefix /tmp/qwen-mtp-long-2048
|
||||
```
|
||||
|
||||
Repeat with `--prefill-chunk 128` and a different dump prefix. The fixture
|
||||
uses repeated, fixed chat token IDs, one graph, a 17416-token context and a
|
||||
1024-expert cache request. It runs the trunk once and probes actual residual
|
||||
rows across the first sparse query and at the final prefix. Each probe
|
||||
compares cache-only preparation, the optimized last-row predictor and a
|
||||
recursive draft with the frozen full-layer reference, using identical input
|
||||
rows and lookahead tokens. It checks complete logits, cache contents, EH
|
||||
projections, residuals and unchanged trunk state for T1/T2/T3 and exact T3
|
||||
verification. Destination cache rows are poisoned before each replay.
|
||||
Bit-identical comparisons are between the candidate and frozen reference
|
||||
replayed from the same saved predictor state within each run. Equality between
|
||||
chunk sizes is checked separately: chunk-dependent grouping can change the
|
||||
arithmetic used to build the historical predictor cache.
|
||||
|
||||
`--dump-prefix` writes the fixture IDs, target/trunk captures, reference and
|
||||
candidate predictor logits, and a JSONL manifest for comparisons between
|
||||
builds. `--prefix-tokens N` changes the fixture length; it must be a multiple
|
||||
of four covering the sparse boundary. These are implementation checks on a
|
||||
synthetic sequence, not a quality evaluation or an acceptance-rate benchmark.
|
||||
Comparing MTP acceptance between commits additionally requires the same
|
||||
prompt, sampling settings and draft-depth policy. A trunk arithmetic change
|
||||
can alter predictor inputs even when the predictor itself remains correct.
|
||||
|
||||
On an M1 Max with 32 GiB and Q2 SSD streaming, the 17408-token fixture passed
|
||||
at chunks 128 and 2048 on `11811b9`, and at chunk 2048 on `04c0867` and
|
||||
`89986c3`. At chunk 2048, all 36 captured tensors from `89986c3` and `11811b9`
|
||||
were byte-identical. Differences from `04c0867` already appeared in the trunk
|
||||
residuals after the attention fix in `89986c3`; both predictor implementations
|
||||
followed those changed inputs.
|
||||
|
||||
Between chunks 128 and 2048 on `11811b9`, the two captured trunk slices and
|
||||
final target logits were byte-identical. Predictor logits differed by at most
|
||||
0.0001266003, with no argmax change in the 16 full/recursive comparisons. A
|
||||
separate replay of identical rows as T3, T1+1+1, T2+1 and T1+2 found arithmetic
|
||||
differences beginning at the HC down projection; its per-stage differences
|
||||
were identical on `04c0867` and `11811b9`. This identifies an existing source
|
||||
of grouping-dependent predictor cache values, not a demonstrated quality or
|
||||
acceptance regression. The full-model runs above do not exercise the
|
||||
8192-row half-operand path.
|
||||
|
||||
The independent `test_metal_qwen4_moe_half` comparison against the shader
|
||||
source from `89986c3` also passed 31 fixtures, including 8192 rows, with Metal
|
||||
API validation. Full shader validation exceeded the M1 threadgroup-memory
|
||||
limit for the NT8 gate/up stress case (36864 bytes reported, 32768 available).
|
||||
A separate NT1/2/4 subset passed with shader validation, including the
|
||||
8192-row fixture at mid NT4/down NT4. These kernel checks do not replace an
|
||||
8192-chunk full-model acceptance comparison.
|
||||
|
||||
Official Alibaba continuations are tracked for 100 short prompts and 12
|
||||
archive/code prompts from 2K to 24K tokens. Build the quality scorer, then run
|
||||
from the repository root:
|
||||
|
||||
```sh
|
||||
gguf-tools/quality-testing/score_official MODEL.gguf \
|
||||
gguf-tools/quality-testing/data/qwen38-flash-alibaba-100/manifest.tsv /tmp/qwen-short.tsv 4096
|
||||
gguf-tools/quality-testing/score_official MODEL.gguf \
|
||||
gguf-tools/quality-testing/data/qwen38-flash-alibaba-long/manifest.tsv /tmp/qwen-long.tsv 32768
|
||||
```
|
||||
|
||||
Repeat with `--quality` and, for the long set, `--continued-prefill 1` and
|
||||
`--continued-prefill 256`. These fixtures match the no-thinking template;
|
||||
no rendered-prompt flag is needed. See [quality testing](../gguf-tools/quality-testing/README.md)
|
||||
for collection settings, measurements and the hosted-checkpoint limitations.
|
||||
|
||||
[Checkpoint-fix benchmark charts and measurements](../speed-bench/qwen38-checkpoints/README.md)
|
||||
compare prefill, ordinary decode, and MTP decode against the preceding PR head.
|
||||
|
||||
Qwen prefill checkpoints include matching logits at each completed chunk,
|
||||
including cancellation frontiers. To test save/restore and continuation:
|
||||
|
||||
```sh
|
||||
DS4_TEST_MODEL=/path/to/main-with-mtp.gguf \
|
||||
DS4_TEST_GLM_MTP=1 DS4_TEST_MTP_EXACT=1 \
|
||||
./ds4_test --qwen4-prefill-checkpoints --qwen4-restore-reuse \
|
||||
--session-snapshot --session-rewind --session-rewind-resample
|
||||
python3 tests/test_qwen4_checkpoint_replay.py \
|
||||
--model /path/to/main-with-mtp.gguf
|
||||
python3 tests/test_qwen4_mtp_limits.py \
|
||||
--model /path/to/main-with-mtp.gguf
|
||||
python3 tests/test_qwen4_logit_dump.py \
|
||||
--model /path/to/main-with-mtp.gguf
|
||||
```
|
||||
|
||||
The server regression checks continued disk saves, cache-budget eviction,
|
||||
and text-answer history with and without tool schemas. It covers clients
|
||||
that omit reasoning and clients that echo `reasoning_content`, including
|
||||
server restart after a continuation. For tools-enabled histories, the first
|
||||
answer keeps the exact disk key; after a client continues without reasoning,
|
||||
the next checkpoint uses the visible history key for restart reuse.
|
||||
|
||||
The MTP tests cover small prefill buffers, rewinds, reused sessions, and
|
||||
truncated checkpoints. The logit-dump test requires NumPy and compares all-row
|
||||
prefill with teacher-forced decode across a chunk boundary. For official
|
||||
continuation scoring, use `--rendered-prompt`
|
||||
when a fixture already contains the complete model chat template.
|
||||
|
||||
`tests/test_qwen4_prefill MODEL PROMPT 8192` checks mixed prefill sizes,
|
||||
progress callbacks and exact replay through the sparse-attention boundary.
|
||||
It also reports differences against a fresh prefill followed by individual
|
||||
decodes. Those schedules can round differently and exchange nearly tied
|
||||
experts; their full logits need not match. Use model-quality checks as well,
|
||||
not only state replay:
|
||||
|
||||
```sh
|
||||
python3 tests/test_server_story.py --url http://127.0.0.1:8000 \
|
||||
--model qwen3.8-flash-next --output /tmp/qwen-story
|
||||
```
|
||||
|
||||
Start the server with at least `--ctx 49152`. This checks all sixteen facts
|
||||
in a 31K-token story, then a correction turn that must reuse the prefix.
|
||||
|
||||
Qwen directional steering and activation capture support all 48 trunk layers;
|
||||
see [directional steering](../dir-steering/README.md). Model-backed regressions:
|
||||
|
||||
```sh
|
||||
python3 tests/test_qwen4_steering.py --model /path/to/main-with-mtp.gguf
|
||||
./ds4_agent_test --full-context-save /path/to/main-with-mtp.gguf
|
||||
make test-web-recovery
|
||||
```
|
||||
|
||||
The agent can save an already-full text transcript without a KV payload.
|
||||
Restart with a larger `--ctx`, then `/switch` to the saved session and
|
||||
`/compact` to recover room. Sessions containing images still use the existing
|
||||
save restriction. Automatic compaction reserves summary space and continues
|
||||
interrupted generation through the existing agent compaction path.
|
||||
@@ -0,0 +1,149 @@
|
||||
# Qwen Q4_K raw-load register reuse
|
||||
|
||||
Measured on 9 October 2026: warmed fresh 16K prefill improved **2.38%** on the
|
||||
standard text fixture and **2.27%** on C source, with exact old/new output checks.
|
||||
|
||||
## Change and provenance
|
||||
|
||||
The NAX gate/up and down lookahead loops previously reloaded a Q4_K header and
|
||||
quant chunk for every 32-weight group. The new next-step loader retains the
|
||||
header within each 256-weight superblock and the quant words across each
|
||||
low/high-nibble pair. Initial loads and non-Q4 loaders are unchanged. The
|
||||
existing dequantizer, arithmetic order, barriers and dispatch geometry are
|
||||
unchanged. No additional buffer or runtime flag is introduced.
|
||||
|
||||
Inspired by the Q4 portion of [StarForge 08ea4d7](https://github.com/Chida82/sf-q3-8flash/commit/08ea4d7eb2e780e2d31690766600880e8dea8718).
|
||||
Only this register reuse is adopted; the donor's other optimizations are excluded.
|
||||
|
||||
The measured host baseline was patched-source commit
|
||||
`b51ae1ce23a59e5e5f7031c0144e76ac35f13e6c`. It already included
|
||||
[PLE prefetch #1154](https://github.com/antirez/ds4/pull/1154), common to both arms.
|
||||
**These gains are incremental to #1154, not a measurement of that PR.** The PR
|
||||
is based on upstream `fc80bd695da76ee14bcb86820bf900a5cdfda806`; its unmodified
|
||||
shader, kernel test and Metal host source match the campaign baseline. The
|
||||
complete upstream host integration was built separately, but was not rerun
|
||||
through the full model campaign.
|
||||
|
||||
Shader SHA256:
|
||||
|
||||
- Control: `13224aeae23802defef678877146ae8047a45c2d1be824361efa2f4f5aa25b28`
|
||||
- Candidate: `dff0b9e48e5d55b68a818d4ccc28ea3468ecd464b8eff83cf426dcb9a102b4cf`
|
||||
|
||||
## Measurement
|
||||
|
||||
M5 Max, 128 GiB, macOS 26.6.2, resident Qwen3.8 Flash Next Q4 GGUF, Q4_K routed
|
||||
gate/up and MXFP4 routed down, 8192-token chunks, native context allocation
|
||||
262144, no batching. Each process warmed weights and a disposable complete
|
||||
prefill/decode session, freed that session, then measured fresh KV on the same
|
||||
engine/source. This avoids first-use specialized Metal pipeline compilation.
|
||||
Only one model process/session ran at a time. Timed runs did not add stage
|
||||
synchronization or state dumps. Decode measurements use 64 ordinary greedy
|
||||
tokens; they do not measure MTP throughput.
|
||||
|
||||
| Corpus | Runs / balanced blocks | Prefill throughput gain | Conditional 95% block-bootstrap interval | Ordinary decode change |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Standard text, 16384 tokens | 16 / 4 | +2.376% | +2.172 to +2.660% | -0.061% |
|
||||
| C source, 16384 tokens | 8 / 2 | +2.274% | +2.147 to +2.401% | -0.622% |
|
||||
|
||||
Each block uses ABBA or BAAB (A=control, B=candidate). Its ratio is mean B time
|
||||
/ mean A time; the summary is the geometric mean of block ratios, converted
|
||||
with `gain = 100 * (1 / ratio - 1)`. The interval enumerates ordered block
|
||||
resamples with replacement. It is conditional on these few blocks, not a
|
||||
complete estimate of host uncertainty. A warmed A/A block showed a false
|
||||
-1.83% difference, and each arm's text prefill times drifted about 6–7%
|
||||
first-to-last. All four text blocks and both code blocks favored the candidate.
|
||||
Cold-process results are excluded from this estimate. Do not extrapolate this
|
||||
gain to every context length.
|
||||
|
||||
Historical model SHA256:
|
||||
`680944460a8cbe93ba8b6d7b6107213ffb7e22320bd913000e563ca0a0f25a8a`.
|
||||
The 177280286720-byte model was not rehashed for this campaign; its recorded
|
||||
size, inode and modification time matched the previously verified file.
|
||||
|
||||
### Per-run durations
|
||||
|
||||
Seconds reconstructed from CSV throughput rounded to two decimal places;
|
||||
printed precision below is not measurement precision. Each row measures
|
||||
16384 prefill tokens and 64 ordinary decode tokens.
|
||||
|
||||
| Corpus | Run | Arm | Block | Prefill seconds | Decode seconds |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Text | 1 | A | 1 | 10.618895 | 1.210974 |
|
||||
| Text | 2 | B | 1 | 10.405314 | 1.201652 |
|
||||
| Text | 3 | B | 1 | 10.576054 | 1.200075 |
|
||||
| Text | 4 | A | 1 | 10.947627 | 1.197829 |
|
||||
| Text | 5 | B | 2 | 11.428253 | 1.231954 |
|
||||
| Text | 6 | A | 2 | 11.613763 | 1.234806 |
|
||||
| Text | 7 | A | 2 | 11.543149 | 1.238151 |
|
||||
| Text | 8 | B | 2 | 11.247340 | 1.234806 |
|
||||
| Text | 9 | A | 3 | 11.488353 | 1.211892 |
|
||||
| Text | 10 | B | 3 | 11.199748 | 1.226054 |
|
||||
| Text | 11 | B | 3 | 11.201050 | 1.218351 |
|
||||
| Text | 12 | A | 3 | 11.421640 | 1.221141 |
|
||||
| Text | 13 | B | 4 | 11.157419 | 1.220675 |
|
||||
| Text | 14 | A | 4 | 11.397010 | 1.217193 |
|
||||
| Text | 15 | A | 4 | 11.355461 | 1.213730 |
|
||||
| Text | 16 | B | 4 | 11.078879 | 1.218120 |
|
||||
| Code | 1 | A | 1 | 10.207209 | 1.199850 |
|
||||
| Code | 2 | B | 1 | 10.157848 | 1.220210 |
|
||||
| Code | 3 | B | 1 | 10.268109 | 1.225115 |
|
||||
| Code | 4 | A | 1 | 10.657300 | 1.221840 |
|
||||
| Code | 5 | B | 2 | 10.232773 | 1.218351 |
|
||||
| Code | 6 | A | 2 | 10.552621 | 1.219048 |
|
||||
| Code | 7 | A | 2 | 10.505931 | 1.217193 |
|
||||
| Code | 8 | B | 2 | 10.331954 | 1.224646 |
|
||||
|
||||
## Correctness and regression checks
|
||||
|
||||
Campaign checks passed on the patched host baseline and final local integration:
|
||||
|
||||
- The focused fixture directly byte-compared 87,492,020 old/new output bytes,
|
||||
covering both changed call sites, NAX levels 0–6, Q4/Q4, Q4/MXFP4, all-MXFP4
|
||||
and IQ2XXS/Q2_K, K512/2560, signed/zero finite Q4 headers, shuffled routes and
|
||||
counts around 8/16/32/64 boundaries. Live values must be finite and nonzero;
|
||||
reserved output slots and trailing write sentinels must remain untouched.
|
||||
- All 24 warmed runs matched each corpus's full 248320-float frontier row.
|
||||
- Complete persisted payloads at 16384 and 16896 tokens, save/load/resave,
|
||||
128 ordinary decode vocabulary rows, token IDs and positions matched exactly.
|
||||
- Near-native retained prefixes at 131072, 245760 and 246272 tokens matched
|
||||
full frontier logits, including the final 512-token append. These are
|
||||
correctness observations, not statistically repeated speed measurements.
|
||||
- `make`, `make test`, the full Qwen kernel suite, PLE rollback/prefetch/failure
|
||||
tests, checkpoint/restore/snapshot/rewind with opportunistic MTP, ordinary/MTP
|
||||
vision completion tests and synthetic Metal resident/SSD Q4 comparisons passed.
|
||||
- An isolated 11-request sampled tool/history/restart fixture passed at
|
||||
temperature 0.6 with non-exact MTP: 102 verification cycles, 94 drafts accepted.
|
||||
This bounded smoke is not a multi-hour agent soak or an MTP speed comparison.
|
||||
|
||||
CUDA sources are untouched; no external CUDA or distributed runtime test is
|
||||
claimed. The finite fixture is not exhaustive over all possible bit patterns.
|
||||
Serialized state comparisons do not cover every ephemeral MTP scratch buffer.
|
||||
|
||||
The existing masked-row loaders can form inactive weight-row addresses past a
|
||||
65-row logical tensor; this is present in both arms. Output sentinels check
|
||||
writes, not logical weight-read bounds. Register reuse preserves/subsets those
|
||||
read addresses; deployed row dimensions are multiples of 64. A bounds repair
|
||||
needs separate validation and is not included in this patch.
|
||||
|
||||
### Reproduce the focused exact comparison
|
||||
|
||||
On M5, from the PR checkout, run the same test executable in separate processes
|
||||
against the baseline and candidate shaders. A fixture pass alone is not the
|
||||
exactness oracle; compare the complete dumps. Run GPU/model tests only when the
|
||||
managed server is free and exclusively reserved.
|
||||
|
||||
```sh
|
||||
make tests/test_qwen4_kernels
|
||||
git show fc80bd695da76ee14bcb86820bf900a5cdfda806:metal/qwen4.metal > /tmp/qwen4-control.metal
|
||||
DS4_METAL_QWEN4_SOURCE=/tmp/qwen4-control.metal DS4_TEST_QWEN4_RAW16_ONLY=1 DS4_TEST_QWEN4_RAW16_DUMP=/tmp/qwen4-control.raw ./tests/test_qwen4_kernels
|
||||
DS4_METAL_QWEN4_SOURCE="$PWD/metal/qwen4.metal" DS4_TEST_QWEN4_RAW16_ONLY=1 DS4_TEST_QWEN4_RAW16_DUMP=/tmp/qwen4-candidate.raw ./tests/test_qwen4_kernels
|
||||
cmp /tmp/qwen4-control.raw /tmp/qwen4-candidate.raw
|
||||
```
|
||||
|
||||
Expected dump size: 87492020 bytes; expected SHA256 for each:
|
||||
`6cd1882c9ede246f78cad98f0e6d13b63a045a5d0ba332e186e25e57e5dba554`.
|
||||
|
||||
Timing used a diagnostic warmed-session harness rather than the stock first
|
||||
prefill invocation. Raw local manifests, shaders, dumps and generator scripts
|
||||
are retained outside the PR; the durations above allow the reported timing
|
||||
ratios to be independently recalculated.
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
# Serving Models
|
||||
|
||||
[README](../README.md) | [Client setup](CLIENTS.md)
|
||||
|
||||
## Start locally
|
||||
|
||||
```sh
|
||||
./ds4-server --ctx 32768
|
||||
```
|
||||
|
||||
The default address is `http://127.0.0.1:8000`. Use `--host 0.0.0.0` to listen
|
||||
on other interfaces. Restrict access to trusted clients; for an Internet-facing
|
||||
deployment, put authentication and TLS in front of the server.
|
||||
|
||||
`--cors` enables browser cross-origin headers. It does not change the listening
|
||||
address or provide access control.
|
||||
|
||||
The selected build chooses the backend. Pass `-m FILE` for an explicit model.
|
||||
Use `--chdir /path/to/ds4` when starting outside the project directory so
|
||||
relative runtime files such as Metal kernels can be found.
|
||||
|
||||
## APIs
|
||||
|
||||
| Endpoint | Use |
|
||||
| --- | --- |
|
||||
| `GET /v1/models` | Loaded model information |
|
||||
| `POST /v1/chat/completions` | OpenAI-style chat |
|
||||
| `POST /v1/responses` | Responses-style requests and continuations |
|
||||
| `POST /v1/completions` | Text completions |
|
||||
| `POST /v1/messages` | Anthropic-style messages |
|
||||
|
||||
The Flash and PRO names accepted by the model endpoints are compatibility
|
||||
aliases, not separate loaded models. The GGUF passed at startup selects the model.
|
||||
|
||||
`GET /v1/models` and `GET /v1/models/{id}` include `architecture.input_modalities` and `architecture.output_modalities`. Input modalities are `["text", "image"]` when the loaded engine has vision enabled, otherwise `["text"]`; output modalities are always `["text"]`. Compatibility aliases report the same loaded-engine capabilities.
|
||||
|
||||
```sh
|
||||
curl http://127.0.0.1:8000/v1/chat/completions \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"Explain Redis streams."}],"stream":true}'
|
||||
```
|
||||
|
||||
Chat, Responses, and Anthropic support tools and SSE streaming. Reasoning is
|
||||
returned separately from visible text in each API's native form. Standard
|
||||
sampling and output-budget fields are supported; explicit request parameters
|
||||
take precedence over defaults.
|
||||
|
||||
The default sampling settings are temperature 1, top-p 1, and min-p 0.05.
|
||||
For DeepSeek, thinking is on by default. `reasoning_effort=max` selects Think
|
||||
Max only with sufficient context; otherwise it falls back to normal thinking.
|
||||
`xhigh` maps to normal thinking, not Think Max. Use `think:false`, a disabled
|
||||
thinking object, or a non-thinking model alias for direct answers.
|
||||
|
||||
## Multiple sessions
|
||||
|
||||
```sh
|
||||
./ds4-server --ctx 4096 --batched-session 4
|
||||
```
|
||||
|
||||
Without `--batched-session`, there is one resident session. With it, the server
|
||||
preallocates independent KV states and queues requests when all slots are busy.
|
||||
Choose context and slot count together: a context that fits once may not fit
|
||||
four times. Idle slots can be cached before reuse; active requests are not evicted.
|
||||
|
||||
Where the model supports it, the slots share one prefill workspace instead of
|
||||
each keeping its own, so an extra slot costs only its caches. That matters most
|
||||
for Qwen3.8 Flash Next, whose transients are sized by the prefill chunk rather
|
||||
than by the context: at the default chunk they run to several GiB per session.
|
||||
The startup line reports both figures.
|
||||
|
||||
| Backend/model | Decode execution |
|
||||
| --- | --- |
|
||||
| Metal, resident Flash | Native shared-expert/QKV batching where supported |
|
||||
| Metal, resident V4.1 Flash | Native decoding for 2-8 sessions |
|
||||
| Metal RDMA TP, V4.1 Flash | Native decoding for 3-8 sessions; ordered fallback for two |
|
||||
| Metal SSD streaming, V4.1 Flash | Ordered fallback |
|
||||
| Metal, GLM 5.2 | Ordered fallback |
|
||||
| Metal, GLM 5.3 | Native batching through 2051 visible tokens; ordered fallback afterward |
|
||||
| Metal, Qwen3.8 Flash Next with resident weights | Native batching of the shared work; recurrent state, caches and PLE history stay per session |
|
||||
| CUDA, supported multi-GPU Flash TP layout | Native grouped decode and mixed prefill/decode |
|
||||
| Metal, Qwen3.8 Flash Next with SSD expert streaming | Ordered fallback with per-session streaming workspaces |
|
||||
| Single-GPU CUDA, including Spark | Ordered fallback |
|
||||
|
||||
Fallback executes the rows separately. It provides concurrency and scheduling
|
||||
fairness, not the aggregate speedup of native batching. Native grouping may
|
||||
change floating-point reduction order slightly. V4.1 sessions containing images
|
||||
use the ordered fallback.
|
||||
|
||||
Long prefills yield to active decoders in bounded intervals, normally 128
|
||||
tokens. `--mixed-prefill-quantum N` changes that interval for testing. While
|
||||
nothing is decoding, a V4.1 slot prefills in the engine's own chunk.
|
||||
Session-batched serving uses ordinary target decoding, except Qwen3.8 on
|
||||
Metal, where `--mtp` also batches speculative decoding with resident weights
|
||||
and uses ordered speculative cycles with SSD expert streaming. Its
|
||||
`--mtp-exact-sampling` mode uses ordinary batches for nonzero-temperature
|
||||
requests. Other models do not use MTP/DSpark while session batching is active.
|
||||
For the eight-L40S example, see [CUDA GPUs](CUDA_MULTI_GPU.md#serve-multiple-users).
|
||||
|
||||
## Images
|
||||
|
||||
Start with the matching language GGUF and `--vision FILE`; see
|
||||
[model-specific instructions](MODELS.md#vision).
|
||||
|
||||
OpenAI chat and Responses accept inline PNG/JPEG data URIs. Anthropic accepts
|
||||
base64 image sources. Remote URLs and server-side file paths are rejected.
|
||||
Image blocks preserve their order in the request. The limit is 16 images and
|
||||
a 64 MiB HTTP body.
|
||||
|
||||
## Disk KV cache
|
||||
|
||||
Disk caching saves useful prefixes across slot reuse and server restarts:
|
||||
|
||||
```sh
|
||||
./ds4-server --ctx 100000 \
|
||||
--kv-disk-dir /tmp/ds4-kv --kv-disk-space-mb 8192
|
||||
```
|
||||
|
||||
Clients can resend complete conversation histories. The server first tries
|
||||
the live token prefix, then compatible rendered-text prefixes from disk, and
|
||||
prefills the new suffix. With multiple slots, each slot has its own live state;
|
||||
disk is the additional persistence layer, not the only way to retain sessions.
|
||||
|
||||
TP cache loading rebuilds the saved token prefix on both ranks. It is not an
|
||||
instantaneous restoration of both GPUs. Pipeline loading redistributes the
|
||||
saved layer state over its route.
|
||||
|
||||
Defaults are intended to avoid saving fragile token boundaries. For unusual
|
||||
workloads, the controls are `--kv-cache-min-tokens`,
|
||||
`--kv-cache-cold-max-tokens`, `--kv-cache-continued-interval-tokens`,
|
||||
`--kv-cache-boundary-trim-tokens`, and `--kv-cache-boundary-align-tokens`.
|
||||
Check `./ds4-server --help` for their defaults.
|
||||
|
||||
Quantization variants may share compatible prefixes. Add
|
||||
`--kv-cache-reject-different-quant` for same-quant reuse only.
|
||||
Cache files contain prompt text and model state: treat the directory as
|
||||
private. It is disposable; stop the server before clearing it.
|
||||
|
||||
## Tool history and debugging
|
||||
|
||||
For DeepSeek, the server preserves sampled DSML tool blocks and assigns
|
||||
unguessable tool IDs. Replaying those IDs avoids retokenizing a differently
|
||||
formatted JSON history. The bounded replay map can be stored in cache files.
|
||||
When exact replay is unavailable, canonical rendering may require rebuilding
|
||||
part of the prefix.
|
||||
|
||||
`--tool-memory-max-ids` bounds this map.
|
||||
`--disable-exact-dsml-tool-replay` disables it for diagnostic comparisons.
|
||||
Use `--trace /tmp/ds4-trace.txt` to record prompt rendering, cache decisions,
|
||||
generated text, and tool-parser events. Traces can contain sensitive content.
|
||||
|
||||
Cache formats are implementation details. The current header and extension
|
||||
definitions are in [ds4_kvstore.h](../ds4_kvstore.h) and
|
||||
[ds4_kvstore.c](../ds4_kvstore.c); model-specific payload handling is in
|
||||
[ds4.c](../ds4.c).
|
||||
@@ -0,0 +1,179 @@
|
||||
# Speculative Decoding
|
||||
|
||||
[README](../README.md)
|
||||
|
||||
Speculation proposes future tokens with a smaller draft block, then checks
|
||||
them with the main model. An accepted prefix advances generation by several
|
||||
tokens in one verification pass. It does not accelerate prefill.
|
||||
|
||||
It is opt-in. Gains depend on the prompt, model, backend, and context length;
|
||||
poor acceptance can make it slower. Measure your workload rather than assuming
|
||||
that a draft model always helps.
|
||||
|
||||
## DeepSeek Flash: DSpark
|
||||
|
||||
DSpark is a separate support GGUF, not a standalone language model. It proposes
|
||||
up to five future tokens. Match its checkpoint to the main model:
|
||||
|
||||
| Main checkpoint | Download | Support file |
|
||||
| --- | --- | --- |
|
||||
| Flash 0731 | `ds4f-dspark` | `gguf/DeepSeek-V4-Flash-DSpark-support-0731.gguf` |
|
||||
| Flash Vision Experimental | `ds4f-vision-dspark` | `gguf/DeepSeek-V4-Flash-Vision-Exp-DSpark-support.gguf` |
|
||||
|
||||
For the 0731 Q2 model:
|
||||
|
||||
```sh
|
||||
./download_model.sh ds4f-q2
|
||||
./download_model.sh ds4f-dspark
|
||||
./ds4 --dspark --mtp-model gguf/DeepSeek-V4-Flash-DSpark-support-0731.gguf
|
||||
```
|
||||
|
||||
For Vision Experimental, substitute its matching main model and support file.
|
||||
Do not mix the two checkpoints. DSpark is not supported for PRO.
|
||||
The same flags work in `ds4-agent` and non-batched `ds4-server` requests.
|
||||
|
||||
The support file adds about 5.6 GiB of weights plus runtime state. The main
|
||||
model must be resident; external DSpark is not supported with SSD streaming.
|
||||
DSpark replaces the legacy one-stage MTP drafter for that run; the two are
|
||||
not stacked.
|
||||
|
||||
Resident M5 paths batch supported verifier expert rows, including two-Mac TP.
|
||||
On DGX Spark, resident Flash 0731 Q2 and two-Spark MXFP4 also batch the seed
|
||||
with longer drafts and use small-batch Q8 and expert kernels. No extra flags
|
||||
are needed.
|
||||
Single-Spark Q2 uses a confidence-pruning threshold of 0.4. Two-Spark
|
||||
Flash 0731 Q2 and MXFP4 leave confidence pruning off and split the drafter
|
||||
over RDMA when both peers load its support file. `--dspark-confidence`
|
||||
overrides the threshold. Exact sampling keeps its default threshold of 0.8.
|
||||
The scheduler can back off when drafting is unproductive. Defaults select the
|
||||
fast paths; diagnostic environment variables are not needed for normal use.
|
||||
Recorded comparisons are in [the QA guide](../QA_BEFORE_RELEASES.md).
|
||||
|
||||
For the tested Strix Halo coding configuration, use `--dspark --dspark-confidence 0.7` with the default five-token draft cap and scheduler. Client sampling is temperature `1.0`, `top_p=0.95`, `min_p=0`, and `top_k=0`; high reasoning was also checked on coding and tool-use requests. This uses opportunistic sampling as described below; exact-mode throughput is not qualified by these measurements. `--mtp-draft` controls legacy autoregressive MTP, not the DSpark draft width.
|
||||
|
||||
## DeepSeek V4.1 Flash: DSpark
|
||||
|
||||
V4.1 has its own 8.04 GiB support GGUF. It retains the native MXFP4 experts
|
||||
and uses a different draft graph from V4 Flash. Do not interchange the files.
|
||||
See [conversion](../gguf-tools/README.md#convert-v41-dspark) to build it.
|
||||
|
||||
```sh
|
||||
./ds4 -m gguf/DeepSeek-V4.1-Flash-Q2.gguf \
|
||||
--ssd-streaming --dspark \
|
||||
--mtp-model gguf/DeepSeek-V4.1-Flash-DSpark.gguf
|
||||
```
|
||||
|
||||
The same options work with `ds4-agent` and `ds4-server`. Metal, CUDA and ROCm
|
||||
support single-host execution, with either resident target weights or SSD
|
||||
streaming. The drafter stays resident, so leave another 8 GiB for it.
|
||||
Two-host tensor parallelism works on Metal and CUDA: add the same `--dspark
|
||||
--mtp-model FILE` options to both the coordinator and worker. Each peer keeps
|
||||
the drafter resident; the target and its verification run across both GPUs.
|
||||
|
||||
Up to five drafts are checked together. The sampling choices below apply;
|
||||
`--mtp-exact-sampling` uses sampled proposals and the full acceptance/correction
|
||||
rule for V4.1. Restored sessions warm the draft window with 128 ordinary tokens
|
||||
before speculation resumes. Measure speed on your prompts: acceptance and SSD
|
||||
cache size both matter. V4.1 DSpark can still be slower than ordinary decoding,
|
||||
including in TP mode.
|
||||
|
||||
## GLM: built-in MTP
|
||||
|
||||
GLM's draft block is already in its main GGUF:
|
||||
|
||||
```sh
|
||||
./ds4 -m gguf/GLM-5.3-Flash-Q2.gguf --mtp
|
||||
```
|
||||
|
||||
`--mtp-timing` also enables it and prints acceptance and timing counters.
|
||||
The current GLM cycle commits up to two tokens. No external support file is
|
||||
needed, and ordinary decode remains the default.
|
||||
|
||||
## Qwen3.8: built-in MTP
|
||||
|
||||
Both Qwen downloads include MTP and native BF16 n-grams in the main GGUF:
|
||||
|
||||
```sh
|
||||
./download_model.sh qwen38-q4k
|
||||
./ds4 --mtp
|
||||
```
|
||||
|
||||
Ordinary decode uses the same file with `--mtp` omitted. For non-zero
|
||||
temperature, add `--mtp-exact-sampling` to preserve the target sampling
|
||||
distribution. See [Qwen setup](QWEN38_FLASH_NEXT.md) for Metal and CUDA.
|
||||
|
||||
For the Q2 model with Metal SSD streaming:
|
||||
|
||||
```sh
|
||||
./ds4 --metal -m gguf/Qwen3.8-Flash-Next-Q2.gguf \
|
||||
--ssd-streaming --ctx 4096 --prefill-chunk 128 \
|
||||
--mtp-timing --temp 0 --nothink -n 100 \
|
||||
-p "narrami la storia di roma"
|
||||
```
|
||||
|
||||
`--mtp-timing` enables MTP and reports verification cycles and first-draft
|
||||
acceptance. Measure generation throughput against the same command without
|
||||
that option: speculative decoding can lose time when too few proposals are
|
||||
accepted, especially when loading the predictor's experts from SSD.
|
||||
|
||||
Qwen's [official model configuration](https://huggingface.co/Qwen/Qwen3.8-Flash-Next/blob/main/config.json)
|
||||
contains one MTP layer with no dedicated token embeddings. The same layer
|
||||
can be called repeatedly to propose multiple tokens; the number of MTP
|
||||
layers is not the draft length. The
|
||||
[official blog](https://www.alibabacloud.com/blog/qwen3-8-flash-next-a-new-architecture-towardsultimate-cost-efficiency_603501)
|
||||
describes training this module over multiple steps and using QSA in it.
|
||||
These weights are already included in the GGUF; no separate draft model is
|
||||
needed.
|
||||
|
||||
With SSD streaming, prompt prefill prepares the predictor's K/V and indexer
|
||||
cache from the trunk hidden states and the next prompt token by default. It skips the predictor's
|
||||
attention output, MoE and vocabulary head, so this preparation does not
|
||||
stream predictor experts from SSD. The final hidden row is retained until
|
||||
the actual next token is known. Snapshot restore and speculative rollback
|
||||
retain that row too. This costs 40 KiB per row for this model, at most
|
||||
160 KiB including the three verifier snapshots. Qwen payload version 3
|
||||
stores the initialized prefix and retained row. Version 2 checkpoints and
|
||||
plain checkpoints restore their target state directly. When MTP is enabled
|
||||
without a saved predictor tail, the engine replays the saved tokens once
|
||||
before continuing. With resident weights, prefill skips historical predictor
|
||||
projections and initializes those cache rows to zero. MTP and target
|
||||
verification remain enabled. Preparing the full history can improve draft
|
||||
acceptance for long generations. Use `DS4_QWEN4_MTP_PREFILL=on` to enable
|
||||
it for resident weights too. [Qwen MTP settings](QWEN38_FLASH_NEXT.md)
|
||||
|
||||
The cycle drafts one token ahead by default and engages a **second, chained
|
||||
draft** (one extra nextn-layer step conditioned on the predictor's own
|
||||
stream, verified in a 3-row pass) while recent first-draft acceptance is
|
||||
perfect, disengaging after repeated second-draft rejections.
|
||||
`DS4_QWEN4_MTP_DEPTH=2` or `=3` fixes the depth;
|
||||
`0` (default) is the adaptive policy.
|
||||
|
||||
## Sampling and reproducibility
|
||||
|
||||
At temperature zero, accepted drafts must match the target's greedy
|
||||
continuation. At non-zero temperature, the default mode is opportunistic:
|
||||
ordinary tokens use the requested sampling settings, but matching greedy
|
||||
drafts are accepted directly. Sampling resumes when the proposed suffix does
|
||||
not match. This is deliberately more deterministic than ordinary sampling.
|
||||
|
||||
Use `--mtp-exact-sampling` to preserve the ordinary target sampling
|
||||
distribution. Greedy proposals are accepted with their target probability;
|
||||
sampled proposals use the target-to-draft probability ratio. Rejections sample
|
||||
from the remaining target probability mass.
|
||||
|
||||
When a verified block crosses a tool sampling-mode boundary (for example
|
||||
entering tool-call syntax during server decoding), the server rewinds to the
|
||||
block start and re-evaluates the boundary token so the next sample uses the
|
||||
new mode. Under exact sampling that rewind restores a pre-verify snapshot of
|
||||
the recurrent state instead of resetting the graph, so long retained
|
||||
contexts are not replayed at every boundary.
|
||||
|
||||
Accepted tokens keep the state produced by the batched verifier. Floating-point
|
||||
reduction order can differ from one-token decode, so long greedy continuations
|
||||
need not be byte-identical. For DeepSeek comparisons against the ordinary
|
||||
target-only path, use `--quality` or `--dspark-strict`; these disable the
|
||||
speculative acceptance path. They do not promise identical output across
|
||||
different hardware or execution configurations.
|
||||
|
||||
Session-batched serving uses ordinary target decoding instead of combining
|
||||
DSpark/MTP with the session batch. See [serving](SERVER.md#multiple-sessions).
|
||||
@@ -0,0 +1,118 @@
|
||||
# Models Larger Than RAM
|
||||
|
||||
[README](../README.md)
|
||||
|
||||
Use resident inference when the model and runtime fit: it is faster.
|
||||
SSD streaming keeps a bounded cache of routed experts and reads missing
|
||||
experts from the GGUF. It trades speed for capacity; it does not remove the
|
||||
memory needed for other weights, activations, scratch, and the context.
|
||||
|
||||
Metal supports streaming for DeepSeek, GLM and [Qwen3.8](QWEN38_FLASH_NEXT.md).
|
||||
CUDA has streaming paths too, and ROCm supports GLM 5.2/5.3 streaming.
|
||||
Do not infer support for every model
|
||||
and tensor layout from the existence of the flag.
|
||||
|
||||
## Start with the automatic budget
|
||||
|
||||
```sh
|
||||
./ds4 -m ds4flash.gguf --ssd-streaming
|
||||
```
|
||||
|
||||
The startup report shows the effective cache, resident layers where applicable,
|
||||
and memory requirements. Prefer the automatic budget for a first run. Use a
|
||||
local SSD and leave memory for the OS and other applications.
|
||||
|
||||
Examples:
|
||||
|
||||
```sh
|
||||
# Flash Q2 on a smaller Mac.
|
||||
./download_model.sh ds4f-q2
|
||||
./ds4 --ssd-streaming --ctx 32768 --nothink
|
||||
|
||||
# GLM 5.3 Flash Q4 on a 128 GB Mac.
|
||||
./download_model.sh glm53-q4
|
||||
./ds4 --ssd-streaming --ctx 4096
|
||||
|
||||
# PRO Q2 on a 128 GB Mac: usable for inspection, but slow.
|
||||
./download_model.sh pro-q2-imatrix
|
||||
./ds4 --ssd-streaming --ctx 32768 --nothink
|
||||
```
|
||||
|
||||
Generation is usually more sensitive to cache misses than prefill. A large
|
||||
model that starts successfully can still be too slow for interactive work.
|
||||
Use a short generation before committing to a long task.
|
||||
|
||||
## Adjusting the cache
|
||||
|
||||
To leave more room for context or other sessions:
|
||||
|
||||
```sh
|
||||
./ds4 --ssd-streaming --ssd-streaming-cache-experts 32GB
|
||||
```
|
||||
|
||||
A byte budget is a target, not a guaranteed allocation. DwarfStar reserves
|
||||
routed-prefill headroom and fits the cache to the remaining model, graph,
|
||||
context, and backend budget. The effective value may be smaller than requested.
|
||||
Non-routed weights and KV state are additional to that expert-cache budget.
|
||||
|
||||
A plain number, such as `--ssd-streaming-cache-experts 4000`, requests dynamic
|
||||
expert slots rather than a byte budget. It is also subject to memory limits.
|
||||
|
||||
By default, GLM spends the cache budget on selected experts across all layers.
|
||||
`--ssd-streaming-full-layers N` reserves full routed prefix layers instead.
|
||||
Metal also keeps the non-routed weights resident when it can; they are needed
|
||||
by every token, and paging them back in delays generation after a prompt.
|
||||
An oversized expert cache can displace those weights and slow decoding.
|
||||
More cache helps only while the rest of the working set still fits.
|
||||
|
||||
More context and more server sessions consume more memory. Metal GLM budgeting
|
||||
includes planned and already resident sessions; a request can still be refused
|
||||
when its mandatory allocations cannot fit. Disabling the memory guard is not
|
||||
a remedy for insufficient RAM.
|
||||
|
||||
Leave expert preloading enabled for normal use. `--ssd-streaming-cold` and
|
||||
`--ssd-streaming-preload-experts N` are mainly useful for controlled measurements.
|
||||
See [benchmarking](PERFORMANCE.md) and the [release QA guide](../QA_BEFORE_RELEASES.md).
|
||||
|
||||
## CUDA expert reads
|
||||
|
||||
DeepSeek V4.1 CUDA prefill reads selected experts in file order and combines
|
||||
adjacent reads by default, instead of prefetching every expert in the next
|
||||
layer. Single-token decode retains its original read order, which avoids a
|
||||
slowdown with small expert caches. The weight formats, routing, and cache
|
||||
budget are unchanged; no extra flags are needed.
|
||||
|
||||
For comparisons on other storage or workloads,
|
||||
`DS4_CUDA_ENABLE_SSD_PREFETCH=1` restores whole-next-layer prefetch.
|
||||
`DS4_CUDA_DISABLE_SSD_SORT_SELECTED_READS=1` restores the original selected-read
|
||||
order. Set both to reproduce the previous policy.
|
||||
`DS4_CUDA_DISABLE_SSD_PREFETCH=1` still overrides prefetch enablement.
|
||||
For experiments, `DS4_CUDA_SSD_SORT_SELECTED_READS=1` also orders single-token
|
||||
loads; the explicit sort-disable switch takes precedence.
|
||||
Unset that old experimental sort flag to use the new prefill-only default.
|
||||
These switches use presence semantics: unset them to return to the default;
|
||||
setting a switch to `0` still enables it.
|
||||
|
||||
## M5 Max measurements
|
||||
|
||||
On a 128 GB M5 Max, with automatic cache sizing and no speculative decoding:
|
||||
|
||||
| Model | Initial prefill | Continued prefill | Generation after each | Runs |
|
||||
| --- | ---: | ---: | ---: | --- |
|
||||
| GLM 5.3 Flash Q4_K, 177.77 GiB | 121 t/s | 104 t/s | 11.9 / 14.9 t/s | Three-run median |
|
||||
| DeepSeek Flash Vision Exp MXFP4, 145.26 GiB | 300 t/s | 263 t/s | 11.9 / 19.3 t/s | Single run |
|
||||
|
||||
September 6, 2026. GLM used a 2K prompt and a 1K append; DeepSeek used 8K
|
||||
and a 4K append. Both generated 128 tokens at each frontier. DeepSeek's latest
|
||||
run uses an 86.2 GiB expert cache and updated eviction priorities; it is not
|
||||
directly comparable to the earlier 64-token measurement. Generation includes
|
||||
the first-token wait. Cache reuse depends on the prompt, so these are
|
||||
workload references, not a speed
|
||||
guarantee for every model larger than RAM.
|
||||
|
||||
Full GLM 5.3 IQ2_XXS (196.58 GiB) also benefits from caching short tool-result
|
||||
prompts. With an 8K context and a 61.35 GiB effective expert cache, a 16-token
|
||||
append fell from 30.8 to 2.9 seconds; generation afterward rose from 4.09 to
|
||||
5.11 t/s (three-run medians, 64 generated tokens). All compared logits and
|
||||
generated text were identical. This is a short-append improvement, not an
|
||||
equivalent gain in initial prefill or every generation workload.
|
||||
@@ -0,0 +1,101 @@
|
||||
# AMD Strix Halo
|
||||
|
||||
[README](../README.md) | [Getting started](../README.md#start-here)
|
||||
|
||||
The reference system is a 128 GB Strix Halo with Radeon 8060S (`gfx1151`),
|
||||
such as the Framework Desktop. The ROCm build uses the standard binary names
|
||||
and selects the ROCm backend by default.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
For a container setup, see the maintained
|
||||
[ROCm toolbox](https://github.com/kyuz0/strix-halo-ds4-toolbox/blob/main/toolboxes/Dockerfile.rocm-10.0).
|
||||
It can also be managed with
|
||||
[AI Toolbox Cockpit](https://github.com/kyuz0/ai-toolbox-cockpit).
|
||||
|
||||
For a native Ubuntu build you need HIP, hipBLAS, hipBLASLt, rocBLAS, rocWMMA,
|
||||
and hipCUB development files. The Ubuntu 26.04 setup used these packages:
|
||||
|
||||
```sh
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y hipcc rocminfo rocm-smi \
|
||||
libamdhip64-dev libhipblas-dev libhipblaslt-dev librocblas-dev \
|
||||
librocwmma-dev libhipcub-dev
|
||||
sudo usermod -aG render,video "$USER"
|
||||
```
|
||||
|
||||
Log out and back in after changing groups. `rocminfo` must report `gfx1151`
|
||||
and be able to open `/dev/kfd` before DwarfStar can run.
|
||||
|
||||
Some packaged rocWMMA headers omit `rocwmma/internal/`. If compilation fails
|
||||
there, install the complete headers matching your ROCm installation, or use
|
||||
the container. Do not mix header versions as a general workaround.
|
||||
|
||||
## GPU-visible memory
|
||||
|
||||
Check the memory pool reported by `rocminfo`. Some 128 GB configurations expose
|
||||
only about 62 GB to the GPU, which is insufficient for resident Flash Q2 plus
|
||||
runtime buffers. Firmware and kernel GTT/TTM settings control this limit.
|
||||
|
||||
The native reference setup used these memory parameters:
|
||||
|
||||
```text
|
||||
amdgpu.gttsize=126976 ttm.pages_limit=32505856 ttm.page_pool_size=32505856
|
||||
```
|
||||
|
||||
They are a system-specific starting point, not an allocation budget for
|
||||
DwarfStar. Preserve existing boot options and consult your kernel's settings
|
||||
before changing them. Keep RAM available for the OS even when the GPU can
|
||||
address most of it. Do not disable the IOMMU merely to copy another host's
|
||||
configuration; doing so changes device isolation.
|
||||
|
||||
## Build and run Flash
|
||||
|
||||
```sh
|
||||
make strix-halo
|
||||
./download_model.sh ds4f-q2
|
||||
./ds4 --rocm
|
||||
```
|
||||
|
||||
`make rocm` is an alias. Use the current 0731 Q2 download for a first run;
|
||||
larger mixed and Q4 models have substantially higher memory requirements.
|
||||
Flash's ROCm resident and pipeline paths should not be confused with the GLM
|
||||
SSD-streaming path.
|
||||
|
||||
## DeepSeek V4.1 Flash
|
||||
|
||||
Use Q2 with SSD streaming on a 128 GB machine:
|
||||
|
||||
```sh
|
||||
./download_model.sh ds41f-q2
|
||||
./ds4 --rocm -m gguf/DeepSeek-V4.1-Flash-Q2.gguf \
|
||||
--ssd-streaming --ctx 4096
|
||||
```
|
||||
|
||||
The 341 GiB GGUF includes disk-only Engram tables. Keep it on a fast local
|
||||
SSD and start with automatic cache sizing. The matching
|
||||
[V4.1 DSpark](SPECULATIVE_DECODING.md#deepseek-v41-flash-dspark) support file
|
||||
is optional. V4.1 vision and network TP are not supported on ROCm.
|
||||
Large prefills are not yet tuned on this backend and can be slow.
|
||||
|
||||
## GLM 5.3 Flash
|
||||
|
||||
The reference Q2 setup uses SSD streaming to leave room for its graph and KV
|
||||
state. Begin with automatic cache sizing and a small context:
|
||||
|
||||
```sh
|
||||
./download_model.sh glm53-q2
|
||||
./ds4 --rocm -m gguf/GLM-5.3-Flash-Q2.gguf \
|
||||
--ssd-streaming --ctx 4096
|
||||
```
|
||||
|
||||
GLM 5.2 also supports ROCm streaming. Full-model GLM 5.2 inference requires it;
|
||||
distributed layer slices can be resident. See [SSD streaming](SSD_STREAMING.md)
|
||||
before adjusting the cache budget.
|
||||
|
||||
Both GLM 5.3 Flash and DeepSeek Flash Vision Experimental support images on
|
||||
ROCm. Add the matching encoder with `--vision FILE`, as described in
|
||||
[models and vision](MODELS.md#vision).
|
||||
|
||||
For a model-free routed-kernel check, use `make test-mxfp4-rocm`.
|
||||
Full-model validation is described in [testing](TESTING.md).
|
||||
@@ -0,0 +1,66 @@
|
||||
# Testing and Development
|
||||
|
||||
[README](../README.md)
|
||||
|
||||
Read [CONTRIBUTING.md](../CONTRIBUTING.md) before proposing an inference change.
|
||||
The [release QA guide](../QA_BEFORE_RELEASES.md) defines the hardware matrix,
|
||||
checkpoint-matched quality tests, long-context agent tasks, and speed checks.
|
||||
It also records which checks were not completed. A smoke test is not full QA.
|
||||
|
||||
## Focused checks
|
||||
|
||||
Model-free checks include:
|
||||
|
||||
```sh
|
||||
make ds4_test ds4_agent_test test-session-state
|
||||
./ds4_test --server
|
||||
./ds4_agent_test
|
||||
```
|
||||
|
||||
On Metal, small GPU tensor tests are available without loading a full GGUF:
|
||||
|
||||
```sh
|
||||
make tests/test_session_state_gpu tests/test_glm53_kda tests/test_mxfp4_metal
|
||||
./tests/test_session_state_gpu
|
||||
./tests/test_glm53_kda
|
||||
./tests/test_mxfp4_metal
|
||||
```
|
||||
|
||||
`make test` also includes model-backed tests. Select the right GGUF and ensure
|
||||
that it fits before running it; do not accidentally load a large model on a
|
||||
single device during multi-GPU QA. ROCm has `make test-rocm`.
|
||||
|
||||
Official-vector tests must use continuations from the same checkpoint as the
|
||||
GGUF. Flash 0731 and Vision Experimental are not interchangeable fixtures.
|
||||
See [test vectors](../tests/test-vectors/README.md) and
|
||||
[quality scoring](../gguf-tools/quality-testing/README.md).
|
||||
|
||||
## Investigating output
|
||||
|
||||
```sh
|
||||
./ds4 --dump-tokens -p "..."
|
||||
./ds4 --dump-logprobs /tmp/out.json --logprobs-top-k 20 --temp 0 -p "..."
|
||||
./ds4 --dump-logits /tmp/logits.json --nothink --prompt-file prompt.txt
|
||||
./ds4-server --trace /tmp/ds4-trace.txt
|
||||
```
|
||||
|
||||
Token dumps catch template differences without inference. Logits and
|
||||
continuations help distinguish sampling changes from graph errors. Server
|
||||
traces include cache and tool-parser decisions. Keep traces private when they
|
||||
contain real conversations.
|
||||
|
||||
## Tools and source references
|
||||
|
||||
- [GGUF conversion and quantization](../gguf-tools/README.md)
|
||||
- [Imatrix collection](../gguf-tools/imatrix/README.md) and
|
||||
[calibration corpus](../gguf-tools/imatrix/dataset/README.md)
|
||||
- [Steering vectors](../dir-steering/README.md)
|
||||
- [Benchmark scripts and charts](../speed-bench/README.md)
|
||||
- [Evaluation data and licenses](../EVAL_DATA.md)
|
||||
- [Session payload implementation](../ds4.c) and
|
||||
[cache header definitions](../ds4_kvstore.h)
|
||||
- [Pipeline protocol](../ds4_distributed.c) and [TP protocol](../ds4_tp.c)
|
||||
|
||||
For changes to state handling, cover rewind/replay, save/load, images, and
|
||||
multiple sessions as well as a fresh prompt. For distributed changes, exercise
|
||||
both ranks and failures; a local command-parser test is not physical TP QA.
|
||||
+415
-6
@@ -4,7 +4,15 @@ set -e
|
||||
GLM_UNSLOTH_REPO="unsloth/GLM-5.2-GGUF"
|
||||
GLM_ANTIREZ_REPO="antirez/GLM-5.2-GGUF"
|
||||
GLM53_REPO="antirez/glm-5.3-flash-gguf"
|
||||
GLM53_FULL_REPO="antirez/glm-5.3-gguf"
|
||||
QWEN38_REPO="antirez/qwen3.8-flash-next-gguf"
|
||||
QWEN38_MMPROJ_REPO="ggml-org/Qwen3.8-Flash-Next-GGUF"
|
||||
REPO="antirez/deepseek-v4-gguf"
|
||||
DS41_REPO="antirez/deepseek-v4.1-flash-gguf"
|
||||
DS41_Q2_FILE="DeepSeek-V4.1-Flash-1017-IQ2_XXS-Q2_K-imatrix.gguf"
|
||||
DS41_Q4_FILE="DeepSeek-V4.1-Flash-Q4.gguf"
|
||||
DS41_MXFP4_BASE="DeepSeek-V4.1-Flash-1017-MXFP4-experts"
|
||||
DS41_VISION_FILE="DeepSeek-V4.1-Flash-Vision.gguf"
|
||||
DS4F_Q2_FILE="DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix-0731.gguf"
|
||||
DS4F_Q4_FILE="DeepSeek-V4-Flash-Q4KExperts-F16HC-F16Compressor-F16Indexer-Q8Attn-Q8Shared-Q8Out-chat-v2-imatrix-0731.gguf"
|
||||
DS4F_MXFP4_FILE="DeepSeek-V4-Flash-MXFP4Experts-F16HC-F16Compressor-F16Indexer-Q8Attn-Q8Shared-Q8Out-chat-v2-mxfp4-0731.gguf"
|
||||
@@ -13,16 +21,25 @@ PRO_Q2_IMATRIX_FILE="DeepSeek-V4-Pro-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-Instruct-
|
||||
PRO_Q4_LAYERS00_30_FILE="DeepSeek-V4-Pro-Q4K-Layers00-30.gguf"
|
||||
PRO_Q4_LAYERS31_OUTPUT_FILE="DeepSeek-V4-Pro-Q4K-Layers-31-output.gguf"
|
||||
DS4F_DSPARK_FILE="DeepSeek-V4-Flash-DSpark-support-0731.gguf"
|
||||
DS4F_VISION_Q2_FILE="DeepSeek-V4-Flash-Vision-Exp-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8.gguf"
|
||||
DS4F_VISION_Q2_Q4_FILE="DeepSeek-V4-Flash-Vision-Exp-Layers37-42Q4KExperts-OtherExpertLayersIQ2XXSGateUp-Q2KDown-AProjQ8-SExpQ8-OutQ8.gguf"
|
||||
DS4F_VISION_MXFP4_FILE="DeepSeek-V4-Flash-Vision-Exp-MXFP4Experts-F16HC-F16Compressor-F16Indexer-Q8Attn-Q8Shared-Q8Out.gguf"
|
||||
DS4F_VISION_ENCODER_FILE="DeepSeek-V4-Flash-Vision-Encoder.gguf"
|
||||
DS4F_VISION_DSPARK_FILE="DeepSeek-V4-Flash-Vision-Exp-DSpark-support.gguf"
|
||||
GLM_UNSLOTH_Q4_REMOTE_BASE="UD-Q4_K_XL/GLM-5.2-UD-Q4_K_XL"
|
||||
GLM_UNSLOTH_Q4_LOCAL_BASE="GLM-5.2-UD-Q4_K_XL"
|
||||
GLM_UNSLOTH_Q4_FIRST_FILE="$GLM_UNSLOTH_Q4_LOCAL_BASE-00001-of-00011.gguf"
|
||||
GLM_ANTIREZ_IQ2XXS_FILE="GLM-5.2-UD-IQ2_XXS_RoutedIQ2XXS_blk78Q2K.gguf"
|
||||
GLM_ANTIREZ_Q2_FILE="GLM-5.2-UD-Q2_K_RoutedQ2K.gguf"
|
||||
GLM_ANTIREZ_Q4_FILE="GLM-5.2-UD-Q4_K_RoutedQ4K.gguf"
|
||||
GLM53_FULL_Q2_FILE="GLM-5.3-UD-IQ2_XXS_RoutedIQ2XXS_blk78Q2K.gguf"
|
||||
GLM53_Q2_FILE="GLM-5.3-Flash-Q2.gguf"
|
||||
GLM53_Q4_FILE="GLM-5.3-Flash-Q4_K.gguf"
|
||||
GLM53_FP8_FILE="GLM-5.3-Flash-FP8.gguf"
|
||||
GLM53_VISION_FILE="GLM-5.3-Flash-Vision-Encoder.gguf"
|
||||
QWEN38_Q4_FILE="Qwen3.8-Flash-Next-Q4.gguf"
|
||||
QWEN38_Q2_FILE="Qwen3.8-Flash-Next-Q2.gguf"
|
||||
QWEN38_VISION_FILE="mmproj-Qwen3.8-Flash-Next-Q8_0.gguf"
|
||||
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
OUT_DIR=${DS4_GGUF_DIR:-"$ROOT/gguf"}
|
||||
@@ -42,6 +59,15 @@ Usage:
|
||||
./download_model.sh ds4f-q4 [--token TOKEN]
|
||||
./download_model.sh ds4f-mxfp4 [--token TOKEN]
|
||||
./download_model.sh ds4f-dspark [--token TOKEN]
|
||||
./download_model.sh ds4f-vision-q2 [--token TOKEN]
|
||||
./download_model.sh ds4f-vision-q2-q4 [--token TOKEN]
|
||||
./download_model.sh ds4f-vision-mxfp4 [--token TOKEN]
|
||||
./download_model.sh ds4f-vision-encoder [--token TOKEN]
|
||||
./download_model.sh ds4f-vision-dspark [--token TOKEN]
|
||||
./download_model.sh ds41f-q2 [--token TOKEN]
|
||||
./download_model.sh ds41f-q4 [--token TOKEN]
|
||||
./download_model.sh ds41f-mxfp4 [--token TOKEN]
|
||||
./download_model.sh ds41f-vision [--token TOKEN]
|
||||
./download_model.sh pro-q2-imatrix [--token TOKEN]
|
||||
./download_model.sh pro-q4-layers00-30 [--token TOKEN]
|
||||
./download_model.sh pro-q4-layers31-output [--token TOKEN]
|
||||
@@ -50,10 +76,14 @@ Usage:
|
||||
./download_model.sh glm-antirez-iq2xxs [--token TOKEN]
|
||||
./download_model.sh glm-antirez-q2 [--token TOKEN]
|
||||
./download_model.sh glm-antirez-q4 [--token TOKEN]
|
||||
./download_model.sh glm53-full-q2 [--token TOKEN]
|
||||
./download_model.sh glm53-q2 [--token TOKEN]
|
||||
./download_model.sh glm53-q4 [--token TOKEN]
|
||||
./download_model.sh glm53-fp8 [--token TOKEN]
|
||||
./download_model.sh glm53-vision [--token TOKEN]
|
||||
./download_model.sh qwen38-q2 [--token TOKEN]
|
||||
./download_model.sh qwen38-q4k [--token TOKEN]
|
||||
./download_model.sh qwen38-vision [--token TOKEN]
|
||||
|
||||
Targets:
|
||||
|
||||
@@ -80,6 +110,47 @@ Targets:
|
||||
Optional DSpark speculative decoding support GGUF for Flash 0731, about
|
||||
6 GB. Enable it with --dspark and --mtp-model when running ds4 or ds4-server.
|
||||
|
||||
ds4f-vision-q2
|
||||
DeepSeek V4 Flash Vision Experimental with 2-bit routed experts, about
|
||||
81 GiB, plus its vision encoder. Recommended for 96 and 128 GB Macs.
|
||||
|
||||
ds4f-vision-q2-q4
|
||||
Mixed Vision Experimental quant with routed experts in layers 37-42 at
|
||||
Q4_K and the others at 2 bits, about 91 GiB, plus its vision encoder.
|
||||
|
||||
ds4f-vision-mxfp4
|
||||
Native MXFP4 Vision Experimental model, about 145 GiB, plus its vision
|
||||
encoder. Intended for CUDA or two 128 GB Macs using tensor parallelism.
|
||||
|
||||
ds4f-vision-encoder
|
||||
Standalone Vision Experimental encoder, about 0.9 GiB. Use this when
|
||||
the matching language GGUF is already present.
|
||||
|
||||
ds4f-vision-dspark
|
||||
Matching DSpark speculative decoding support for Vision Experimental,
|
||||
about 5.6 GiB. It is not compatible with the 0731 language checkpoint.
|
||||
|
||||
ds41f-q2
|
||||
DeepSeek V4.1 Flash calibrated Q2, about 341 GiB on disk. Main weights
|
||||
occupy 152 GiB; Engram tables stay on disk. Metal only: use SSD streaming
|
||||
on one 128 GB Mac, tensor parallelism on two, or a larger resident Mac.
|
||||
|
||||
ds41f-vision
|
||||
Matching V4.1 Flash vision encoder, about 0.9 GiB. Add --vision FILE
|
||||
to the language-model command. Does not update ./ds4flash.gguf.
|
||||
|
||||
ds41f-q4
|
||||
DeepSeek V4.1 Flash calibrated Q4, about 483 GiB on disk. Main weights
|
||||
occupy 294 GiB; Engram tables stay on disk. Metal only: use SSD streaming
|
||||
on smaller Macs or full residency on a 512 GB Mac. Downloads two parts
|
||||
and joins them automatically; allow another 37 GiB of free disk space.
|
||||
|
||||
ds41f-mxfp4
|
||||
DeepSeek V4.1 Flash 1017 MXFP4 routed experts, about 467 GiB on disk.
|
||||
Downloads and verifies all nine GGUF shards, then restores the original
|
||||
page-aligned GGUF for DwarfStar. Retains the shards: allow about 935 GiB
|
||||
of disk space for shards plus output. Downloads and merging can resume.
|
||||
|
||||
pro-q2-imatrix
|
||||
DeepSeek V4 PRO 0813 q2 imatrix quant, as a single GGUF file. About
|
||||
430 GB on disk; intended for 512 GB RAM machines.
|
||||
@@ -113,6 +184,10 @@ Targets:
|
||||
GLM 5.2 antirez routed Q4_K GGUF from antirez/GLM-5.2-GGUF.
|
||||
About 434 GB on disk.
|
||||
|
||||
glm53-full-q2
|
||||
Full GLM 5.3 routed IQ2_XXS/Q2_K GGUF, about 197 GiB on disk.
|
||||
Intended for 256 GB machines or SSD streaming on smaller systems.
|
||||
|
||||
glm53-q2
|
||||
GLM 5.3 Flash imatrix Q2 GGUF, about 90 GiB on disk. Intended for
|
||||
resident inference on 128 GB Macs.
|
||||
@@ -130,6 +205,25 @@ Targets:
|
||||
GLM 5.3 Flash vision encoder, about 1.1 GB on disk. Load it separately
|
||||
with --vision; this target does not update ./ds4flash.gguf.
|
||||
|
||||
qwen38-q2 (alias: qwen38-iq2)
|
||||
Qwen3.8-Flash-Next Q2: one 137.10 GiB GGUF. Main/MTP weights
|
||||
occupy 41.73 GiB; original BF16 n-grams stay on disk.
|
||||
Imatrix IQ2_XXS gate/up and Q2_K down experts (640 logical inputs,
|
||||
padded to 768 on disk), with higher-precision dense/control tensors.
|
||||
Smaller option for 64 GB Macs: start with --ctx 8192 and
|
||||
--prefill-chunk 1024. Context buffers need additional RAM.
|
||||
Keep the file on a local SSD. Add --mtp for speculation.
|
||||
|
||||
qwen38-q4k
|
||||
Qwen3.8-Flash-Next Q4: one 165.11 GiB GGUF. Main/MTP weights
|
||||
occupy 69.74 GiB; original BF16 n-grams stay on disk.
|
||||
Imatrix Q4_K gate/up and MXFP4 down routed experts. Fits 128 GB
|
||||
Macs with room for context buffers. Add --mtp for speculation.
|
||||
|
||||
qwen38-vision
|
||||
Qwen3.8-Flash-Next vision encoder, about 0.6 GB on disk. Load it
|
||||
with --vision; this target does not update ./ds4flash.gguf.
|
||||
|
||||
Options:
|
||||
--token TOKEN Hugging Face token. Otherwise HF_TOKEN or the local HF token
|
||||
cache is used if present.
|
||||
@@ -145,10 +239,13 @@ Then the default commands work:
|
||||
./ds4 -p "Hello"
|
||||
./ds4-server --ctx 100000
|
||||
|
||||
After downloading DSpark support, enable it explicitly in greedy mode:
|
||||
./ds4 --dspark --mtp-model <download directory>/$DS4F_DSPARK_FILE --temp 0
|
||||
Qwen3.8 includes its n-grams and MTP; add --mtp to enable speculation:
|
||||
./ds4 --mtp
|
||||
|
||||
PRO and GLM files are downloaded with the official Hugging Face downloader
|
||||
After downloading DSpark support, enable it explicitly:
|
||||
./ds4 --dspark --mtp-model <download directory>/$DS4F_DSPARK_FILE
|
||||
|
||||
PRO, V4.1, GLM and Qwen files use the official Hugging Face downloader
|
||||
because they are too large, sharded, or nested for the curl path used by the
|
||||
smaller DeepSeek Flash GGUF files.
|
||||
EOF
|
||||
@@ -172,6 +269,55 @@ case "$MODEL" in
|
||||
ds4f-q4) MODEL_FILE=$DS4F_Q4_FILE ;;
|
||||
ds4f-mxfp4) MODEL_FILE=$DS4F_MXFP4_FILE; FORCE_HF_DOWNLOAD=1 ;;
|
||||
ds4f-dspark) MODEL_FILE=$DS4F_DSPARK_FILE; LINK_MODEL=0 ;;
|
||||
ds4f-vision-q2)
|
||||
MODEL_FILE=$DS4F_VISION_Q2_FILE
|
||||
MODEL_FILES="$MODEL_FILE $DS4F_VISION_ENCODER_FILE"
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
;;
|
||||
ds4f-vision-q2-q4)
|
||||
MODEL_FILE=$DS4F_VISION_Q2_Q4_FILE
|
||||
MODEL_FILES="$MODEL_FILE $DS4F_VISION_ENCODER_FILE"
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
;;
|
||||
ds4f-vision-mxfp4)
|
||||
MODEL_FILE=$DS4F_VISION_MXFP4_FILE
|
||||
MODEL_FILES="$MODEL_FILE $DS4F_VISION_ENCODER_FILE"
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
;;
|
||||
ds4f-vision-encoder)
|
||||
MODEL_FILE=$DS4F_VISION_ENCODER_FILE
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
LINK_MODEL=0
|
||||
;;
|
||||
ds4f-vision-dspark)
|
||||
MODEL_FILE=$DS4F_VISION_DSPARK_FILE
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
LINK_MODEL=0
|
||||
;;
|
||||
ds41f-q2)
|
||||
REPO=$DS41_REPO
|
||||
MODEL_FILE=$DS41_Q2_FILE
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
;;
|
||||
ds41f-q4)
|
||||
REPO=$DS41_REPO
|
||||
MODEL_FILE=$DS41_Q4_FILE
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
;;
|
||||
ds41f-mxfp4)
|
||||
REPO=$DS41_REPO
|
||||
MODEL_FILE="$DS41_MXFP4_BASE.gguf"
|
||||
for part in 00001 00002 00003 00004 00005 00006 00007 00008 00009; do
|
||||
MODEL_FILES="$MODEL_FILES $DS41_MXFP4_BASE-${part}-of-00009.gguf"
|
||||
done
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
;;
|
||||
ds41f-vision)
|
||||
REPO=$DS41_REPO
|
||||
MODEL_FILE=$DS41_VISION_FILE
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
LINK_MODEL=0
|
||||
;;
|
||||
pro-q2-imatrix) MODEL_FILE=$PRO_Q2_IMATRIX_FILE ;;
|
||||
pro-q4-layers00-30) MODEL_FILE=$PRO_Q4_LAYERS00_30_FILE; LINK_MODEL=0 ;;
|
||||
pro-q4-layers31-output) MODEL_FILE=$PRO_Q4_LAYERS31_OUTPUT_FILE; LINK_MODEL=0 ;;
|
||||
@@ -204,6 +350,11 @@ case "$MODEL" in
|
||||
MODEL_FILE=$GLM_ANTIREZ_Q4_FILE
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
;;
|
||||
glm53-full-q2)
|
||||
REPO=$GLM53_FULL_REPO
|
||||
MODEL_FILE=$GLM53_FULL_Q2_FILE
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
;;
|
||||
glm53-q2)
|
||||
REPO=$GLM53_REPO
|
||||
MODEL_FILE=$GLM53_Q2_FILE
|
||||
@@ -226,6 +377,22 @@ case "$MODEL" in
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
LINK_MODEL=0
|
||||
;;
|
||||
qwen38-q2|qwen38-iq2)
|
||||
REPO=$QWEN38_REPO
|
||||
MODEL_FILE=$QWEN38_Q2_FILE
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
;;
|
||||
qwen38-q4k)
|
||||
REPO=$QWEN38_REPO
|
||||
MODEL_FILE=$QWEN38_Q4_FILE
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
;;
|
||||
qwen38-vision)
|
||||
REPO=$QWEN38_MMPROJ_REPO
|
||||
MODEL_FILE=$QWEN38_VISION_FILE
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
LINK_MODEL=0
|
||||
;;
|
||||
-h|--help|help)
|
||||
usage
|
||||
exit 0
|
||||
@@ -296,6 +463,99 @@ local_download_name() {
|
||||
fi
|
||||
}
|
||||
|
||||
artifact_identity() {
|
||||
case "$1" in
|
||||
"$QWEN38_Q2_FILE")
|
||||
expected_bytes=147207127040
|
||||
expected_sha=b1b93fa69aca5f187b0fb813aca8f3ec1beb5cf8cf0bd38cf041b93e0b6ccac9
|
||||
;;
|
||||
"$QWEN38_Q4_FILE")
|
||||
expected_bytes=177280286720
|
||||
expected_sha=680944460a8cbe93ba8b6d7b6107213ffb7e22320bd913000e563ca0a0f25a8a
|
||||
;;
|
||||
"$DS41_Q2_FILE")
|
||||
expected_bytes=365713686528
|
||||
expected_sha=5296e85ff64b42ecb62c8d0c0c54e1caa07e3df8e93112f2365ebbaed62ce93a
|
||||
;;
|
||||
"$DS41_Q4_FILE")
|
||||
expected_bytes=518596067328
|
||||
expected_sha=a5e2e2c3ada4b2e98d9f9e4b50f6d9c2a12c2c96f5da165c07e13aff9264984e
|
||||
;;
|
||||
"$DS41_Q4_FILE.part1")
|
||||
expected_bytes=480000000000
|
||||
expected_sha=6442b1f9224079662c02003c0ef9ef6be6e2aff509510f681dab9e6cc41df246
|
||||
;;
|
||||
"$DS41_Q4_FILE.part2")
|
||||
expected_bytes=38596067328
|
||||
expected_sha=7c3e10646c918eeaffbc39305a75ec96117450262c61454ff194cef00d7617f0
|
||||
;;
|
||||
"$DS41_MXFP4_BASE.gguf")
|
||||
expected_bytes=501609136128
|
||||
expected_sha=79608a69cd5bb919d8140c37105edc3a33419692d4e6caf4e68c901e4b359141
|
||||
;;
|
||||
"$DS41_MXFP4_BASE-00001-of-00009.gguf")
|
||||
expected_bytes=49353784544
|
||||
expected_sha=636918b09de6253bf41af29f06aac3ceeda220e46379d8a2058ef92c4e433ed8
|
||||
;;
|
||||
"$DS41_MXFP4_BASE-00002-of-00009.gguf")
|
||||
expected_bytes=49412635712
|
||||
expected_sha=b26d3fc1e6ae1be6c52665542f3c1fee6a33791478f26e6cd90b5a75940a9f96
|
||||
;;
|
||||
"$DS41_MXFP4_BASE-00003-of-00009.gguf")
|
||||
expected_bytes=49582960352
|
||||
expected_sha=20c7fe7adb0578336d8a5e5f9eed521aeb88e8c119ba46e0916cdaab28931c80
|
||||
;;
|
||||
"$DS41_MXFP4_BASE-00004-of-00009.gguf")
|
||||
expected_bytes=49418206432
|
||||
expected_sha=67592ba15870c4292a615666adcc2736b53cc345b2cccb1c36710a0a6caf15b4
|
||||
;;
|
||||
"$DS41_MXFP4_BASE-00005-of-00009.gguf")
|
||||
expected_bytes=49412829632
|
||||
expected_sha=ecd04e0aa71bfab8cfe79ea7fef7f35b6be0b6772455139e2081d6ce1dcc18ed
|
||||
;;
|
||||
"$DS41_MXFP4_BASE-00006-of-00009.gguf")
|
||||
expected_bytes=49257603776
|
||||
expected_sha=88214e0cc80fbae578b80c114527e6ce725199684e0b6849475c7d00b3d0f54c
|
||||
;;
|
||||
"$DS41_MXFP4_BASE-00007-of-00009.gguf")
|
||||
expected_bytes=2406482112
|
||||
expected_sha=38939faf3e4258f3ae333f0fa1954ce89515b75ceee90d37bf4ebe5fafe285e6
|
||||
;;
|
||||
"$DS41_MXFP4_BASE-00008-of-00009.gguf")
|
||||
expected_bytes=101377628544
|
||||
expected_sha=a9d1634da2ac57459a64b10b69ddedfadda7dcde0e71ca6e55bea7c293c4a9f0
|
||||
;;
|
||||
"$DS41_MXFP4_BASE-00009-of-00009.gguf")
|
||||
expected_bytes=101380404256
|
||||
expected_sha=b9832b355288c005da6ef444d02424464dd27f6ebdefeadbec2244264eb65285
|
||||
;;
|
||||
"$DS41_VISION_FILE")
|
||||
expected_bytes=970555552
|
||||
expected_sha=cc283f032b3e8b8d78aeb5fccaa14e97b859b0c53aae3cd6bffa690ddf0e9e15
|
||||
;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
verify_download() {
|
||||
artifact_identity "$1" || return 0
|
||||
if [ "$(wc -c < "$2")" -ne "$expected_bytes" ]; then
|
||||
echo "Incorrect file size: $2. Move the incomplete file aside and retry." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Verifying SHA-256: $2"
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
actual_sha=$(sha256sum < "$2")
|
||||
else
|
||||
actual_sha=$(shasum -a 256 < "$2")
|
||||
fi
|
||||
actual_sha=${actual_sha%% *}
|
||||
if [ "$actual_sha" != "$expected_sha" ]; then
|
||||
echo "Checksum mismatch: $2. The file was not accepted." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
download_one_hf() {
|
||||
file=$1
|
||||
local_file=$(local_download_name "$file")
|
||||
@@ -306,6 +566,7 @@ download_one_hf() {
|
||||
mkdir -p "$(dirname "$out")"
|
||||
|
||||
if [ -s "$out" ]; then
|
||||
verify_download "$file" "$out"
|
||||
echo "Already downloaded: $out"
|
||||
return
|
||||
fi
|
||||
@@ -345,6 +606,7 @@ download_one_hf() {
|
||||
echo "Hugging Face download finished but expected file is missing: $out" >&2
|
||||
exit 1
|
||||
fi
|
||||
verify_download "$file" "$out"
|
||||
}
|
||||
|
||||
download_one() {
|
||||
@@ -386,7 +648,138 @@ download_one() {
|
||||
mv "$part" "$out"
|
||||
}
|
||||
|
||||
if [ -n "$MODEL_FILES" ]; then
|
||||
download_ds41_q4() {
|
||||
q4_out="$OUT_DIR/$DS41_Q4_FILE"
|
||||
if [ -e "$q4_out" ]; then
|
||||
verify_download "$DS41_Q4_FILE" "$q4_out"
|
||||
echo "Already downloaded: $q4_out"
|
||||
return
|
||||
fi
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
echo "Joining the Q4 download requires Python 3." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -e "$q4_out.assembling" ]; then
|
||||
download_one_hf "$DS41_Q4_FILE.part1"
|
||||
fi
|
||||
download_one_hf "$DS41_Q4_FILE.part2"
|
||||
artifact_identity "$DS41_Q4_FILE"
|
||||
q4_bytes=$expected_bytes
|
||||
q4_sha=$expected_sha
|
||||
artifact_identity "$DS41_Q4_FILE.part1"
|
||||
python3 - "$q4_out" "$expected_bytes" "$q4_bytes" "$q4_sha" <<'PY'
|
||||
import fcntl
|
||||
import hashlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
out = Path(sys.argv[1])
|
||||
boundary, total = map(int, sys.argv[2:4])
|
||||
expected = sys.argv[4]
|
||||
first, second, pending = (Path(str(out) + suffix)
|
||||
for suffix in (".part1", ".part2", ".assembling"))
|
||||
|
||||
def verify(path):
|
||||
if path.stat().st_size != total:
|
||||
sys.exit("Incorrect file size: " + str(path))
|
||||
print("Verifying SHA-256: " + str(path), flush=True)
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as fp:
|
||||
for block in iter(lambda: fp.read(16 << 20), b""):
|
||||
digest.update(block)
|
||||
if digest.hexdigest() != expected:
|
||||
sys.exit("Checksum mismatch: " + str(path) +
|
||||
". Move this file aside before retrying; it was not accepted.")
|
||||
|
||||
# Keep this lock file: unlinking it could allow two different locks for the
|
||||
# same download. The first part becomes the output without a second full copy.
|
||||
with Path(str(pending) + ".lock").open("a") as lock:
|
||||
fcntl.flock(lock, fcntl.LOCK_EX)
|
||||
if out.exists():
|
||||
verify(out)
|
||||
sys.exit(0)
|
||||
if not pending.exists():
|
||||
if first.stat().st_size != boundary:
|
||||
sys.exit("Incorrect file size: " + str(first))
|
||||
first.rename(pending)
|
||||
size = pending.stat().st_size
|
||||
if not boundary <= size <= total:
|
||||
sys.exit("Invalid partial assembly: " + str(pending) +
|
||||
". Move it aside before retrying.")
|
||||
if second.stat().st_size != total - boundary:
|
||||
sys.exit("Incorrect file size: " + str(second))
|
||||
# An interrupted append restarts only the smaller tail, never the prefix.
|
||||
with pending.open("r+b") as dst:
|
||||
dst.truncate(boundary)
|
||||
dst.seek(boundary)
|
||||
if shutil.disk_usage(out.parent).free < total - boundary + (1 << 30):
|
||||
sys.exit("Not enough disk space to join Q4; keep the parts and retry.")
|
||||
print("Joining Q4 download parts", flush=True)
|
||||
with second.open("rb") as src:
|
||||
shutil.copyfileobj(src, dst, 16 << 20)
|
||||
dst.flush()
|
||||
os.fsync(dst.fileno())
|
||||
verify(pending)
|
||||
pending.rename(out)
|
||||
second.unlink()
|
||||
PY
|
||||
}
|
||||
|
||||
download_ds41_mxfp4() {
|
||||
mx_out="$OUT_DIR/$MODEL_FILE"
|
||||
if [ -e "$mx_out" ]; then
|
||||
verify_download "$MODEL_FILE" "$mx_out"
|
||||
echo "Already downloaded: $mx_out"
|
||||
return
|
||||
fi
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
echo "Merging the MXFP4 shards requires Python 3." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$ROOT/gguf-tools/merge_deepseek41_shards.py" ]; then
|
||||
echo "Missing gguf-tools/merge_deepseek41_shards.py; use a complete checkout." >&2
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$OUT_DIR"
|
||||
artifact_identity "$MODEL_FILE"
|
||||
mx_bytes=$expected_bytes
|
||||
mx_sha=$expected_sha
|
||||
set -- "$mx_out.assembling" "$mx_bytes"
|
||||
for shard in $MODEL_FILES; do
|
||||
artifact_identity "$shard"
|
||||
set -- "$@" "$OUT_DIR/$shard" "$expected_bytes"
|
||||
done
|
||||
# Check peak storage before downloading hundreds of GB. Retain the input
|
||||
# shards and account for a resumable output; HF partial caches are extra.
|
||||
python3 - "$OUT_DIR" "$@" <<'PY'
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
needed = 1 << 30
|
||||
for name, size in zip(sys.argv[2::2], sys.argv[3::2]):
|
||||
path = Path(name)
|
||||
needed += max(0, int(size) - (path.stat().st_size if path.exists() else 0))
|
||||
if shutil.disk_usage(sys.argv[1]).free < needed:
|
||||
sys.exit("Not enough disk space for MXFP4 shards and merged GGUF: "
|
||||
f"need {needed / (1 << 30):.1f} GiB free. Existing files were kept.")
|
||||
PY
|
||||
set --
|
||||
for shard in $MODEL_FILES; do
|
||||
download_one_hf "$shard"
|
||||
set -- "$@" "$OUT_DIR/$shard"
|
||||
done
|
||||
python3 "$ROOT/gguf-tools/merge_deepseek41_shards.py" \
|
||||
--output "$mx_out" --expected-bytes "$mx_bytes" --sha256 "$mx_sha" "$@"
|
||||
}
|
||||
|
||||
if [ "$MODEL" = "ds41f-mxfp4" ]; then
|
||||
download_ds41_mxfp4
|
||||
elif [ "$MODEL" = "ds41f-q4" ]; then
|
||||
download_ds41_q4
|
||||
elif [ -n "$MODEL_FILES" ]; then
|
||||
for file in $MODEL_FILES; do
|
||||
download_one "$file"
|
||||
done
|
||||
@@ -396,8 +789,12 @@ fi
|
||||
|
||||
if [ "$MODEL" = "ds4f-dspark" ]; then
|
||||
echo
|
||||
echo "DSpark support downloaded. Enable it explicitly in greedy mode:"
|
||||
echo " ./ds4 --dspark -m ./ds4flash.gguf --mtp-model $OUT_DIR/$DS4F_DSPARK_FILE --temp 0"
|
||||
echo "DSpark support downloaded. Enable it explicitly:"
|
||||
echo " ./ds4 --dspark -m ./ds4flash.gguf --mtp-model $OUT_DIR/$DS4F_DSPARK_FILE"
|
||||
elif [ "$MODEL" = "ds4f-vision-dspark" ]; then
|
||||
echo
|
||||
echo "Vision Experimental DSpark support downloaded. Use it only with the matching checkpoint:"
|
||||
echo " ./ds4 --dspark -m ./ds4flash.gguf --mtp-model $OUT_DIR/$DS4F_VISION_DSPARK_FILE"
|
||||
elif [ "$MODEL" = "pro-q4-layers00-30" ] || [ "$MODEL" = "pro-q4-layers31-output" ] || [ "$MODEL" = "pro-q4-split" ]; then
|
||||
echo
|
||||
echo "Downloaded PRO Q4 distributed split file(s). Use them with --layers,"
|
||||
@@ -410,3 +807,15 @@ fi
|
||||
|
||||
echo
|
||||
echo "Done."
|
||||
if [ "$MODEL" = qwen38-q2 ] || [ "$MODEL" = qwen38-iq2 ]; then
|
||||
echo "Run with an 8K starting context:"
|
||||
echo " ./ds4 --ctx 8192 --prefill-chunk 1024"
|
||||
echo "Add --mtp to enable speculation."
|
||||
elif [ "$MODEL" = qwen38-q4k ]; then
|
||||
echo "Run ./ds4. Add --mtp to enable speculation."
|
||||
fi
|
||||
if [ "$MODEL" = qwen38-vision ]; then
|
||||
echo
|
||||
echo "Qwen3.8 vision encoder downloaded. Pass it with --vision, for example:"
|
||||
printf ' ./ds4 --vision "%s/%s"\n' "$OUT_DIR" "$QWEN38_VISION_FILE"
|
||||
fi
|
||||
@@ -26,7 +26,11 @@ typedef enum {
|
||||
DS4_THINK_NONE,
|
||||
DS4_THINK_HIGH,
|
||||
DS4_THINK_MAX,
|
||||
DS4_THINK_LOW, /* Qwen3.8 reasoning_effort low/medium; other models render them as HIGH */
|
||||
DS4_THINK_MEDIUM,
|
||||
} ds4_think_mode;
|
||||
/* Explicit numeric effort lives outside the stable named-mode values. */
|
||||
#define DS4_THINK_LEVEL_BASE 1000
|
||||
|
||||
typedef enum {
|
||||
DS4_LOG_DEFAULT,
|
||||
@@ -177,6 +181,9 @@ typedef struct {
|
||||
typedef struct {
|
||||
float *data;
|
||||
uint32_t token_count;
|
||||
uint32_t layout;
|
||||
uint32_t grid_width;
|
||||
uint32_t grid_height;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t content_width;
|
||||
@@ -300,8 +307,13 @@ bool ds4_engine_glm_layer_payload_bytes(ds4_engine *e,
|
||||
int ds4_engine_model_id(ds4_engine *e);
|
||||
bool ds4_engine_is_glm_dsa(ds4_engine *e);
|
||||
bool ds4_engine_is_glm53(ds4_engine *e);
|
||||
bool ds4_engine_is_qwen4(ds4_engine *e);
|
||||
/* Qwen3.8 reasoning-effort system instruction for a think mode (NULL when none) */
|
||||
const char *ds4_qwen4_reasoning_effort_text(ds4_think_mode mode);
|
||||
const char *ds4_backend_name(ds4_backend backend);
|
||||
bool ds4_think_mode_enabled(ds4_think_mode mode);
|
||||
int ds4_think_mode_level(ds4_think_mode mode);
|
||||
bool ds4_think_mode_parse_level(const char *text, ds4_think_mode *out);
|
||||
const char *ds4_think_mode_name(ds4_think_mode mode);
|
||||
const char *ds4_think_max_prefix(void);
|
||||
const char *ds4_glm_reasoning_effort_text(ds4_think_mode mode);
|
||||
@@ -333,16 +345,20 @@ int ds4_engine_collect_imatrix(ds4_engine *e,
|
||||
const char *output_path,
|
||||
int ctx_size,
|
||||
int max_prompts,
|
||||
int max_tokens);
|
||||
int max_tokens,
|
||||
int min_expert_samples);
|
||||
void ds4_engine_dump_tokens(ds4_engine *e, const ds4_tokens *tokens);
|
||||
int ds4_dump_text_tokenization(const char *model_path, const char *text, FILE *fp);
|
||||
int ds4_dump_chat_tokenization(const char *model_path,
|
||||
const char *system,
|
||||
const char *prompt,
|
||||
ds4_think_mode think_mode,
|
||||
int ctx_size,
|
||||
FILE *fp);
|
||||
int ds4_engine_head_test(ds4_engine *e, const ds4_tokens *prompt);
|
||||
bool ds4_engine_is_glm_dsa(ds4_engine *e);
|
||||
bool ds4_engine_is_deepseek41(ds4_engine *e);
|
||||
const char *ds4_deepseek41_reasoning_effort_text(ds4_think_mode mode);
|
||||
int ds4_engine_first_token_test(ds4_engine *e, const ds4_tokens *prompt);
|
||||
int ds4_engine_metal_graph_test(ds4_engine *e, const ds4_tokens *prompt);
|
||||
int ds4_engine_metal_graph_full_test(ds4_engine *e, const ds4_tokens *prompt);
|
||||
@@ -363,6 +379,7 @@ void ds4_encode_chat_prompt(
|
||||
ds4_think_mode think_mode,
|
||||
ds4_tokens *out);
|
||||
void ds4_chat_append_max_effort_prefix(ds4_engine *e, ds4_tokens *tokens);
|
||||
void ds4_chat_append_think_prefix(ds4_engine *e, ds4_tokens *tokens, ds4_think_mode mode);
|
||||
void ds4_chat_append_message(ds4_engine *e, ds4_tokens *tokens, const char *role, const char *content);
|
||||
void ds4_chat_append_assistant_prefix(ds4_engine *e, ds4_tokens *tokens, ds4_think_mode think_mode);
|
||||
|
||||
@@ -382,6 +399,9 @@ int ds4_token_assistant(ds4_engine *e);
|
||||
* with the caller. */
|
||||
struct ds4_tp;
|
||||
int ds4_engine_tp_bind(ds4_engine *e, struct ds4_tp *tp, char *err, size_t errlen);
|
||||
/* Release gate resources before freeing a bound transport. Sessions must
|
||||
* already be closed. Also called by ds4_engine_close(). */
|
||||
void ds4_engine_tp_unbind(ds4_engine *e);
|
||||
|
||||
int ds4_session_create(ds4_session **out, ds4_engine *e, int ctx_size);
|
||||
void ds4_session_free(ds4_session *s);
|
||||
@@ -396,9 +416,10 @@ void ds4_session_set_progress(ds4_session *s, ds4_session_progress_fn fn, void *
|
||||
/* UI-only progress. It may report fine-grained progress inside a prefill chunk;
|
||||
* callers must not treat it as a durable KV checkpoint boundary. */
|
||||
void ds4_session_set_display_progress(ds4_session *s, ds4_session_progress_fn fn, void *ud);
|
||||
/* Optional cooperative cancellation. ds4_session_sync() checks it only at
|
||||
* safe boundaries where the live checkpoint is either unchanged or represents a
|
||||
* valid token prefix, and returns DS4_SESSION_SYNC_INTERRUPTED when it stops. */
|
||||
/* Cooperative cancellation for ds4_session_sync(), which drains pending work
|
||||
* before returning DS4_SESSION_SYNC_INTERRUPTED. A complete prefix may remain
|
||||
* usable, but a partial layer-major pass is invalidated and the next sync
|
||||
* rebuilds it. Do not assume an interrupted session can be saved or decoded. */
|
||||
void ds4_session_set_cancel(ds4_session *s, ds4_session_cancel_fn fn, void *ud);
|
||||
void ds4_session_report_progress(ds4_session *s, const char *event, int current, int total);
|
||||
/* Distributed coordinator sessions return 1 when the full layer route is
|
||||
@@ -424,17 +445,57 @@ int ds4_session_sync_multimodal(ds4_session *s,
|
||||
size_t image_count,
|
||||
char *err,
|
||||
size_t errlen);
|
||||
/* A reusable image prefix has unchanged spans/fingerprints for all historical
|
||||
* images, with any new images starting at or after the live token frontier.
|
||||
* The caller must also check the token prefix. Invalid checkpoints never match. */
|
||||
bool ds4_session_vision_prefix_matches(const ds4_session *s,
|
||||
const ds4_vision_span *images,
|
||||
size_t image_count);
|
||||
/* Like the prefix check, but also require exactly the same image count. */
|
||||
bool ds4_session_vision_state_matches(const ds4_session *s,
|
||||
const ds4_vision_span *images,
|
||||
size_t image_count);
|
||||
/* Fingerprint-only image prefix: every historical image matches by row count
|
||||
* and fingerprint, ignoring token positions (which rebase repairs) and
|
||||
* allowing appended request images. Lets the server routing probe admit
|
||||
* image-append continuations while still rejecting pixel mismatches. */
|
||||
bool ds4_session_vision_fingerprint_prefix_matches(const ds4_session *s,
|
||||
const ds4_vision_span *images,
|
||||
size_t image_count);
|
||||
/* Restore image positions from an independently authenticated live continuation
|
||||
* (for example, matching tool-call IDs). Checks every fingerprint and row count;
|
||||
* on failure, leaves spans unchanged. This does not verify the text history. */
|
||||
bool ds4_session_rebase_vision_state(const ds4_session *s,
|
||||
ds4_vision_span *images, size_t image_count);
|
||||
/* True while a session contains, or is actively syncing, image-conditioned
|
||||
* state. Such state must not be written to the text-keyed disk KV cache. */
|
||||
bool ds4_session_has_vision_state(const ds4_session *s);
|
||||
bool ds4_session_rewrite_requires_rebuild(int live_len, int canonical_len, int common);
|
||||
ds4_session_rewrite_result ds4_session_rewrite_from_common(
|
||||
ds4_session *s, const ds4_tokens *prompt, int common,
|
||||
char *err, size_t errlen);
|
||||
int ds4_session_common_prefix(ds4_session *s, const ds4_tokens *prompt);
|
||||
bool ds4_session_checkpoint_valid(const ds4_session *s);
|
||||
/* Test helpers (ds4-test): allocate a session shell holding only the given
|
||||
* checkpoint tokens, for server-side routing/probe unit tests. Not usable
|
||||
* for inference; free with ds4_session_free_test_checkpoint(). */
|
||||
ds4_session *ds4_session_new_test_checkpoint(const int *tokens, int n);
|
||||
void ds4_session_free_test_checkpoint(ds4_session *s);
|
||||
/* Attach synthetic image identities to a test checkpoint (copies
|
||||
* token_start/row-count/fingerprint per span). Not usable for inference. */
|
||||
void ds4_session_set_test_images(ds4_session *s,
|
||||
const ds4_vision_span *images, size_t n);
|
||||
int ds4_session_argmax(ds4_session *s);
|
||||
int ds4_session_argmax_excluding(ds4_session *s, int excluded_id);
|
||||
int ds4_session_argmax_ignoring_eos(ds4_session *s,
|
||||
ds4_think_mode think_mode);
|
||||
int ds4_sample_logits(const float *logits, int n_vocab, float temperature,
|
||||
int top_k, float top_p, float min_p, uint64_t *rng);
|
||||
int ds4_session_sample(ds4_session *s, float temperature, int top_k, float top_p, float min_p, uint64_t *rng);
|
||||
#ifdef DS4_TEST_HOOKS
|
||||
/* Teacher-force a V4.1 verification block, returning logits after each token. */
|
||||
int ds4_test_ds41_verify_tokens(ds4_session *s, const int *tokens, int count,
|
||||
float *logits, char *err, size_t errlen);
|
||||
int ds4_test_sample_logits(const float *logits, uint32_t n_vocab,
|
||||
float temperature, int top_k,
|
||||
float top_p, float min_p, uint64_t *rng,
|
||||
@@ -464,6 +525,7 @@ int ds4_test_speculative_delta_sample(const float *target_logits,
|
||||
int ds4_test_argmax_excluding_logits(const float *logits, uint32_t n_vocab,
|
||||
int excluded_id);
|
||||
uint64_t ds4_test_mixed_native_count(void);
|
||||
uint64_t ds4_test_ds41_batch_count(void);
|
||||
#endif
|
||||
int ds4_session_top_logprobs(ds4_session *s, ds4_token_score *out, int k);
|
||||
int ds4_session_token_logprob(ds4_session *s, int token, ds4_token_score *out);
|
||||
@@ -484,6 +546,16 @@ typedef struct {
|
||||
* sequential fallback. */
|
||||
int ds4_sessions_eval_batch(ds4_decode_item *items, int count,
|
||||
char *err, size_t errlen);
|
||||
/* One speculative cycle for a batch of sessions (greedy acceptance, Qwen3.8
|
||||
* with --mtp): each item feeds its token; a pending draft rides along as a
|
||||
* second row and is committed when it is the target's argmax. accepted[i]
|
||||
* lists the tokens committed for item i (the fed token, then the draft) and
|
||||
* n_accepted[i] how many; the session's logits then follow its last
|
||||
* committed token. Engines without native batching run one cycle per
|
||||
* session in turn. */
|
||||
int ds4_sessions_eval_batch_speculative_argmax(ds4_decode_item *items, int count,
|
||||
int (*accepted)[2], int *n_accepted,
|
||||
char *err, size_t errlen);
|
||||
/* Advance one resumed prefill suffix and an independent decode batch as one
|
||||
* scheduling step. Unsupported combinations use the ordinary serialized
|
||||
* session operations. */
|
||||
@@ -495,6 +567,10 @@ int ds4_session_eval_speculative_argmax(ds4_session *s, int first_token,
|
||||
int max_tokens, int eos_token,
|
||||
int *accepted, int accepted_cap,
|
||||
char *err, size_t errlen);
|
||||
int ds4_session_eval_speculative_argmax_ignoring_eos(
|
||||
ds4_session *s, int first_token, int max_tokens, int eos_token,
|
||||
ds4_think_mode think_mode,
|
||||
int *accepted, int accepted_cap, char *err, size_t errlen);
|
||||
/* Evaluate one already-sampled target token and speculatively extend it.
|
||||
* Positive-temperature DSpark normally commits greedily verified draft
|
||||
* tokens; dspark_exact_sampling selects exact stochastic p/q acceptance for
|
||||
@@ -507,10 +583,29 @@ int ds4_session_eval_speculative(ds4_session *s, int first_token,
|
||||
char *err, size_t errlen);
|
||||
/* TP worker side of a mirrored speculative-verify block: run its half of the
|
||||
* batch verify for KV side effects, then obey the leader's commit frame
|
||||
* (keep, or roll back and replay). Only called from ds4_tp_worker_run. */
|
||||
* (keep, or roll back and replay). flags are the VERIFY frame's
|
||||
* DS4_TP_VERIFY_* bits. Only called from ds4_tp_worker_run. */
|
||||
int ds4_session_tp_spec_cycle(ds4_session *s, const int *drafts, int draft_n,
|
||||
char *err, size_t errlen);
|
||||
uint32_t flags, char *err, size_t errlen);
|
||||
int ds4_session_glm_tp_spec_cycle(ds4_session *s, int token, int limit,
|
||||
char *err, size_t errlen);
|
||||
/* TP worker side of a lockstep DSpark DRAFT/MAINTAIN frame (mode is a
|
||||
* DS4_TP_DSPARK_* value). Only called from ds4_tp_worker_run. */
|
||||
int ds4_session_tp_dspark_draft(ds4_session *s, int token, int pos,
|
||||
uint32_t mode, char *err, size_t errlen);
|
||||
/* Internal TP restore: no collectives and no logits exchange. Return 0 after
|
||||
* restoring a recent verifier prefix, 1 if unavailable, -1 on copy failure.
|
||||
* Logits are valid only at the block start; otherwise replay before sampling. */
|
||||
int ds4_session_restore_speculative_prefix(ds4_session *s, int pos);
|
||||
void ds4_session_invalidate(ds4_session *s);
|
||||
/* Try a recent speculative rewind, including valid next-token logits. False
|
||||
* means the caller must use ordinary rewind/sync before continuing; a failed
|
||||
* restore may have invalidated the checkpoint. */
|
||||
bool ds4_session_rewind_speculative(ds4_session *s, int pos);
|
||||
/* Keep the token prefix, restoring recurrent state where possible. This may
|
||||
* copy GPU state and replay a bounded speculative suffix on both TP ranks.
|
||||
* Otherwise the checkpoint becomes invalid: sync the retained prefix before eval.
|
||||
* Callers retaining images must use sync_multimodal for that rebuild. */
|
||||
void ds4_session_rewind(ds4_session *s, int pos);
|
||||
int ds4_session_pos(ds4_session *s);
|
||||
int ds4_session_ctx(ds4_session *s);
|
||||
@@ -519,6 +614,7 @@ int ds4_engine_routed_quant_bits(ds4_engine *e);
|
||||
bool ds4_engine_has_output_head(ds4_engine *e);
|
||||
bool ds4_engine_has_mtp(ds4_engine *e);
|
||||
int ds4_engine_mtp_draft_tokens(ds4_engine *e);
|
||||
bool ds4_engine_mtp_exact_sampling(ds4_engine *e);
|
||||
const ds4_tokens *ds4_session_tokens(ds4_session *s);
|
||||
|
||||
/* Low-level graph slice entry points used by distributed inference. The
|
||||
|
||||
+2209
-591
File diff suppressed because it is too large.
Load diff
+131
-13
@@ -36,6 +36,7 @@ extern int cudaProfilerStop(void) __attribute__((weak));
|
||||
|
||||
typedef struct {
|
||||
const char *model_path;
|
||||
const char *mtp_path;
|
||||
const char *prompt_path;
|
||||
const char *chat_prompt_path;
|
||||
const char *system;
|
||||
@@ -69,6 +70,9 @@ typedef struct {
|
||||
bool cuda_tensor_parallel;
|
||||
bool show_output;
|
||||
bool teacher_forced_decode;
|
||||
bool dspark;
|
||||
bool dspark_confidence_threshold_set;
|
||||
float dspark_confidence_threshold;
|
||||
} bench_config;
|
||||
|
||||
static double bench_now_sec(void) {
|
||||
@@ -264,6 +268,19 @@ static bench_config parse_options(int argc, char **argv) {
|
||||
|
||||
if (!strcmp(arg, "-m") || !strcmp(arg, "--model")) {
|
||||
c.model_path = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--mtp-model")) {
|
||||
c.mtp_path = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--dspark")) {
|
||||
c.dspark = true;
|
||||
} else if (!strcmp(arg, "--dspark-confidence")) {
|
||||
const double v = parse_double_arg(need_arg(&i, argc, argv, arg), arg);
|
||||
if (v < 0.0 || v > 1.0) {
|
||||
fprintf(stderr, "ds4-bench: --dspark-confidence must be between 0 and 1\n");
|
||||
exit(2);
|
||||
}
|
||||
c.dspark = true;
|
||||
c.dspark_confidence_threshold = (float)v;
|
||||
c.dspark_confidence_threshold_set = true;
|
||||
} else if (!strcmp(arg, "--prompt-file")) {
|
||||
c.prompt_path = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--chat-prompt-file")) {
|
||||
@@ -369,6 +386,16 @@ static bench_config parse_options(int argc, char **argv) {
|
||||
fprintf(stderr, "ds4-bench: specify exactly one of --prompt-file or --chat-prompt-file\n");
|
||||
exit(2);
|
||||
}
|
||||
if (c.dspark && !c.mtp_path) {
|
||||
fprintf(stderr, "ds4-bench: --dspark requires --mtp-model FILE\n");
|
||||
exit(2);
|
||||
}
|
||||
if (c.dspark && c.teacher_forced_decode) {
|
||||
fprintf(stderr,
|
||||
"ds4-bench: --dspark cannot be combined with "
|
||||
"--teacher-forced-decode\n");
|
||||
exit(2);
|
||||
}
|
||||
if (c.ctx_start > c.ctx_max) {
|
||||
fprintf(stderr, "ds4-bench: --ctx-start must be <= --ctx-max\n");
|
||||
exit(2);
|
||||
@@ -620,6 +647,7 @@ int main(int argc, char **argv) {
|
||||
|
||||
ds4_engine_options opt = {
|
||||
.model_path = cfg.model_path,
|
||||
.mtp_path = cfg.mtp_path,
|
||||
.backend = cfg.backend,
|
||||
.n_threads = cfg.threads,
|
||||
.context_size = cfg.ctx_alloc,
|
||||
@@ -632,6 +660,9 @@ int main(int argc, char **argv) {
|
||||
.power_percent = cfg.power_percent,
|
||||
.warm_weights = cfg.warm_weights,
|
||||
.quality = cfg.quality,
|
||||
.dspark = cfg.dspark,
|
||||
.dspark_confidence_threshold = cfg.dspark_confidence_threshold,
|
||||
.dspark_confidence_threshold_set = cfg.dspark_confidence_threshold_set,
|
||||
.cuda_tensor_parallel = cfg.cuda_tensor_parallel,
|
||||
.ssd_streaming = cfg.ssd_streaming,
|
||||
.ssd_streaming_cold = cfg.ssd_streaming_cold,
|
||||
@@ -755,6 +786,20 @@ int main(int argc, char **argv) {
|
||||
const bool distributed =
|
||||
cfg.dist.role == DS4_DISTRIBUTED_COORDINATOR ||
|
||||
cfg.tp.role == DS4_TP_LEADER;
|
||||
const bool speculative = cfg.dspark && ds4_engine_mtp_draft_tokens(engine) > 1;
|
||||
if (cfg.dspark && !speculative) {
|
||||
fprintf(stderr, "ds4-bench: DSpark support model did not enable speculative decoding\n");
|
||||
if (out != stdout) fclose(out);
|
||||
ds4_session_free(session);
|
||||
ds4_tokens_free(&prompt);
|
||||
close_engine(engine, tp_leader);
|
||||
return 1;
|
||||
}
|
||||
if (speculative) {
|
||||
fprintf(stderr,
|
||||
"ds4-bench: DSpark enabled with draft width %d; frontier restoration uses session snapshots\n",
|
||||
ds4_engine_mtp_draft_tokens(engine));
|
||||
}
|
||||
ds4_session_snapshot snap = {0};
|
||||
const uint64_t snapshot_max_bytes = bench_snapshot_max_bytes();
|
||||
bool warned_large_snapshot = false;
|
||||
@@ -788,6 +833,9 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
const double prefill_t1 = bench_now_sec();
|
||||
const double prefill_sec = prefill_t1 - prefill_t0;
|
||||
if (getenv("DS4_METAL_CB_TIMES"))
|
||||
fprintf(stderr, "ds4-bench: prefill window mono %.1f .. %.1f ms\n",
|
||||
prefill_t0 * 1e3, prefill_t1 * 1e3);
|
||||
const int prefill_tokens = frontier - previous;
|
||||
|
||||
if (write_frontier_logits_json(&cfg, engine, session, frontier, previous) != 0) {
|
||||
@@ -825,6 +873,7 @@ int main(int argc, char **argv) {
|
||||
const double gen_t0 = bench_now_sec();
|
||||
double gen_first_sec = 0.0;
|
||||
double gen_steady_sec = 0.0;
|
||||
int gen_first_tokens = 0;
|
||||
int gen_done = 0;
|
||||
int *gen_token_buf = cfg.show_output && cfg.gen_tokens > 0
|
||||
? malloc((size_t)cfg.gen_tokens * sizeof(gen_token_buf[0]))
|
||||
@@ -845,14 +894,15 @@ int main(int argc, char **argv) {
|
||||
cuda_profile_start = -1;
|
||||
cuda_profile_tokens = 0;
|
||||
}
|
||||
for (int i = 0; i < cfg.gen_tokens; i++) {
|
||||
bool generation_stop = false;
|
||||
while (gen_done < cfg.gen_tokens && !generation_stop) {
|
||||
if (ds4_session_pos(session) + 1 >= ds4_session_ctx(session)) {
|
||||
fprintf(stderr, "ds4-bench: generation would exceed allocated context at frontier %d\n", frontier);
|
||||
rc = 1;
|
||||
break;
|
||||
}
|
||||
const int token = cfg.teacher_forced_decode
|
||||
? prompt.v[frontier + i]
|
||||
? prompt.v[frontier + gen_done]
|
||||
: ds4_session_argmax_excluding(session, eos);
|
||||
if (token < 0) {
|
||||
fprintf(stderr, "ds4-bench: failed to choose non-EOS token at frontier %d\n", frontier);
|
||||
@@ -861,27 +911,94 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
const double token_t0 = bench_now_sec();
|
||||
#if defined(DS4_BENCH_HAVE_CUDA_PROFILER)
|
||||
if (i == cuda_profile_start && cudaProfilerStart) {
|
||||
if (gen_done == cuda_profile_start && cudaProfilerStart) {
|
||||
(void)cudaProfilerStart();
|
||||
}
|
||||
#endif
|
||||
if (ds4_session_eval(session, token, err, sizeof(err)) != 0) {
|
||||
fprintf(stderr, "ds4-bench: decode at frontier %d failed: %s\n", frontier, err);
|
||||
rc = 1;
|
||||
break;
|
||||
int toks[17];
|
||||
int ntok = 1;
|
||||
if (speculative) {
|
||||
const double spec_t0 = bench_now_sec();
|
||||
ntok = ds4_session_eval_speculative_argmax(
|
||||
session, token, cfg.gen_tokens - gen_done, eos,
|
||||
toks, (int)(sizeof(toks) / sizeof(toks[0])),
|
||||
err, sizeof(err));
|
||||
if (getenv("DS4_BENCH_SPEC_TRACE") && ntok > 0 && ntok < 17) {
|
||||
static double call_ms[17];
|
||||
static unsigned call_n[17];
|
||||
static unsigned calls_total;
|
||||
call_ms[ntok] += (bench_now_sec() - spec_t0) * 1e3;
|
||||
call_n[ntok]++;
|
||||
if (++calls_total % 32u == 0u) {
|
||||
fprintf(stderr, "ds4-bench: spec calls %u:", calls_total);
|
||||
for (int k = 1; k < 17; k++)
|
||||
if (call_n[k])
|
||||
fprintf(stderr, " %d-tok x%u avg %.1f ms", k, call_n[k], call_ms[k] / call_n[k]);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
if (ntok < 0) {
|
||||
fprintf(stderr, "ds4-bench: DSpark decode at frontier %d failed: %s\n", frontier, err);
|
||||
rc = 1;
|
||||
break;
|
||||
}
|
||||
if (ntok == 0) {
|
||||
fprintf(stderr, "ds4-bench: DSpark decode at frontier %d accepted no tokens\n", frontier);
|
||||
rc = 1;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
toks[0] = token;
|
||||
if (ds4_session_eval(session, token, err, sizeof(err)) != 0) {
|
||||
fprintf(stderr, "ds4-bench: decode at frontier %d failed: %s\n", frontier, err);
|
||||
rc = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#if defined(DS4_BENCH_HAVE_CUDA_PROFILER)
|
||||
if (cuda_profile_start >= 0 &&
|
||||
i + 1 == cuda_profile_start + cuda_profile_tokens &&
|
||||
gen_done + ntok >=
|
||||
cuda_profile_start + cuda_profile_tokens &&
|
||||
cudaProfilerStop) {
|
||||
(void)cudaProfilerStop();
|
||||
}
|
||||
#endif
|
||||
const double token_t1 = bench_now_sec();
|
||||
if (i == 0) gen_first_sec = token_t1 - token_t0;
|
||||
else gen_steady_sec += token_t1 - token_t0;
|
||||
if (gen_token_buf) gen_token_buf[gen_token_count++] = token;
|
||||
gen_done++;
|
||||
/* Diagnostic: DS4_BENCH_DECODE_LOGITS_HASH prints an FNV-1a hash of
|
||||
* every decode step's full logits, to compare arms bit for bit. */
|
||||
if (!speculative && getenv("DS4_BENCH_DECODE_LOGITS_HASH")) {
|
||||
const int vocab = ds4_engine_vocab_size(engine);
|
||||
float *lg = vocab > 0 ? malloc((size_t)vocab * sizeof(lg[0])) : NULL;
|
||||
if (lg && ds4_session_copy_logits(session, lg, vocab) == vocab) {
|
||||
uint64_t h = 1469598103934665603ull;
|
||||
const unsigned char *b = (const unsigned char *)lg;
|
||||
for (size_t k = 0; k < (size_t)vocab * sizeof(lg[0]); k++) h = (h ^ b[k]) * 1099511628211ull;
|
||||
fprintf(stderr, "ds4-bench: decode-hash frontier=%d step=%d %016llx\n",
|
||||
frontier, gen_done, (unsigned long long)h);
|
||||
const char *dump = getenv("DS4_BENCH_DECODE_LOGITS_DUMP");
|
||||
if (dump) {
|
||||
FILE *fp = fopen(dump, "ab");
|
||||
if (fp) { fwrite(lg, sizeof(lg[0]), (size_t)vocab, fp); fclose(fp); }
|
||||
}
|
||||
}
|
||||
free(lg);
|
||||
}
|
||||
int cycle_tokens = 0;
|
||||
for (int j = 0; j < ntok && gen_done < cfg.gen_tokens; j++) {
|
||||
if (toks[j] == eos) {
|
||||
generation_stop = true;
|
||||
break;
|
||||
}
|
||||
if (gen_token_buf) gen_token_buf[gen_token_count++] = toks[j];
|
||||
gen_done++;
|
||||
cycle_tokens++;
|
||||
}
|
||||
if (gen_first_tokens == 0) {
|
||||
gen_first_sec = token_t1 - token_t0;
|
||||
gen_first_tokens = cycle_tokens;
|
||||
} else {
|
||||
gen_steady_sec += token_t1 - token_t0;
|
||||
}
|
||||
}
|
||||
const double gen_t1 = bench_now_sec();
|
||||
if (cfg.show_output && gen_token_buf && gen_token_count > 0) {
|
||||
@@ -917,7 +1034,8 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
const double gen_sec = gen_t1 - gen_t0;
|
||||
const int gen_steady_tokens = gen_done > 1 ? gen_done - 1 : 0;
|
||||
const int gen_steady_tokens = gen_done > gen_first_tokens ?
|
||||
gen_done - gen_first_tokens : 0;
|
||||
fprintf(out,
|
||||
"%d,%d,%.2f,%d,%.2f,%.3f,%d,%.2f,%llu\n",
|
||||
frontier,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "ds4_gpu_args.h"
|
||||
#include "ds4_tp.h"
|
||||
#include "ds4_help.h"
|
||||
#include "ds4_prompt_prefix.h"
|
||||
#include "linenoise.h"
|
||||
|
||||
/* ds4 CLI.
|
||||
@@ -61,6 +62,7 @@ static bool cli_greedy_argmax_requested(bool speculative_requested) {
|
||||
typedef struct {
|
||||
const char *prompt;
|
||||
const char *system;
|
||||
ds4_prompt_prefix prefix;
|
||||
bool raw_prompt;
|
||||
int n_predict;
|
||||
int ctx_size;
|
||||
@@ -81,6 +83,7 @@ typedef struct {
|
||||
const char *imatrix_output_path;
|
||||
int imatrix_max_prompts;
|
||||
int imatrix_max_tokens;
|
||||
int imatrix_min_expert_samples;
|
||||
ds4_think_mode think_mode;
|
||||
bool head_test;
|
||||
bool first_token_test;
|
||||
@@ -497,14 +500,29 @@ static void print_generated_token(void *ud, int token) {
|
||||
free(text);
|
||||
}
|
||||
|
||||
static void build_chat_prompt(ds4_engine *engine,
|
||||
const cli_generation_options *gen,
|
||||
ds4_tokens *out) {
|
||||
const ds4_think_mode think_mode = cli_effective_think_mode(gen);
|
||||
ds4_chat_begin(engine, out);
|
||||
ds4_chat_append_think_prefix(engine, out, think_mode);
|
||||
if (gen->system && gen->system[0])
|
||||
ds4_chat_append_message(engine, out, "system", gen->system);
|
||||
ds4_prompt_prefix_append(engine, out, &gen->prefix);
|
||||
ds4_chat_append_message(engine, out, "user", gen->prompt ? gen->prompt : "");
|
||||
ds4_chat_append_assistant_prefix(engine, out, think_mode);
|
||||
}
|
||||
|
||||
static void build_prompt(ds4_engine *engine, const cli_generation_options *gen, ds4_tokens *out) {
|
||||
if (gen->raw_prompt) {
|
||||
ds4_tokenize_text(engine, gen->prompt ? gen->prompt : "", out);
|
||||
} else if (is_rendered_chat_prompt(gen->prompt)) {
|
||||
ds4_tokenize_rendered_chat(engine, gen->prompt, out);
|
||||
} else {
|
||||
} else if (gen->prefix.count == 0) {
|
||||
ds4_encode_chat_prompt(engine, gen->system, gen->prompt,
|
||||
cli_effective_think_mode(gen), out);
|
||||
} else {
|
||||
build_chat_prompt(engine, gen, out);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1268,8 +1286,8 @@ static char *trim_inplace(char *s) {
|
||||
static void print_repl_help(void) {
|
||||
puts("Commands:");
|
||||
puts(" /help Show this help.");
|
||||
puts(" /think Use normal thinking mode.");
|
||||
puts(" /think-max Use Think Max only when context is at least 393216 tokens.");
|
||||
puts(" /think [N] Use normal thinking, or V4.1 effort 0..100 (0 disables thinking).");
|
||||
puts(" /think-max Use maximum thinking (V4.1: 100; V4: requires ctx >= 393216).");
|
||||
puts(" /nothink Disable thinking mode.");
|
||||
puts(" /ctx N Set context size for following prompts.");
|
||||
puts(" /power N Set GPU duty cycle percentage, 1..100.");
|
||||
@@ -1314,8 +1332,11 @@ typedef struct {
|
||||
int ctx_size;
|
||||
int think_prefix_pos;
|
||||
int think_prefix_tokens;
|
||||
bool initial_system_text;
|
||||
} repl_chat;
|
||||
|
||||
static void repl_chat_free(repl_chat *chat);
|
||||
|
||||
static void repl_chat_trim_images(repl_chat *chat, size_t count) {
|
||||
while (chat->image_count > count) {
|
||||
chat->image_count--;
|
||||
@@ -1364,34 +1385,22 @@ static void tokens_remove(ds4_tokens *dst, int pos, int n) {
|
||||
dst->len -= n;
|
||||
}
|
||||
|
||||
static const char *repl_glm_reasoning_effort_text(ds4_think_mode mode) {
|
||||
switch (mode) {
|
||||
case DS4_THINK_HIGH: return "Reasoning Effort: High";
|
||||
case DS4_THINK_MAX: return "Reasoning Effort: Max";
|
||||
case DS4_THINK_NONE: return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void repl_chat_build_think_prefix(ds4_engine *engine,
|
||||
ds4_think_mode mode,
|
||||
ds4_tokens *prefix) {
|
||||
if (ds4_engine_is_glm_dsa(engine)) {
|
||||
const char *effort = repl_glm_reasoning_effort_text(mode);
|
||||
if (effort) ds4_chat_append_message(engine, prefix, "system", effort);
|
||||
} else if (mode == DS4_THINK_MAX) {
|
||||
ds4_chat_append_max_effort_prefix(engine, prefix);
|
||||
}
|
||||
ds4_chat_append_think_prefix(engine, prefix, mode);
|
||||
}
|
||||
|
||||
/* Insert/replace the model-family thinking prefix inside the existing
|
||||
* transcript. It lives immediately after the BOS sequence and before any
|
||||
* user/system text, matching the GGUF chat templates. */
|
||||
static void repl_chat_apply_think_prefix(ds4_engine *engine,
|
||||
static bool repl_chat_apply_think_prefix(ds4_engine *engine,
|
||||
repl_chat *chat,
|
||||
ds4_think_mode mode) {
|
||||
ds4_tokens prefix = {0};
|
||||
repl_chat_build_think_prefix(engine, mode, &prefix);
|
||||
if (ds4_engine_is_deepseek41(engine) && chat->initial_system_text && !prefix.len)
|
||||
ds4_chat_append_message(engine, &prefix, "system", "");
|
||||
|
||||
bool same = chat->think_prefix_tokens == prefix.len;
|
||||
if (same && prefix.len > 0) {
|
||||
@@ -1401,11 +1410,16 @@ static void repl_chat_apply_think_prefix(ds4_engine *engine,
|
||||
}
|
||||
if (!same) {
|
||||
const int old_prefix_tokens = chat->think_prefix_tokens;
|
||||
const int shift = prefix.len - old_prefix_tokens;
|
||||
if (chat->ctx_size > 0 && (int64_t)chat->transcript.len + shift >= chat->ctx_size) {
|
||||
fprintf(stderr, "ds4: no context room to change the thinking prefix\n");
|
||||
ds4_tokens_free(&prefix);
|
||||
return false;
|
||||
}
|
||||
tokens_remove(&chat->transcript, chat->think_prefix_pos,
|
||||
chat->think_prefix_tokens);
|
||||
tokens_insert(&chat->transcript, chat->think_prefix_pos, &prefix);
|
||||
chat->think_prefix_tokens = prefix.len;
|
||||
const int shift = prefix.len - old_prefix_tokens;
|
||||
for (size_t i = 0; i < chat->image_count; i++) {
|
||||
chat->images[i].token_start =
|
||||
(uint32_t)((int64_t)chat->images[i].token_start + shift);
|
||||
@@ -1413,6 +1427,7 @@ static void repl_chat_apply_think_prefix(ds4_engine *engine,
|
||||
if (chat->session) ds4_session_invalidate(chat->session);
|
||||
}
|
||||
ds4_tokens_free(&prefix);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int repl_chat_create_session(ds4_engine *engine, repl_chat *chat, int ctx_size) {
|
||||
@@ -1429,13 +1444,47 @@ static int repl_chat_create_session(ds4_engine *engine, repl_chat *chat, int ctx
|
||||
|
||||
static int repl_chat_init(ds4_engine *engine, repl_chat *chat, const cli_config *cfg) {
|
||||
memset(chat, 0, sizeof(*chat));
|
||||
chat->initial_system_text = cfg->gen.system && cfg->gen.system[0];
|
||||
ds4_chat_begin(engine, &chat->transcript);
|
||||
chat->think_prefix_pos = chat->transcript.len;
|
||||
repl_chat_apply_think_prefix(engine, chat, cli_effective_think_mode(&cfg->gen));
|
||||
if (cfg->gen.system && cfg->gen.system[0]) {
|
||||
ds4_chat_append_message(engine, &chat->transcript, "system", cfg->gen.system);
|
||||
}
|
||||
return repl_chat_create_session(engine, chat, cfg->gen.ctx_size);
|
||||
ds4_prompt_prefix_append(engine, &chat->transcript, &cfg->gen.prefix);
|
||||
if (repl_chat_create_session(engine, chat, cfg->gen.ctx_size) != 0) {
|
||||
ds4_tokens_free(&chat->transcript);
|
||||
return 1;
|
||||
}
|
||||
if (cfg->gen.prefix.count == 0) return 0;
|
||||
if (cli_wait_distributed_route(cfg, chat->session) != 0) {
|
||||
repl_chat_free(chat);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char err[160] = {0};
|
||||
cli_prefill_progress progress = {
|
||||
.base_tokens = 0,
|
||||
.input_tokens = chat->transcript.len,
|
||||
.use_color = ds4_log_is_tty(stderr),
|
||||
};
|
||||
ds4_session_set_progress(chat->session, cli_prefill_progress_cb, &progress);
|
||||
ds4_session_set_display_progress(chat->session,
|
||||
progress.use_color ? cli_prefill_progress_cb : NULL,
|
||||
progress.use_color ? &progress : NULL);
|
||||
cli_dist_busy_set(cfg, true);
|
||||
int rc = ds4_session_sync(chat->session, &chat->transcript,
|
||||
err, sizeof(err));
|
||||
cli_dist_busy_set(cfg, false);
|
||||
ds4_session_set_progress(chat->session, NULL, NULL);
|
||||
ds4_session_set_display_progress(chat->session, NULL, NULL);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "ds4: prefix prefill failed: %s\n",
|
||||
err[0] ? err : "unknown error");
|
||||
repl_chat_free(chat);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void repl_chat_free(repl_chat *chat) {
|
||||
@@ -1472,27 +1521,22 @@ static int run_chat_turn(ds4_engine *engine, cli_config *cfg, repl_chat *chat,
|
||||
|
||||
ds4_think_mode think_mode = ds4_think_mode_for_context(cfg->gen.think_mode,
|
||||
chat->ctx_size);
|
||||
repl_chat_apply_think_prefix(engine, chat, think_mode);
|
||||
if (!repl_chat_apply_think_prefix(engine, chat, think_mode)) return 1;
|
||||
const int rollback_len = chat->transcript.len;
|
||||
const size_t rollback_images = chat->image_count;
|
||||
if (image) {
|
||||
ds4_tokens_push(&chat->transcript, ds4_token_user(engine));
|
||||
ds4_vision_span *span = repl_chat_add_image(chat);
|
||||
const char *text_parts[] = {"", user_text ? user_text : ""};
|
||||
char image_error[160] = {0};
|
||||
if (!span || !ds4_prompt_append_vision(engine, &chat->transcript,
|
||||
span, image,
|
||||
image_error,
|
||||
sizeof(image_error))) {
|
||||
if (!span || !ds4_chat_append_multimodal_message(
|
||||
engine, &chat->transcript, "user", text_parts,
|
||||
image, 1, span, image_error, sizeof(image_error))) {
|
||||
repl_chat_trim_images(chat, rollback_images);
|
||||
chat->transcript.len = rollback_len;
|
||||
fprintf(stderr, "ds4: failed to add image: %s\n",
|
||||
image_error[0] ? image_error : "out of memory");
|
||||
return 1;
|
||||
}
|
||||
if (user_text && user_text[0]) {
|
||||
ds4_tokenize_text(engine, "\n", &chat->transcript);
|
||||
ds4_tokenize_text(engine, user_text, &chat->transcript);
|
||||
}
|
||||
} else {
|
||||
ds4_chat_append_message(engine, &chat->transcript, "user", user_text);
|
||||
}
|
||||
@@ -1702,22 +1746,31 @@ static int run_repl(ds4_engine *engine, cli_config *cfg) {
|
||||
|
||||
if (!strcmp(cmd, "/help")) {
|
||||
print_repl_help();
|
||||
} else if (!strcmp(cmd, "/think")) {
|
||||
cfg->gen.think_mode = DS4_THINK_HIGH;
|
||||
repl_chat_apply_think_prefix(engine, &chat, DS4_THINK_HIGH);
|
||||
puts("Thinking mode: high.");
|
||||
} else if (!strncmp(cmd, "/think", 6) &&
|
||||
(cmd[6] == '\0' || isspace((unsigned char)cmd[6]))) {
|
||||
const char *arg = trim_inplace(cmd + 6);
|
||||
ds4_think_mode mode = DS4_THINK_HIGH;
|
||||
if (arg[0] && (!ds4_engine_is_deepseek41(engine) ||
|
||||
!ds4_think_mode_parse_level(arg, &mode))) {
|
||||
fprintf(stderr, "ds4: /think N requires V4.1 and a level from 0 to 100\n");
|
||||
} else if (repl_chat_apply_think_prefix(engine, &chat, mode)) {
|
||||
cfg->gen.think_mode = mode;
|
||||
printf("Thinking mode: %s.\n", ds4_think_mode_name(mode));
|
||||
}
|
||||
} else if (!strcmp(cmd, "/think-max")) {
|
||||
cfg->gen.think_mode = DS4_THINK_MAX;
|
||||
bool active = ds4_think_mode_for_context(cfg->gen.think_mode,
|
||||
bool active = ds4_think_mode_for_context(DS4_THINK_MAX,
|
||||
chat.ctx_size) == DS4_THINK_MAX;
|
||||
repl_chat_apply_think_prefix(engine, &chat,
|
||||
active ? DS4_THINK_MAX : DS4_THINK_HIGH);
|
||||
cli_warn_think_max_downgraded(&cfg->gen, "/think-max");
|
||||
printf("Thinking mode: %s.\n", active ? "max" : "high (ctx below 393216)");
|
||||
if (repl_chat_apply_think_prefix(engine, &chat,
|
||||
active ? DS4_THINK_MAX : DS4_THINK_HIGH)) {
|
||||
cfg->gen.think_mode = DS4_THINK_MAX;
|
||||
cli_warn_think_max_downgraded(&cfg->gen, "/think-max");
|
||||
printf("Thinking mode: %s.\n", active ? "max" : "high (ctx below 393216)");
|
||||
}
|
||||
} else if (!strcmp(cmd, "/nothink")) {
|
||||
cfg->gen.think_mode = DS4_THINK_NONE;
|
||||
repl_chat_apply_think_prefix(engine, &chat, DS4_THINK_NONE);
|
||||
puts("Thinking mode: none.");
|
||||
if (repl_chat_apply_think_prefix(engine, &chat, DS4_THINK_NONE)) {
|
||||
cfg->gen.think_mode = DS4_THINK_NONE;
|
||||
puts("Thinking mode: none.");
|
||||
}
|
||||
} else if (!strncmp(cmd, "/power", 6) && (cmd[6] == '\0' || isspace((unsigned char)cmd[6]))) {
|
||||
char *arg = trim_inplace(cmd + 6);
|
||||
if (!arg[0]) {
|
||||
@@ -1942,6 +1995,19 @@ static cli_config parse_options(int argc, char **argv) {
|
||||
}
|
||||
c.prompt_owned = read_prompt_file(need_arg(&i, argc, argv, arg), true);
|
||||
c.gen.prompt = c.prompt_owned;
|
||||
} else if (!strcmp(arg, "--prefix-file")) {
|
||||
if (c.gen.prefix.count != 0) {
|
||||
fprintf(stderr, "ds4: specify --prefix-file only once\n");
|
||||
exit(2);
|
||||
}
|
||||
const char *path = need_arg(&i, argc, argv, arg);
|
||||
char error[256] = {0};
|
||||
if (ds4_prompt_prefix_load(&c.gen.prefix, path,
|
||||
error, sizeof(error)) != 0) {
|
||||
fprintf(stderr, "ds4: %s\n",
|
||||
error[0] ? error : "invalid prefix file");
|
||||
exit(2);
|
||||
}
|
||||
} else if (!strcmp(arg, "-sys") || !strcmp(arg, "--system")) {
|
||||
c.gen.system = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--raw") || !strcmp(arg, "--raw-prompt")) {
|
||||
@@ -2088,6 +2154,14 @@ static cli_config parse_options(int argc, char **argv) {
|
||||
c.gen.imatrix_max_prompts = parse_int(need_arg(&i, argc, argv, arg), arg);
|
||||
} else if (!strcmp(arg, "--imatrix-max-tokens")) {
|
||||
c.gen.imatrix_max_tokens = parse_int(need_arg(&i, argc, argv, arg), arg);
|
||||
} else if (!strcmp(arg, "--imatrix-min-expert-samples")) {
|
||||
c.gen.imatrix_min_expert_samples =
|
||||
parse_int(need_arg(&i, argc, argv, arg), arg);
|
||||
} else if (!strcmp(arg, "--think-level")) {
|
||||
if (!ds4_think_mode_parse_level(need_arg(&i, argc, argv, arg), &c.gen.think_mode)) {
|
||||
fprintf(stderr, "ds4: --think-level requires an integer from 0 to 100\n");
|
||||
exit(2);
|
||||
}
|
||||
} else if (!strcmp(arg, "--think")) {
|
||||
c.gen.think_mode = DS4_THINK_HIGH;
|
||||
} else if (!strcmp(arg, "--think-max")) {
|
||||
@@ -2147,10 +2221,23 @@ static cli_config parse_options(int argc, char **argv) {
|
||||
fprintf(stderr, "ds4: --imatrix-dataset requires --imatrix-out\n");
|
||||
exit(2);
|
||||
}
|
||||
if (c.gen.imatrix_min_expert_samples < 0) {
|
||||
fprintf(stderr, "ds4: --imatrix-min-expert-samples must not be negative\n");
|
||||
exit(2);
|
||||
}
|
||||
if (c.gen.perplexity_file_path && c.gen.prompt) {
|
||||
fprintf(stderr, "ds4: --perplexity-file does not use -p/--prompt-file\n");
|
||||
exit(2);
|
||||
}
|
||||
if (c.gen.prefix.count != 0 && c.gen.raw_prompt) {
|
||||
fprintf(stderr, "ds4: --prefix-file cannot be combined with --raw-prompt\n");
|
||||
exit(2);
|
||||
}
|
||||
if (c.gen.prefix.count != 0 && is_rendered_chat_prompt(c.gen.prompt)) {
|
||||
fprintf(stderr,
|
||||
"ds4: --prefix-file cannot be combined with an already-rendered prompt\n");
|
||||
exit(2);
|
||||
}
|
||||
char tp_err[256];
|
||||
if (!ds4_tp_adopt_distributed_options(&c.engine.tp, c.dist,
|
||||
tp_err, sizeof(tp_err))) {
|
||||
@@ -2173,8 +2260,17 @@ static cli_config parse_options(int argc, char **argv) {
|
||||
int main(int argc, char **argv) {
|
||||
cli_config cfg = parse_options(argc, argv);
|
||||
if (cfg.gen.dump_tokens) {
|
||||
if (cfg.gen.prefix.count != 0) {
|
||||
fprintf(stderr, "ds4: --dump-tokens does not support --prefix-file\n");
|
||||
ds4_prompt_prefix_free(&cfg.gen.prefix);
|
||||
ds4_dist_options_free(cfg.dist);
|
||||
free(cfg.prompt_owned);
|
||||
return 2;
|
||||
}
|
||||
if (cfg.gen.prompt == NULL) {
|
||||
fprintf(stderr, "ds4: --dump-tokens requires -p or --prompt-file\n");
|
||||
ds4_prompt_prefix_free(&cfg.gen.prefix);
|
||||
ds4_dist_options_free(cfg.dist);
|
||||
free(cfg.prompt_owned);
|
||||
return 2;
|
||||
}
|
||||
@@ -2187,10 +2283,12 @@ int main(int argc, char **argv) {
|
||||
rc = ds4_dump_chat_tokenization(cfg.engine.model_path,
|
||||
cfg.gen.system,
|
||||
cfg.gen.prompt,
|
||||
cli_effective_think_mode(&cfg.gen),
|
||||
cfg.gen.think_mode,
|
||||
cfg.gen.ctx_size,
|
||||
stdout);
|
||||
}
|
||||
ds4_dist_options_free(cfg.dist);
|
||||
ds4_prompt_prefix_free(&cfg.gen.prefix);
|
||||
free(cfg.prompt_owned);
|
||||
return rc;
|
||||
}
|
||||
@@ -2241,6 +2339,14 @@ int main(int argc, char **argv) {
|
||||
free(cfg.prompt_owned);
|
||||
return 1;
|
||||
}
|
||||
if (ds4_think_mode_level(cfg.gen.think_mode) >= 0 && !ds4_engine_is_deepseek41(engine)) {
|
||||
fprintf(stderr, "ds4: --think-level requires a DeepSeek V4.1 model\n");
|
||||
ds4_engine_close(engine);
|
||||
ds4_dist_options_free(cfg.dist);
|
||||
ds4_prompt_prefix_free(&cfg.gen.prefix);
|
||||
free(cfg.prompt_owned);
|
||||
return 2;
|
||||
}
|
||||
cli_apply_model_sampling_defaults(engine, &cfg.gen);
|
||||
if (cfg.engine.tp.role == DS4_TP_WORKER) {
|
||||
int rc = ds4_tp_worker_run(engine, &cfg.engine.tp);
|
||||
@@ -2313,7 +2419,8 @@ int main(int argc, char **argv) {
|
||||
cfg.gen.imatrix_output_path,
|
||||
cfg.gen.ctx_size,
|
||||
cfg.gen.imatrix_max_prompts,
|
||||
cfg.gen.imatrix_max_tokens);
|
||||
cfg.gen.imatrix_max_tokens,
|
||||
cfg.gen.imatrix_min_expert_samples);
|
||||
} else if (cfg.gen.perplexity_file_path) {
|
||||
rc = run_perplexity_file(engine, &cfg);
|
||||
} else if (cfg.gen.prompt == NULL) {
|
||||
@@ -2325,6 +2432,7 @@ int main(int argc, char **argv) {
|
||||
ds4_engine_close(engine);
|
||||
ds4_tp_free(tp_leader);
|
||||
ds4_dist_options_free(cfg.dist);
|
||||
ds4_prompt_prefix_free(&cfg.gen.prefix);
|
||||
free(cfg.prompt_owned);
|
||||
return rc;
|
||||
}
|
||||
+4330
-736
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,471 @@
|
||||
/* Internal V4.1 drafter graph. Included after the target graph in ds4.c. */
|
||||
#ifndef DS4_DEEPSEEK41_DSPARK_H
|
||||
#define DS4_DEEPSEEK41_DSPARK_H
|
||||
|
||||
#define DS41_DRAFT_WINDOW 128u
|
||||
#define DS41_DRAFT_ROWS 5u
|
||||
#define DS41_VERIFY_ROWS (DS41_DRAFT_ROWS + 1u)
|
||||
|
||||
struct ds41_dspark {
|
||||
const ds4_dspark_weights *weights;
|
||||
ds41_prefill_row batch;
|
||||
ds4_gpu_tensor *target[3], *window[3];
|
||||
uint32_t positions[3][DS41_DRAFT_WINDOW];
|
||||
uint32_t kv_end;
|
||||
bool kv_valid;
|
||||
ds4_gpu_tensor *mean_weights, *mean, *input, *projected, *main_x, *main_kv;
|
||||
ds4_gpu_tensor *logits, *tokens, *markov_embed, *markov_logits, *confidence_input, *confidence;
|
||||
float *host_logits, *draft_probs;
|
||||
ds4_gpu_tensor *verify_residual, *verify_pre, *verify_logits, *verify_pool;
|
||||
ds4_gpu_tensor *old_raw, *old_features;
|
||||
uint32_t old_positions[3][DS41_VERIFY_ROWS];
|
||||
ds41_gpu_graph verified[DS41_VERIFY_ROWS + 1u];
|
||||
float *host_verified;
|
||||
uint32_t verify_start, verify_count, rewind_end;
|
||||
};
|
||||
|
||||
static void ds41_dspark_reset(ds41_dspark *d) {
|
||||
if (!d) return;
|
||||
memset(d->positions, 0xff, sizeof(d->positions));
|
||||
d->kv_valid = false;
|
||||
d->kv_end = 0;
|
||||
d->verify_count = 0;
|
||||
d->rewind_end = 0;
|
||||
}
|
||||
|
||||
static void ds41_dspark_free(ds41_dspark *d) {
|
||||
if (!d) return;
|
||||
#define DS41_DRAFT_FREE(name, count) ds4_gpu_tensor_free(d->batch.name);
|
||||
DS41_PREFILL_ROWS(DS41_DRAFT_FREE)
|
||||
#undef DS41_DRAFT_FREE
|
||||
for (unsigned i = 0; i < 3; i++) {
|
||||
ds4_gpu_tensor_free(d->target[i]);
|
||||
ds4_gpu_tensor_free(d->window[i]);
|
||||
}
|
||||
ds4_gpu_tensor_free(d->mean_weights); ds4_gpu_tensor_free(d->mean);
|
||||
ds4_gpu_tensor_free(d->input); ds4_gpu_tensor_free(d->projected);
|
||||
ds4_gpu_tensor_free(d->main_x); ds4_gpu_tensor_free(d->main_kv);
|
||||
ds4_gpu_tensor_free(d->logits); ds4_gpu_tensor_free(d->markov_embed);
|
||||
ds4_gpu_tensor_free(d->tokens);
|
||||
ds4_gpu_tensor_free(d->markov_logits); ds4_gpu_tensor_free(d->confidence_input);
|
||||
ds4_gpu_tensor_free(d->confidence);
|
||||
for (unsigned i = 0; i <= DS41_VERIFY_ROWS; i++) {
|
||||
ds41_gpu_graph *g = &d->verified[i];
|
||||
ds4_gpu_tensor_free(g->residual); ds4_gpu_tensor_free(g->pre);
|
||||
ds4_gpu_tensor_free(g->logits);
|
||||
ds4_gpu_tensor_free(g->tp_logits_half);
|
||||
for (unsigned j = 0; j < 4; j++) {
|
||||
ds4_gpu_tensor_free(g->verify_previous_kv[j]);
|
||||
ds4_gpu_tensor_free(g->verify_previous_score[j]);
|
||||
}
|
||||
}
|
||||
ds4_gpu_tensor_free(d->verify_residual); ds4_gpu_tensor_free(d->verify_pre);
|
||||
ds4_gpu_tensor_free(d->verify_logits); ds4_gpu_tensor_free(d->verify_pool);
|
||||
ds4_gpu_tensor_free(d->old_raw); ds4_gpu_tensor_free(d->old_features);
|
||||
free(d->host_verified);
|
||||
free(d->host_logits);
|
||||
free(d->draft_probs);
|
||||
free(d);
|
||||
}
|
||||
|
||||
static DS4_MAYBE_UNUSED ds41_dspark *ds41_dspark_alloc(const ds4_dspark_weights *dw) {
|
||||
if (!dw || !dw->v41 || dw->n_stages != 3 || dw->block_size != DS41_DRAFT_ROWS ||
|
||||
dw->missing_tensors || dw->invalid_tensors || dw->metadata_errors) return NULL;
|
||||
ds41_dspark *d = calloc(1, sizeof(*d));
|
||||
if (!d) return NULL;
|
||||
d->weights = dw;
|
||||
const ds41_gpu_graph shape = {.ctx = DS41_DRAFT_WINDOW}, *g = &shape;
|
||||
#define DS41_DRAFT_ALLOC(name, width) \
|
||||
if (!(d->batch.name = ds4_gpu_tensor_alloc((uint64_t)(width) * DS41_DRAFT_ROWS * sizeof(float)))) goto fail;
|
||||
DS41_PREFILL_ROWS(DS41_DRAFT_ALLOC)
|
||||
#undef DS41_DRAFT_ALLOC
|
||||
for (unsigned i = 0; i < 3; i++) {
|
||||
d->target[i] = ds4_gpu_tensor_alloc((uint64_t)DS41_DRAFT_WINDOW * DS4_N_EMBD * 4u);
|
||||
d->window[i] = ds4_gpu_tensor_alloc((uint64_t)(DS41_DRAFT_WINDOW + DS41_DRAFT_ROWS) * DS4_N_HEAD_DIM * 4u);
|
||||
if (!d->target[i] || !d->window[i]) goto fail;
|
||||
}
|
||||
#define DS41_DRAFT_A(name, count) \
|
||||
if (!(d->name = ds4_gpu_tensor_alloc((uint64_t)(count) * sizeof(float)))) goto fail;
|
||||
DS41_DRAFT_A(mean_weights, DS41_DRAFT_WINDOW * DS4_N_HC)
|
||||
DS41_DRAFT_A(mean, DS41_DRAFT_WINDOW * DS4_N_EMBD)
|
||||
DS41_DRAFT_A(input, DS41_DRAFT_WINDOW * 3u * DS4_N_EMBD)
|
||||
DS41_DRAFT_A(projected, DS41_DRAFT_WINDOW * DS4_N_EMBD)
|
||||
DS41_DRAFT_A(main_x, DS41_DRAFT_WINDOW * DS4_N_EMBD)
|
||||
DS41_DRAFT_A(main_kv, DS41_DRAFT_WINDOW * DS4_N_HEAD_DIM)
|
||||
DS41_DRAFT_A(logits, DS41_DRAFT_ROWS * DS4_N_VOCAB)
|
||||
DS41_DRAFT_A(tokens, DS41_DRAFT_ROWS)
|
||||
DS41_DRAFT_A(markov_embed, dw->markov_rank)
|
||||
DS41_DRAFT_A(markov_logits, DS4_N_VOCAB)
|
||||
DS41_DRAFT_A(confidence_input, DS4_N_EMBD + dw->markov_rank)
|
||||
DS41_DRAFT_A(confidence, 1)
|
||||
DS41_DRAFT_A(verify_residual, (DS41_VERIFY_ROWS + 1u) * DS4_N_HC * DS4_N_EMBD)
|
||||
DS41_DRAFT_A(verify_pre, (DS41_VERIFY_ROWS + 1u) * DS4_N_HC)
|
||||
DS41_DRAFT_A(verify_logits, (DS41_VERIFY_ROWS + 1u) * DS4_N_VOCAB)
|
||||
DS41_DRAFT_A(verify_pool, (DS41_VERIFY_ROWS + 1u) * 8u * DS4_N_HEAD_DIM)
|
||||
DS41_DRAFT_A(old_raw, 40u * DS41_VERIFY_ROWS * DS4_N_HEAD_DIM)
|
||||
DS41_DRAFT_A(old_features, 3u * DS41_VERIFY_ROWS * DS4_N_EMBD)
|
||||
#undef DS41_DRAFT_A
|
||||
d->host_logits = malloc((uint64_t)DS41_DRAFT_ROWS * DS4_N_VOCAB * sizeof(float));
|
||||
d->draft_probs = malloc((uint64_t)DS41_DRAFT_ROWS * DS4_N_VOCAB * sizeof(float));
|
||||
d->host_verified = malloc((uint64_t)(DS41_VERIFY_ROWS + 1u) * DS4_N_VOCAB * sizeof(float));
|
||||
if (!d->host_logits || !d->draft_probs || !d->host_verified || !ds4_gpu_tensor_fill_f32(d->mean_weights,
|
||||
1.0f / DS4_N_HC, DS41_DRAFT_WINDOW * DS4_N_HC) ||
|
||||
!ds4_gpu_tensor_fill_f32(d->tokens, 0, DS41_DRAFT_ROWS)) goto fail;
|
||||
ds41_dspark_reset(d);
|
||||
return d;
|
||||
fail:
|
||||
ds41_dspark_free(d);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool ds41_dspark_capture(ds41_dspark *d, uint32_t layer, uint32_t pos,
|
||||
const ds4_gpu_tensor *residual, uint32_t rows) {
|
||||
if (!d) return true;
|
||||
d->rewind_end = 0;
|
||||
unsigned stage = 0;
|
||||
while (stage < 3 && d->weights->target_layers[stage] != layer) stage++;
|
||||
if (stage == 3) return true;
|
||||
if (!rows || rows > UINT32_MAX - pos) return false;
|
||||
const uint32_t count = rows < DS41_DRAFT_WINDOW ? rows : DS41_DRAFT_WINDOW;
|
||||
const uint32_t first = rows - count, start = pos + first;
|
||||
const uint64_t bytes = (uint64_t)DS4_N_EMBD * sizeof(float);
|
||||
ds4_gpu_tensor *src = ds4_gpu_tensor_view((ds4_gpu_tensor *)residual,
|
||||
first * DS4_N_HC * bytes, count * DS4_N_HC * bytes);
|
||||
ds4_gpu_tensor *out = ds4_gpu_tensor_view(d->mean, 0, count * bytes);
|
||||
ds4_gpu_tensor *mix = ds4_gpu_tensor_view(d->mean_weights, 0, count * DS4_N_HC * sizeof(float));
|
||||
bool ok = src && out && mix && ds4_gpu_hc_weighted_sum_tensor(out, src, mix, DS4_N_EMBD, DS4_N_HC) &&
|
||||
ds4_gpu_dsv41_quantize(out, DS4_N_EMBD, count, DS4_V41_BF16);
|
||||
const uint32_t slot = start % DS41_DRAFT_WINDOW;
|
||||
const uint32_t part = count < DS41_DRAFT_WINDOW - slot ? count : DS41_DRAFT_WINDOW - slot;
|
||||
if (ok) ok = ds4_gpu_tensor_copy(d->target[stage], slot * bytes, out, 0, part * bytes) &&
|
||||
(part == count || ds4_gpu_tensor_copy(d->target[stage], 0, out, part * bytes, (count - part) * bytes));
|
||||
ds4_gpu_tensor_free(mix); ds4_gpu_tensor_free(out); ds4_gpu_tensor_free(src);
|
||||
if (ok) for (uint32_t i = 0; i < count; i++)
|
||||
d->positions[stage][(start + i) % DS41_DRAFT_WINDOW] = start + i;
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool ds41_dspark_matmul_batch(ds4_gpu_tensor *out, const ds4_model *m,
|
||||
const ds4_tensor *weight, const ds4_gpu_tensor *in, uint32_t count, bool round) {
|
||||
#if !defined(__APPLE__) && !defined(DS4_ROCM_BUILD)
|
||||
/* Draft rows share a GEMM. Target verification retains its scalar-reduction
|
||||
* kernels; proposal arithmetic need not reproduce their reduction order. */
|
||||
if (count > 1 && weight->type == DS4_TENSOR_F16 &&
|
||||
!getenv("DS4_DISABLE_V41_DRAFT_GEMM"))
|
||||
return metal_graph_matmul_plain_tensor(out, m, weight, weight->dim[0],
|
||||
weight->dim[1], in, count) && (!round ||
|
||||
ds4_gpu_dsv41_quantize(out, (uint32_t)weight->dim[1], count, DS4_V41_BF16));
|
||||
#endif
|
||||
return ds41_matmul_batch(out, m, weight, in, count, round);
|
||||
}
|
||||
|
||||
/* Only accepted target features enter these rings. A fresh prefill seeds the
|
||||
* last window once; subsequent calls project only the newly accepted rows. */
|
||||
static bool ds41_dspark_seed(ds41_dspark *d, const ds4_model *m, uint32_t pos) {
|
||||
if (!pos) return false;
|
||||
const uint32_t oldest = pos > DS41_DRAFT_WINDOW ? pos - DS41_DRAFT_WINDOW : 0;
|
||||
const uint32_t start = d->kv_valid && d->kv_end >= oldest && d->kv_end <= pos ? d->kv_end : oldest;
|
||||
const uint32_t count = pos - start;
|
||||
if (!count) return true;
|
||||
const uint64_t row_bytes = (uint64_t)DS4_N_EMBD * sizeof(float);
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
const uint32_t slot = (start + i) % DS41_DRAFT_WINDOW;
|
||||
for (unsigned stage = 0; stage < 3; stage++) {
|
||||
if (d->positions[stage][slot] != start + i ||
|
||||
!ds4_gpu_tensor_copy(d->input, (i * 3u + stage) * row_bytes,
|
||||
d->target[stage], slot * row_bytes, row_bytes)) return false;
|
||||
}
|
||||
}
|
||||
const ds4_dspark_stage_weights *first = &d->weights->stage[0];
|
||||
if (!ds41_dspark_matmul_batch(d->projected, m, first->main_proj, d->input, count, true) ||
|
||||
!ds41_norm_batch(d->main_x, d->projected, m, first->main_norm, count)) return false;
|
||||
const uint64_t kv_bytes = DS4_N_HEAD_DIM * sizeof(float);
|
||||
for (unsigned stage = 0; stage < 3; stage++) {
|
||||
const ds4_layer_weights *l = &d->weights->stage[stage].block;
|
||||
if (!ds41_dspark_matmul_batch(d->main_kv, m, l->attn_kv, d->main_x, count, true) ||
|
||||
!ds41_norm_batch(d->main_kv, d->main_kv, m, l->attn_kv_a_norm, count) ||
|
||||
!ds4_gpu_dsv41_rope(d->main_kv, DS4_N_HEAD_DIM, 1, count, start, false, false) ||
|
||||
!ds4_gpu_dsv41_quantize(d->main_kv, DS4_N_HEAD_DIM, count, DS4_V41_FP8_E8M0)) return false;
|
||||
const uint32_t slot = start % DS41_DRAFT_WINDOW;
|
||||
const uint32_t part = count < DS41_DRAFT_WINDOW - slot ? count : DS41_DRAFT_WINDOW - slot;
|
||||
if (!ds4_gpu_tensor_copy(d->window[stage], slot * kv_bytes, d->main_kv, 0, part * kv_bytes) ||
|
||||
(part < count && !ds4_gpu_tensor_copy(d->window[stage], 0, d->main_kv,
|
||||
part * kv_bytes, (count - part) * kv_bytes))) return false;
|
||||
}
|
||||
d->kv_end = pos;
|
||||
d->kv_valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ds41_dspark_attention_output(ds41_prefill_row *b, const ds4_model *m,
|
||||
const ds4_layer_weights *l) {
|
||||
const uint32_t groups = DS4_N_OUT_GROUP, width = DS4_N_HEAD * DS4_N_HEAD_DIM / groups;
|
||||
uint64_t weight_row;
|
||||
if (!tensor_nbytes(l->attn_output_a->type, width, &weight_row)) return false;
|
||||
for (uint32_t row = 0; row < DS41_DRAFT_ROWS; row++) {
|
||||
for (uint32_t group = 0; group < groups; group++) {
|
||||
ds4_tensor slice = *l->attn_output_a;
|
||||
slice.abs_offset += group * DS4_N_LORA_O * weight_row;
|
||||
slice.dim[1] = DS4_N_LORA_O;
|
||||
ds4_gpu_tensor *x = ds4_gpu_tensor_view(b->heads,
|
||||
((uint64_t)row * groups + group) * width * 4u, width * 4u);
|
||||
ds4_gpu_tensor *y = ds4_gpu_tensor_view(b->low,
|
||||
((uint64_t)row * groups + group) * DS4_N_LORA_O * 4u, DS4_N_LORA_O * 4u);
|
||||
const bool ok = x && y && ds41_matmul(y, m, &slice, x, true);
|
||||
ds4_gpu_tensor_free(y); ds4_gpu_tensor_free(x);
|
||||
if (!ok) return false;
|
||||
}
|
||||
}
|
||||
return ds41_dspark_matmul_batch(b->block, m, l->attn_output_b, b->low, DS41_DRAFT_ROWS, true);
|
||||
}
|
||||
|
||||
static bool ds41_dspark_stage(ds41_dspark *d, const ds4_model *m, uint32_t stage, uint32_t pos) {
|
||||
ds41_prefill_row *b = &d->batch;
|
||||
const ds4_layer_weights *l = &d->weights->stage[stage].block;
|
||||
const uint32_t n = DS41_DRAFT_ROWS, past = pos < DS41_DRAFT_WINDOW ? pos : DS41_DRAFT_WINDOW;
|
||||
if (!ds41_hc_mix_batch(b, m, l, false, n) ||
|
||||
!(stage ? ds4_gpu_hc_weighted_sum_split_tensor(b->x, b->residual, b->ffn_split, DS4_N_EMBD, DS4_N_HC) :
|
||||
ds4_gpu_hc_weighted_sum_tensor(b->x, b->residual, b->pre, DS4_N_EMBD, DS4_N_HC)) ||
|
||||
!ds4_gpu_dsv41_quantize(b->x, DS4_N_EMBD, n, DS4_V41_BF16) ||
|
||||
!ds41_norm_batch(b->norm, b->x, m, l->attn_norm, n) ||
|
||||
!ds41_dspark_matmul_batch(b->qr, m, l->attn_q_a, b->norm, n, true) ||
|
||||
!ds41_norm_batch(b->qr, b->qr, m, l->attn_q_a_norm, n) ||
|
||||
!ds41_dspark_matmul_batch(b->q, m, l->attn_q_b, b->qr, n, true) ||
|
||||
!ds4_gpu_dsv41_rope(b->q, DS4_N_HEAD_DIM, DS4_N_HEAD, n, pos, false, false) ||
|
||||
!ds41_dspark_matmul_batch(b->kv, m, l->attn_kv, b->norm, n, true) ||
|
||||
!ds41_norm_batch(b->kv, b->kv, m, l->attn_kv_a_norm, n) ||
|
||||
!ds4_gpu_dsv41_rope(b->kv, DS4_N_HEAD_DIM, 1, n, pos, false, false) ||
|
||||
!ds4_gpu_dsv41_quantize(b->kv, DS4_N_HEAD_DIM, n, DS4_V41_FP8_E8M0) ||
|
||||
!ds4_gpu_tensor_copy(d->window[stage], (uint64_t)past * DS4_N_HEAD_DIM * 4u,
|
||||
b->kv, 0, (uint64_t)n * DS4_N_HEAD_DIM * 4u) ||
|
||||
!ds4_gpu_attention_noncausal_raw_batch_heads_tensor(b->heads, m->map, m->size,
|
||||
l->attn_sinks->abs_offset, b->q, d->window[stage], n, past + n,
|
||||
DS41_DRAFT_WINDOW + n, 0, DS4_N_HEAD, DS4_N_HEAD_DIM) ||
|
||||
!ds4_gpu_dsv41_quantize(b->heads, DS4_N_HEAD * DS4_N_HEAD_DIM, n, DS4_V41_BF16) ||
|
||||
!ds4_gpu_dsv41_rope(b->heads, DS4_N_HEAD_DIM, DS4_N_HEAD, n, pos, false, true) ||
|
||||
!ds41_dspark_attention_output(b, m, l) ||
|
||||
!ds41_after_attention_batch(b, m, l, n, false)) {
|
||||
fprintf(stderr, "ds4: V4.1 draft attention failed\n");
|
||||
return false;
|
||||
}
|
||||
const uint32_t experts = d->weights->n_experts, active = d->weights->n_experts_used;
|
||||
const uint64_t gate_row = routed_expert_row_bytes(l->ffn_gate_exps);
|
||||
const uint64_t down_row = routed_expert_row_bytes(l->ffn_down_exps);
|
||||
bool mid_f16 = false;
|
||||
if (!(ds41_dspark_matmul_batch(b->route_logits, m, l->ffn_gate_inp, b->norm, n, false) &&
|
||||
ds4_gpu_router_select_batch_tensor(b->selected, b->route_weights, b->route_probs,
|
||||
m->map, m->size, l->ffn_exp_probs_b->abs_offset, 0, 0, 0, 0, true, false,
|
||||
b->route_logits, d->tokens, experts, active, DS4_EXPERT_WEIGHT_SCALE, n))) {
|
||||
fprintf(stderr, "ds4: V4.1 draft routing failed\n"); return false;
|
||||
}
|
||||
if (!(ds41_dspark_matmul_batch(b->shared_gate, m, l->ffn_gate_shexp, b->norm, n, true) &&
|
||||
ds41_dspark_matmul_batch(b->shared_up, m, l->ffn_up_shexp, b->norm, n, true) &&
|
||||
ds4_gpu_swiglu_tensor(b->shared_mid, b->shared_gate, b->shared_up,
|
||||
n * DS4_N_FF_EXP, DS4_SWIGLU_CLAMP_EXP, 1.0f) &&
|
||||
ds4_gpu_dsv41_quantize(b->shared_mid, DS4_N_FF_EXP, n, DS4_V41_BF16) &&
|
||||
ds41_dspark_matmul_batch(b->shared, m, l->ffn_down_shexp, b->shared_mid, n, true))) {
|
||||
fprintf(stderr, "ds4: V4.1 draft shared expert failed\n"); return false;
|
||||
}
|
||||
if (!ds4_gpu_routed_moe_batch_tensor(b->routed, b->gate, b->up, b->mid, b->experts,
|
||||
m->map, m->size, l->ffn_gate_exps->abs_offset, l->ffn_up_exps->abs_offset,
|
||||
l->ffn_down_exps->abs_offset, l->ffn_gate_exps->type, l->ffn_down_exps->type,
|
||||
gate_row * DS4_N_FF_EXP, gate_row, down_row * DS4_N_EMBD, down_row,
|
||||
DS4_N_EMBD, DS4_N_FF_EXP, DS4_N_EMBD, b->selected, b->route_weights,
|
||||
experts, active, DS4_SWIGLU_CLAMP_EXP, b->norm, stage, n, &mid_f16, true)) {
|
||||
fprintf(stderr, "ds4: V4.1 draft routed experts failed\n"); return false;
|
||||
}
|
||||
return ds4_gpu_add_tensor(b->block, b->routed, b->shared, n * DS4_N_EMBD) &&
|
||||
ds4_gpu_dsv41_quantize(b->block, DS4_N_EMBD, n, DS4_V41_BF16) &&
|
||||
ds4_gpu_hc_expand_split_tensor(b->residual, b->block, b->after_attn,
|
||||
b->ffn_split, DS4_N_EMBD, DS4_N_HC) &&
|
||||
ds4_gpu_dsv41_quantize(b->residual, DS4_N_HC * DS4_N_EMBD, n, DS4_V41_BF16);
|
||||
}
|
||||
|
||||
/* Leaves the five base-logit rows and unnormalized collapsed features on the
|
||||
* GPU. Markov sampling is sequential and performed after this stage chain. */
|
||||
static DS4_MAYBE_UNUSED bool ds41_dspark_forward(ds41_dspark *d, const ds4_model *target,
|
||||
const ds4_weights *tw, const ds4_model *draft,
|
||||
uint32_t pos, int token, ds41_gpu_graph *target_graph) {
|
||||
if (!d || !pos || token < 0 || (uint32_t)token >= DS4_N_VOCAB) return false;
|
||||
ds41_prefill_row *b = &d->batch;
|
||||
const bool local_experts = target_graph && target_graph->tp_world == 2;
|
||||
if (local_experts) ds4_gpu_tp_suspend_expert_sharding(1);
|
||||
bool ok = ds4_gpu_begin_commands() && ds41_dspark_seed(d, draft, pos);
|
||||
if (!ok) fprintf(stderr, "ds4: V4.1 DSpark seed failed\n");
|
||||
const float pre[DS41_DRAFT_ROWS][4] = {{1, 0, 0, 0}, {1, 0, 0, 0}, {1, 0, 0, 0}, {1, 0, 0, 0}, {1, 0, 0, 0}};
|
||||
if (ok) ok = ds4_gpu_tensor_write(b->pre, 0, pre, sizeof(pre));
|
||||
for (uint32_t row = 0; ok && row < DS41_DRAFT_ROWS; row++) {
|
||||
const uint64_t bytes = (uint64_t)DS4_N_EMBD * DS4_N_HC * sizeof(float);
|
||||
ds4_gpu_tensor *out = ds4_gpu_tensor_view(b->residual, row * bytes, bytes);
|
||||
ok = out && ds4_gpu_embed_token_hc_tensor(out, target->map, target->size,
|
||||
tw->token_embd->abs_offset, DS4_N_VOCAB, row ? d->weights->noise_token_id : (uint32_t)token,
|
||||
DS4_N_EMBD, DS4_N_HC);
|
||||
ds4_gpu_tensor_free(out);
|
||||
}
|
||||
for (uint32_t stage = 0; ok && stage < 3; stage++) {
|
||||
ok = ds41_dspark_stage(d, draft, stage, pos);
|
||||
if (!ok) fprintf(stderr, "ds4: V4.1 DSpark stage %u failed\n", stage);
|
||||
}
|
||||
if (ok) ok = ds4_gpu_hc_weighted_sum_split_tensor(b->x, b->residual,
|
||||
b->ffn_split, DS4_N_EMBD, DS4_N_HC) &&
|
||||
ds4_gpu_dsv41_quantize(b->x, DS4_N_EMBD, DS41_DRAFT_ROWS, DS4_V41_BF16) &&
|
||||
ds41_norm_batch(b->norm, b->x, draft, d->weights->stage[2].norm, DS41_DRAFT_ROWS);
|
||||
if (ok) ok = target_graph ?
|
||||
ds41_output_projection(target_graph, d->logits, target, tw, b->norm, DS41_DRAFT_ROWS) :
|
||||
ds41_matmul_batch(d->logits, target, tw->output, b->norm, DS41_DRAFT_ROWS, false);
|
||||
if (ds4_gpu_commands_active() && !ds4_gpu_end_commands()) ok = false;
|
||||
if (local_experts) ds4_gpu_tp_suspend_expert_sharding(0);
|
||||
if (!ok) d->kv_valid = false;
|
||||
return ok;
|
||||
}
|
||||
|
||||
static DS4_MAYBE_UNUSED bool ds41_dspark_markov(ds41_dspark *d, const ds4_model *m,
|
||||
uint32_t row, int previous, float *confidence) {
|
||||
if (!d || row >= DS41_DRAFT_ROWS || previous < 0 ||
|
||||
(uint32_t)previous >= DS4_N_VOCAB || !confidence) return false;
|
||||
const ds4_dspark_stage_weights *last = &d->weights->stage[2];
|
||||
float embedding[256];
|
||||
const uint32_t rank = d->weights->markov_rank;
|
||||
if (rank != 256 || !dspark_dense_row_to_f32(embedding, m, last->markov_w1, (uint32_t)previous))
|
||||
return false;
|
||||
const uint64_t bytes = (uint64_t)DS4_N_VOCAB * sizeof(float);
|
||||
ds4_gpu_tensor *logits = ds4_gpu_tensor_view(d->logits, row * bytes, bytes);
|
||||
bool ok = logits && ds4_gpu_begin_commands() &&
|
||||
ds4_gpu_tensor_write(d->markov_embed, 0, embedding, sizeof(embedding)) &&
|
||||
ds41_matmul(d->markov_logits, m, last->markov_w2, d->markov_embed, false) &&
|
||||
ds4_gpu_add_tensor(logits, logits, d->markov_logits, DS4_N_VOCAB) &&
|
||||
ds4_gpu_tensor_copy(d->confidence_input, 0, d->batch.x,
|
||||
(uint64_t)row * DS4_N_EMBD * 4u, (uint64_t)DS4_N_EMBD * 4u) &&
|
||||
ds4_gpu_tensor_copy(d->confidence_input, (uint64_t)DS4_N_EMBD * 4u,
|
||||
d->markov_embed, 0, rank * 4u) &&
|
||||
ds41_matmul(d->confidence, m, last->confidence_proj, d->confidence_input, false);
|
||||
if (ds4_gpu_commands_active() && !ds4_gpu_end_commands()) ok = false;
|
||||
if (ok) ok = ds4_gpu_tensor_read(logits, 0, d->host_logits + (uint64_t)row * DS4_N_VOCAB, bytes) &&
|
||||
ds4_gpu_tensor_read(d->confidence, 0, confidence, sizeof(*confidence));
|
||||
ds4_gpu_tensor_free(logits);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/* Cache appends beyond the accepted frontier are invisible. Only the six
|
||||
* overwritten raw-ring slots and pooling carries need rollback; no compressed
|
||||
* cache proportional to the context is copied. */
|
||||
static DS4_MAYBE_UNUSED bool ds41_dspark_verify(ds41_dspark *d, ds41_gpu_graph *g,
|
||||
const ds4_model *m, const ds4_weights *w, const int *tokens, uint32_t count) {
|
||||
if (!d || !g->valid || count < 2 || count > DS41_VERIFY_ROWS ||
|
||||
g->pos > g->ctx || count > g->ctx - g->pos || !ds41_tp_batch_enabled(g)) return false;
|
||||
/* Prepost the complete small-row exchange window, as in V4.0 verification.
|
||||
* Bulk-prefill handshakes at every attention/FFN gate are too expensive. */
|
||||
const bool tp_block = g->tp_world == 2;
|
||||
uint32_t gates = 2;
|
||||
if (tp_block && (!g->tp_batch_out || !g->tp_batch_in || !g_tp_block_ctx ||
|
||||
!ds4_tp_batch_block_begin_gates(g_tp_block_ctx, count, DS4_N_LAYER, &gates) ||
|
||||
gates != 2)) return false;
|
||||
const uint64_t kv = DS4_N_HEAD_DIM * 4u, feature = DS4_N_EMBD * 4u;
|
||||
ds41_gpu_graph *graphs[DS41_VERIFY_ROWS];
|
||||
bool ok = ds4_gpu_begin_commands();
|
||||
d->verify_start = g->pos;
|
||||
d->verify_count = 0;
|
||||
d->rewind_end = 0;
|
||||
for (uint32_t i = 0; ok && i <= DS41_VERIFY_ROWS; i++) {
|
||||
if (i >= count && i != DS41_VERIFY_ROWS) continue;
|
||||
const uint32_t slot = (g->pos + i) % DS41_DRAFT_WINDOW;
|
||||
for (uint32_t il = 0; ok && i < count && il < 40; il++)
|
||||
ok = ds4_gpu_tensor_copy(d->old_raw, (il * DS41_VERIFY_ROWS + i) * kv,
|
||||
g->window[il], slot * kv, kv);
|
||||
for (uint32_t stage = 0; ok && i < count && stage < 3; stage++) {
|
||||
d->old_positions[stage][i] = d->positions[stage][slot];
|
||||
ok = ds4_gpu_tensor_copy(d->old_features, (stage * DS41_VERIFY_ROWS + i) * feature,
|
||||
d->target[stage], slot * feature, feature);
|
||||
}
|
||||
ds41_gpu_graph *row = &d->verified[i];
|
||||
ds4_gpu_tensor *res = row->residual, *pre = row->pre, *logits = row->logits;
|
||||
ds4_gpu_tensor *half = row->tp_logits_half;
|
||||
ds4_gpu_tensor *pool_k[4], *pool_s[4];
|
||||
memcpy(pool_k, row->verify_previous_kv, sizeof(pool_k));
|
||||
memcpy(pool_s, row->verify_previous_score, sizeof(pool_s));
|
||||
*row = *g;
|
||||
row->residual = res ? res : ds4_gpu_tensor_view(d->verify_residual, i * DS4_N_HC * feature, DS4_N_HC * feature);
|
||||
row->pre = pre ? pre : ds4_gpu_tensor_view(d->verify_pre, i * DS4_N_HC * 4u, DS4_N_HC * 4u);
|
||||
row->logits = logits ? logits : ds4_gpu_tensor_view(d->verify_logits,
|
||||
(uint64_t)i * DS4_N_VOCAB * 4u, (uint64_t)DS4_N_VOCAB * 4u);
|
||||
row->tp_logits_half = NULL;
|
||||
if (g->tp_logits_half) {
|
||||
const uint64_t bytes = (uint64_t)DS4_N_VOCAB * 2u;
|
||||
row->tp_logits_half = half ? half : ds4_gpu_tensor_view(row->logits,
|
||||
g->tp_rank * bytes, bytes);
|
||||
ok = ok && row->tp_logits_half;
|
||||
}
|
||||
row->verify_prefix = true;
|
||||
for (uint32_t owner = 0; owner < 4; owner++) {
|
||||
const uint64_t off = (i * 8u + owner * 2u) * kv;
|
||||
row->verify_previous_kv[owner] = pool_k[owner] ? pool_k[owner] :
|
||||
ds4_gpu_tensor_view(d->verify_pool, off, kv);
|
||||
row->verify_previous_score[owner] = pool_s[owner] ? pool_s[owner] :
|
||||
ds4_gpu_tensor_view(d->verify_pool, off + kv, kv);
|
||||
ok = ok && row->verify_previous_kv[owner] && row->verify_previous_score[owner];
|
||||
}
|
||||
ok = ok && row->residual && row->pre && row->logits;
|
||||
if (i < count) graphs[i] = row;
|
||||
else {
|
||||
/* Keep the block start as well: exact sampling can change mode
|
||||
* at the first token, before any verified token is consumed. */
|
||||
ok = ok && ds4_gpu_tensor_copy(row->residual, 0, g->residual, 0, DS4_N_HC * feature) &&
|
||||
ds4_gpu_tensor_copy(row->pre, 0, g->pre, 0, DS4_N_HC * 4u) &&
|
||||
ds4_gpu_tensor_copy(row->logits, 0, g->logits, 0, (uint64_t)DS4_N_VOCAB * 4u);
|
||||
for (uint32_t owner = 0; ok && owner < 4; owner++)
|
||||
ok = ds4_gpu_tensor_copy(row->verify_previous_kv[owner], 0, g->previous_kv[owner], 0, kv) &&
|
||||
ds4_gpu_tensor_copy(row->verify_previous_score[owner], 0, g->previous_score[owner], 0, kv);
|
||||
}
|
||||
}
|
||||
if (ok) ok = ds41_graph_step_batch(graphs, tokens, (int)count, count, m, w);
|
||||
if (ds4_gpu_commands_active() && !ds4_gpu_end_commands()) ok = false;
|
||||
if (tp_block && (!ok || !ds4_tp_batch_block_end(g_tp_block_ctx))) {
|
||||
ds4_tp_mark_failed(g_tp_block_ctx);
|
||||
ok = false;
|
||||
}
|
||||
if (ok) ok = ds4_gpu_tensor_read(d->verify_logits, 0, d->host_verified,
|
||||
(uint64_t)count * DS4_N_VOCAB * 4u) &&
|
||||
ds4_gpu_tensor_read(d->verify_logits, (uint64_t)DS41_VERIFY_ROWS * DS4_N_VOCAB * 4u,
|
||||
d->host_verified + (uint64_t)DS41_VERIFY_ROWS * DS4_N_VOCAB, DS4_N_VOCAB * 4u);
|
||||
if (ok) d->verify_count = count;
|
||||
else g->valid = false;
|
||||
return ok;
|
||||
}
|
||||
|
||||
static DS4_MAYBE_UNUSED bool ds41_dspark_commit(ds41_dspark *d, ds41_gpu_graph *g,
|
||||
uint32_t keep, float *logits) {
|
||||
if (!d || !d->verify_count || keep > d->verify_count ||
|
||||
(g->pos != d->verify_start && g->pos != d->rewind_end) ||
|
||||
(d->rewind_end && d->verify_start + keep > d->rewind_end)) return false;
|
||||
const uint64_t kv = DS4_N_HEAD_DIM * 4u, feature = DS4_N_EMBD * 4u;
|
||||
const uint32_t row = keep ? keep - 1u : DS41_VERIFY_ROWS;
|
||||
ds41_gpu_graph *last = &d->verified[row];
|
||||
bool ok = ds4_gpu_begin_commands();
|
||||
for (uint32_t i = keep; ok && i < d->verify_count; i++) {
|
||||
const uint32_t slot = (d->verify_start + i) % DS41_DRAFT_WINDOW;
|
||||
for (uint32_t il = 0; ok && il < 40; il++)
|
||||
ok = ds4_gpu_tensor_copy(g->window[il], slot * kv,
|
||||
d->old_raw, (il * DS41_VERIFY_ROWS + i) * kv, kv);
|
||||
for (uint32_t stage = 0; ok && stage < 3; stage++) {
|
||||
ok = ds4_gpu_tensor_copy(d->target[stage], slot * feature,
|
||||
d->old_features, (stage * DS41_VERIFY_ROWS + i) * feature, feature);
|
||||
d->positions[stage][slot] = d->old_positions[stage][i];
|
||||
}
|
||||
}
|
||||
for (uint32_t owner = 0; ok && owner < 4; owner++)
|
||||
ok = ds4_gpu_tensor_copy(g->previous_kv[owner], 0, last->verify_previous_kv[owner], 0, kv) &&
|
||||
ds4_gpu_tensor_copy(g->previous_score[owner], 0, last->verify_previous_score[owner], 0, kv);
|
||||
if (ok) ok = ds4_gpu_tensor_copy(g->residual, 0, last->residual, 0, DS4_N_HC * feature) &&
|
||||
ds4_gpu_tensor_copy(g->pre, 0, last->pre, 0, DS4_N_HC * 4u) &&
|
||||
ds4_gpu_tensor_copy(g->logits, 0, last->logits, 0, (uint64_t)DS4_N_VOCAB * 4u);
|
||||
if (ds4_gpu_commands_active() && !ds4_gpu_end_commands()) ok = false;
|
||||
if (ok) {
|
||||
g->pos = last->pos;
|
||||
g->history = last->history;
|
||||
memcpy(logits, d->host_verified + (uint64_t)row * DS4_N_VOCAB, DS4_N_VOCAB * 4u);
|
||||
} else g->valid = false;
|
||||
d->rewind_end = ok ? g->pos : 0;
|
||||
return ok;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,315 @@
|
||||
#ifndef DS4_DEEPSEEK41_GPU_H
|
||||
#define DS4_DEEPSEEK41_GPU_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef DS4_GPU_TENSOR_DEFINED
|
||||
#define DS4_GPU_TENSOR_DEFINED
|
||||
typedef struct ds4_gpu_tensor ds4_gpu_tensor;
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* V4.1 activation/cache formats. Buffers are float-addressable but the
|
||||
* rounded values follow the released BF16/FP8/FP4 inference graph. */
|
||||
typedef enum {
|
||||
DS4_V41_BF16 = 0,
|
||||
DS4_V41_FP8_E8M0 = 1,
|
||||
DS4_V41_FP4_E8M0 = 2,
|
||||
DS4_V41_FP4_E4M3 = 3,
|
||||
} ds4_v41_activation_format;
|
||||
/* One-token router: probabilities, biased top-k and normalised weights in one dispatch. */
|
||||
int ds4_gpu_dsv41_router_one(ds4_gpu_tensor *selected, ds4_gpu_tensor *weights,
|
||||
ds4_gpu_tensor *probs, const ds4_gpu_tensor *logits,
|
||||
const void *model_map, uint64_t model_size, uint64_t bias_offset,
|
||||
uint32_t n_expert, uint32_t top_k, float scale);
|
||||
/* The same for a few rows (Metal only), and the rows' F32 logits with the
|
||||
* single-row matvec's reduction. */
|
||||
int ds4_gpu_dsv41_router_rows(ds4_gpu_tensor *selected, ds4_gpu_tensor *weights,
|
||||
ds4_gpu_tensor *probs, const ds4_gpu_tensor *logits,
|
||||
const void *model_map, uint64_t model_size, uint64_t bias_offset,
|
||||
uint32_t n_expert, uint32_t top_k, float scale, uint32_t rows);
|
||||
int ds4_gpu_dsv41_matmul_f32_rows(ds4_gpu_tensor *out, const void *model_map, uint64_t model_size,
|
||||
uint64_t weight_offset, uint32_t in_dim, uint32_t out_dim,
|
||||
const ds4_gpu_tensor *x, uint32_t rows);
|
||||
int ds4_gpu_dsv41_quantize(ds4_gpu_tensor *x, uint32_t width, uint32_t rows,
|
||||
ds4_v41_activation_format format);
|
||||
#if !defined(__APPLE__) && !defined(DS4_ROCM_BUILD) && !defined(DS4_NO_GPU)
|
||||
/* Same reductions as the ordinary kernels, with the required BF16 store
|
||||
* folded into the operation. hc_sum reads either 4- or 24-float row weights. */
|
||||
int ds4_gpu_dsv41_norm(ds4_gpu_tensor *out, const ds4_gpu_tensor *x,
|
||||
const void *map, uint64_t size, uint64_t offset,
|
||||
uint32_t width, uint32_t rows, float eps);
|
||||
int ds4_gpu_dsv41_hc_sum(ds4_gpu_tensor *out, const ds4_gpu_tensor *x,
|
||||
const ds4_gpu_tensor *weights, uint32_t width, uint32_t rows,
|
||||
uint32_t weight_stride);
|
||||
int ds4_gpu_dsv41_hc_expand(ds4_gpu_tensor *out, const ds4_gpu_tensor *block,
|
||||
const ds4_gpu_tensor *residual, const ds4_gpu_tensor *split,
|
||||
uint32_t width, uint32_t rows);
|
||||
/* CUDA scalar Q8 shared expert. 1: queued; 0: unsupported, no work queued;
|
||||
* -1: failure. After 1, keep the input/output tensors alive and unchanged
|
||||
* until join, which orders the result before subsequent main-stream work.
|
||||
* Other work may run between start and join using separate tensors.
|
||||
* Optional ownership picks the rank with fewer selected routed experts.
|
||||
* IDs are read on the main stream before later owned-expert filtering. */
|
||||
typedef struct {
|
||||
const ds4_gpu_tensor *selected;
|
||||
uint32_t experts, active, rank, tie_owner;
|
||||
} ds4_v41_shared_owner;
|
||||
int ds4_gpu_dsv41_shared_start(
|
||||
ds4_gpu_tensor *out, ds4_gpu_tensor *gate, ds4_gpu_tensor *up,
|
||||
ds4_gpu_tensor *mid, const ds4_gpu_tensor *x,
|
||||
const void *model_map, uint64_t model_size,
|
||||
uint64_t gate_offset, uint64_t up_offset, uint64_t down_offset,
|
||||
uint32_t width, uint32_t hidden, float clamp,
|
||||
const ds4_v41_shared_owner *owner);
|
||||
int ds4_gpu_dsv41_shared_join(void);
|
||||
/* Mask a computed short batch before the owned router filter mutates IDs. */
|
||||
int ds4_gpu_dsv41_shared_mask(ds4_gpu_tensor *shared, const ds4_gpu_tensor *selected,
|
||||
uint32_t width, uint32_t rows, uint32_t experts, uint32_t active,
|
||||
uint32_t rank, uint32_t tie_owner);
|
||||
#endif
|
||||
/* Full-head prefill, with BF16 rounding between the two Q8 projections. */
|
||||
int ds4_gpu_dsv41_attention_output_batch(
|
||||
ds4_gpu_tensor *out, ds4_gpu_tensor *low,
|
||||
const void *model_map, uint64_t model_size,
|
||||
uint64_t out_a_offset, uint64_t out_b_offset,
|
||||
const ds4_gpu_tensor *heads, uint32_t n_tokens);
|
||||
/* Packed local 32-head input and BF16 low projection; output is an unrounded
|
||||
* rank partial. The graph sums ranks before rounding the attention block. */
|
||||
int ds4_gpu_dsv41_attention_output_tp_batch(
|
||||
ds4_gpu_tensor *out, ds4_gpu_tensor *low,
|
||||
const void *model_map, uint64_t model_size,
|
||||
uint64_t out_a_offset, uint64_t out_b_offset,
|
||||
const ds4_gpu_tensor *heads, uint32_t n_tokens, uint32_t tp_rank);
|
||||
/* Adjacent-pair, unit-magnitude RoPE with the released V4.1 frequencies. */
|
||||
int ds4_gpu_dsv41_rope(ds4_gpu_tensor *x, uint32_t width, uint32_t heads,
|
||||
uint32_t rows, uint32_t start, bool compressed, bool inverse);
|
||||
/* Compressed pairs advance two absolute token positions per stored row. */
|
||||
int ds4_gpu_dsv41_rope_bf16(ds4_gpu_tensor *x, uint32_t width, uint32_t heads,
|
||||
uint32_t rows, uint32_t start, bool compressed, bool inverse);
|
||||
int ds4_gpu_dsv41_rope_stride(ds4_gpu_tensor *x, uint32_t width, uint32_t heads,
|
||||
uint32_t rows, uint32_t start, uint32_t stride,
|
||||
bool compressed, bool inverse);
|
||||
int ds4_gpu_dsv41_engram_add(ds4_gpu_tensor *residual,
|
||||
const ds4_gpu_tensor *kv,
|
||||
const ds4_gpu_tensor *q_weight,
|
||||
const ds4_gpu_tensor *k_weight,
|
||||
const ds4_gpu_tensor *mask,
|
||||
uint32_t width, uint32_t rows, float eps);
|
||||
/* Pool complete pairs and retain the last unpaired projection in previous_*.
|
||||
* start is the absolute token position, including earlier chunks. */
|
||||
int ds4_gpu_dsv41_pool2(ds4_gpu_tensor *out,
|
||||
const ds4_gpu_tensor *kv, const ds4_gpu_tensor *scores,
|
||||
ds4_gpu_tensor *previous_kv, ds4_gpu_tensor *previous_scores,
|
||||
uint32_t width, uint32_t rows, uint32_t start);
|
||||
/* Candidate blocks contain eight compressed positions. Produce causal block
|
||||
* maxima, pinning the newest block; filter consumes a 0/-inf block mask. */
|
||||
int ds4_gpu_dsv41_candidate_blocks(ds4_gpu_tensor *blocks,
|
||||
const ds4_gpu_tensor *scores,
|
||||
uint32_t width, uint32_t rows,
|
||||
uint32_t start, uint32_t ratio);
|
||||
int ds4_gpu_dsv41_candidate_filter(ds4_gpu_tensor *scores,
|
||||
const ds4_gpu_tensor *block_mask,
|
||||
uint32_t width, uint32_t rows,
|
||||
uint32_t start, uint32_t ratio,
|
||||
uint32_t mask_stride);
|
||||
/* Per-row block top-2048 with the causal block count of ratio-1 rows, and
|
||||
* the 0/-inf block masks it selects at mask_stride floats per row. */
|
||||
int ds4_gpu_dsv41_candidate_topk_batch(ds4_gpu_tensor *selected,
|
||||
const ds4_gpu_tensor *blocks,
|
||||
uint32_t width, uint32_t rows, uint32_t start);
|
||||
int ds4_gpu_dsv41_candidate_mask_batch(ds4_gpu_tensor *mask,
|
||||
const ds4_gpu_tensor *selected,
|
||||
uint32_t width, uint32_t rows,
|
||||
uint32_t mask_stride);
|
||||
/* Causal index scores over ratio-1/2 compressed keys, without an extra cast
|
||||
* of the already quantized FP4 queries/keys. Scores have source_rows stride. */
|
||||
int ds4_gpu_dsv41_indexer_scores_batch(ds4_gpu_tensor *scores,
|
||||
const ds4_gpu_tensor *q,
|
||||
const ds4_gpu_tensor *weights,
|
||||
const ds4_gpu_tensor *keys,
|
||||
uint32_t source_rows, uint32_t rows,
|
||||
uint32_t start, uint32_t ratio);
|
||||
#ifndef __APPLE__
|
||||
int ds4_gpu_dsv41_indexer_scores_masked_one(ds4_gpu_tensor *scores,
|
||||
const ds4_gpu_tensor *q, const ds4_gpu_tensor *weights, const ds4_gpu_tensor *keys,
|
||||
const ds4_gpu_tensor *block_mask, uint32_t width);
|
||||
#endif
|
||||
/* One-token index scores of FP4 queries/keys: the GLM scorer, plus its wide
|
||||
* kernel for long rows (Metal only). */
|
||||
int ds4_gpu_dsv41_indexer_score_one_tensor(ds4_gpu_tensor *scores,
|
||||
const ds4_gpu_tensor *q,
|
||||
const ds4_gpu_tensor *weights,
|
||||
const ds4_gpu_tensor *indexer_key_cache,
|
||||
uint32_t n_rows, uint32_t n_head,
|
||||
uint32_t head_dim, float scale,
|
||||
bool cache_f16);
|
||||
/* Rows [first, first + rows) of the index-key cache into its bf16 view; with flag, a key that is not bf16-exact
|
||||
* sets it nonzero. score_row: one decode index row (32 heads x 128, scale 1/64) over the cache, reading view, when
|
||||
* given, where the wide scorer runs (the same bits). */
|
||||
int ds4_gpu_dsv41_index_keys_bf16(ds4_gpu_tensor *view, const ds4_gpu_tensor *cache,
|
||||
uint32_t first, uint32_t rows, ds4_gpu_tensor *flag);
|
||||
int ds4_gpu_dsv41_indexer_score_row(ds4_gpu_tensor *scores, const ds4_gpu_tensor *q, const ds4_gpu_tensor *weights,
|
||||
const ds4_gpu_tensor *cache, const ds4_gpu_tensor *view, uint32_t rows);
|
||||
/* The top-k of a row filtered by a block mask of more than 2048 blocks, from
|
||||
* the scores of its 2048 candidate blocks only: the same ids in the same order
|
||||
* (Metal only). Returns 0, leaving the full row to the caller, where unavailable. */
|
||||
int ds4_gpu_dsv41_indexer_candidates_tensor(ds4_gpu_tensor *selected, ds4_gpu_tensor *scores,
|
||||
ds4_gpu_tensor *list, const ds4_gpu_tensor *q,
|
||||
const ds4_gpu_tensor *weights, const ds4_gpu_tensor *keys,
|
||||
const ds4_gpu_tensor *block_mask, uint32_t n_comp,
|
||||
float scale, uint32_t top_k, int list_ready);
|
||||
/* list_ready != 0: list already holds block_mask's candidates (built by an earlier call since the mask was written),
|
||||
* so it is not rebuilt. */
|
||||
int ds4_gpu_dsv41_tensor_ops_available(void);
|
||||
/* Reuse exact BF16 views of FP4 queries/keys across score tiles. Invalid
|
||||
* casts retain the F32 arithmetic for the affected tile. */
|
||||
uint64_t ds4_gpu_dsv41_indexer_packed_bytes(uint32_t source_rows, uint32_t rows);
|
||||
int ds4_gpu_dsv41_indexer_pack(ds4_gpu_tensor *packed,
|
||||
const ds4_gpu_tensor *q, const ds4_gpu_tensor *keys,
|
||||
uint32_t source_rows, uint32_t rows);
|
||||
int ds4_gpu_dsv41_indexer_scores_packed(ds4_gpu_tensor *scores,
|
||||
const ds4_gpu_tensor *q,
|
||||
const ds4_gpu_tensor *weights,
|
||||
const ds4_gpu_tensor *keys,
|
||||
const ds4_gpu_tensor *packed,
|
||||
uint32_t source_rows, uint32_t rows,
|
||||
uint32_t start, uint32_t ratio,
|
||||
uint32_t packed_rows, uint32_t offset);
|
||||
/* Exact row-sort ordering with independent causal widths; more than 512
|
||||
* visible keys per row. Output stride is 512 indices. */
|
||||
int ds4_gpu_dsv41_indexer_topk_batch(ds4_gpu_tensor *selected,
|
||||
const ds4_gpu_tensor *scores,
|
||||
uint32_t width, uint32_t rows,
|
||||
uint32_t start, uint32_t ratio);
|
||||
/* The indexer top-k, plus the radix select for wide rows (Metal); other models
|
||||
* keep the merge ladder and its tie order. */
|
||||
int ds4_gpu_dsv41_indexer_topk_tensor(ds4_gpu_tensor *selected,
|
||||
const ds4_gpu_tensor *scores,
|
||||
uint32_t n_comp, uint32_t n_tokens,
|
||||
uint32_t top_k);
|
||||
/* Rows with at most 512 visible keys select all of them, in key order. */
|
||||
int ds4_gpu_dsv41_indexer_all_batch(ds4_gpu_tensor *selected, uint32_t rows,
|
||||
uint32_t start, uint32_t ratio);
|
||||
enum { DS4_V41_CARRY_BF16, DS4_V41_CARRY_MASK, DS4_V41_CARRY_F32 };
|
||||
/* Lossless storage for already-BF16 activations or 0/-inf candidate masks.
|
||||
* Packed rows are padded to whole uint32_t words. Plain rows remain F32. */
|
||||
int ds4_gpu_dsv41_carry_copy(ds4_gpu_tensor *packed, uint32_t row_offset,
|
||||
ds4_gpu_tensor *plain, uint32_t width, uint32_t rows,
|
||||
uint32_t format, bool pack);
|
||||
/* Batched F16 projections with the same arithmetic as individual matvecs. */
|
||||
int ds4_gpu_dsv41_projection_rows(ds4_gpu_tensor *out,
|
||||
const void *model_map, uint64_t model_size,
|
||||
uint64_t weight_offset, uint32_t width,
|
||||
uint32_t outputs, uint32_t rows,
|
||||
const ds4_gpu_tensor *in);
|
||||
/* The HC mixer projection of one row with its norm fused (0: not fused). */
|
||||
int ds4_gpu_dsv41_hc_project(ds4_gpu_tensor *mix, const ds4_gpu_tensor *residual,
|
||||
const void *model_map, uint64_t model_size, uint64_t fn_offset,
|
||||
uint32_t n, uint32_t mix_dim, float eps);
|
||||
/* One block's whole HC input in one dispatch: the mixer projection with its
|
||||
* norm, then split, weighted sum with `pre`'s first n_hc weights and norm
|
||||
* (0: not fused). */
|
||||
/* The same for a few rows: `pre` advances pre_stride floats per row. */
|
||||
int ds4_gpu_dsv41_hc_block_input_rows(ds4_gpu_tensor *mix, ds4_gpu_tensor *x, ds4_gpu_tensor *norm,
|
||||
ds4_gpu_tensor *split, const ds4_gpu_tensor *stream,
|
||||
const ds4_gpu_tensor *pre, const void *model_map, uint64_t model_size,
|
||||
uint64_t fn_offset, uint64_t scale_offset, uint64_t base_offset,
|
||||
uint64_t norm_offset, uint32_t n, uint32_t mix_dim, uint32_t n_embd,
|
||||
uint32_t n_hc, uint32_t sinkhorn_iters, float hc_eps, float norm_eps,
|
||||
uint32_t rows, uint32_t pre_stride);
|
||||
/* One block's input: split, weighted sum with `pre`'s first n_hc weights, norm. */
|
||||
int ds4_gpu_dsv41_hc_input(ds4_gpu_tensor *x, ds4_gpu_tensor *norm, ds4_gpu_tensor *split,
|
||||
const ds4_gpu_tensor *mix, const ds4_gpu_tensor *residual,
|
||||
const ds4_gpu_tensor *pre, const void *model_map, uint64_t model_size,
|
||||
uint64_t scale_offset, uint64_t base_offset, uint64_t norm_offset,
|
||||
uint32_t n_embd, uint32_t n_hc, uint32_t sinkhorn_iters,
|
||||
float hc_eps, float norm_eps);
|
||||
/* Two Q8_0 projections of one row, outputs rounded to bf16 (0: run them apart). */
|
||||
int ds4_gpu_dsv41_project_pair_q8(ds4_gpu_tensor *out_a, ds4_gpu_tensor *out_b,
|
||||
const void *model_map, uint64_t model_size,
|
||||
uint64_t offset_a, uint64_t offset_b, uint32_t in_dim,
|
||||
uint32_t out_a_dim, uint32_t out_b_dim, const ds4_gpu_tensor *x);
|
||||
/* The shared expert's SwiGLU input from the Q8_0 gate and up weights, as the
|
||||
* bf16 matvecs and the SwiGLU pass would produce it; gate/up are scratch. */
|
||||
int ds4_gpu_dsv41_shared_swiglu(ds4_gpu_tensor *mid, ds4_gpu_tensor *gate, ds4_gpu_tensor *up,
|
||||
const void *model_map, uint64_t model_size, uint64_t gate_offset,
|
||||
uint64_t up_offset, uint32_t in_dim, uint32_t out_dim,
|
||||
const ds4_gpu_tensor *x, float clamp, uint32_t rows);
|
||||
/* Rope, quantize and store one row into dst at dst_offset bytes; x may be
|
||||
* left roped and quantized in place. */
|
||||
int ds4_gpu_dsv41_rope_quantize(const ds4_gpu_tensor *x, ds4_gpu_tensor *dst, uint64_t dst_offset,
|
||||
uint32_t width, uint32_t start, bool compressed,
|
||||
ds4_v41_activation_format format);
|
||||
/* One row's F16 projection rounded to bf16 in the kernel (0: not fused). */
|
||||
int ds4_gpu_dsv41_project_f16_bf16(ds4_gpu_tensor *out, const void *model_map, uint64_t model_size,
|
||||
uint64_t weight_offset, uint32_t in_dim, uint32_t out_dim,
|
||||
const ds4_gpu_tensor *x);
|
||||
/* Decode attention over the raw ring and the compressed rows selected by
|
||||
* ids, staged in one pass (0: not available; `selected` is gather scratch). */
|
||||
int ds4_gpu_dsv41_attention_decode(ds4_gpu_tensor *heads, const void *model_map, uint64_t model_size,
|
||||
uint64_t sinks_offset, const ds4_gpu_tensor *q,
|
||||
const ds4_gpu_tensor *raw_kv, uint32_t n_raw, uint32_t raw_cap,
|
||||
uint32_t raw_start, const ds4_gpu_tensor *comp_cache,
|
||||
const ds4_gpu_tensor *ids, ds4_gpu_tensor *selected,
|
||||
uint32_t n_comp, uint32_t attended, uint32_t n_head, uint32_t head_dim);
|
||||
/* Single-Mac decode: the block input's collapse and norm now, then its hyper-connection weights inside the query
|
||||
* projection's dispatch, which also ropes and stores the layer's KV row. 0 when DS4_GPU_V41_DEFERRED_HC is off. */
|
||||
int ds4_gpu_dsv41_deferred_collapse(ds4_gpu_tensor *x, ds4_gpu_tensor *norm,
|
||||
const ds4_gpu_tensor *residual, const ds4_gpu_tensor *pre,
|
||||
const void *model_map, uint64_t model_size, uint64_t norm_offset, float eps);
|
||||
int ds4_gpu_dsv41_query_deferred_hc(ds4_gpu_tensor *q, ds4_gpu_tensor *mix, ds4_gpu_tensor *split,
|
||||
const ds4_gpu_tensor *qr, const ds4_gpu_tensor *residual,
|
||||
const void *model_map, uint64_t model_size, uint64_t q_offset, uint64_t fn_offset,
|
||||
uint64_t scale_offset, uint64_t base_offset, uint32_t pos, bool compressed,
|
||||
uint32_t iters, float hc_eps, float norm_eps, ds4_gpu_tensor *x, ds4_gpu_tensor *norm,
|
||||
const ds4_gpu_tensor *pre, uint64_t norm_offset,
|
||||
const ds4_gpu_tensor *kv, ds4_gpu_tensor *cache, uint64_t cache_offset);
|
||||
/* The Q8_0 q projection of one row with the layer's rope on its outputs. */
|
||||
int ds4_gpu_dsv41_project_q(ds4_gpu_tensor *out, const void *model_map, uint64_t model_size,
|
||||
uint64_t weight_offset, uint32_t in_dim, uint32_t out_dim,
|
||||
const ds4_gpu_tensor *x, uint32_t pos, bool compressed);
|
||||
/* A bf16 Q8_0 matvec of one row expanded straight into the n_hc residual
|
||||
* streams; `add` (optional) joins the row before the expand. */
|
||||
int ds4_gpu_dsv41_matmul_expand_rows(ds4_gpu_tensor *out_hc, const void *model_map, uint64_t model_size,
|
||||
uint64_t weight_offset, uint32_t in_dim, uint32_t out_dim,
|
||||
const ds4_gpu_tensor *x, const ds4_gpu_tensor *add,
|
||||
const ds4_gpu_tensor *residual_hc, const ds4_gpu_tensor *split,
|
||||
uint32_t n_hc, uint32_t rows);
|
||||
/* The attention output low projection of one row with the heads' inverse
|
||||
* rope (and bf16 rounding) folded into the load; heads may be roped in place. */
|
||||
int ds4_gpu_dsv41_attention_low(ds4_gpu_tensor *low, const void *model_map, uint64_t model_size,
|
||||
uint64_t out_a_offset, uint32_t group_dim, uint32_t rank,
|
||||
uint32_t n_groups, ds4_gpu_tensor *heads, uint32_t pos, bool compressed);
|
||||
/* Metal: one decode step's RoPE coefficient table (64 float2: (cos, sin) of float(pos) * frequency, entries 0..31
|
||||
* raw, 32..63 compressed). rope_quantize_heads_tab: rope + quantization of heads rows of width in place, in one
|
||||
* dispatch reading it. heads_once_arm: the next single-row decode reduce of 64 heads x 512 writes its heads already
|
||||
* bf16-rounded and inverse-roped with the table; take returns 1 when it did (and disarms), and attention_low_plain
|
||||
* then projects them. reduce_check (test entry): that reduce on tmp's partials, plain or transformed. */
|
||||
int ds4_gpu_dsv41_rope_coeff_table(ds4_gpu_tensor *tab, uint32_t pos);
|
||||
int ds4_gpu_dsv41_rope_quantize_heads_tab(ds4_gpu_tensor *x, uint32_t width, uint32_t heads, uint32_t start,
|
||||
bool compressed, ds4_v41_activation_format format, const ds4_gpu_tensor *tab);
|
||||
void ds4_gpu_dsv41_heads_once_arm(const ds4_gpu_tensor *tab, bool compressed);
|
||||
int ds4_gpu_dsv41_heads_once_take(void);
|
||||
int ds4_gpu_dsv41_attention_low_plain(ds4_gpu_tensor *low, const void *model_map, uint64_t model_size,
|
||||
uint64_t out_a_offset, uint32_t group_dim, uint32_t rank,
|
||||
uint32_t n_groups, const ds4_gpu_tensor *heads);
|
||||
int ds4_gpu_dsv41_flash_attn_reduce_check(ds4_gpu_tensor *heads, const ds4_gpu_tensor *tmp, uint32_t nrows,
|
||||
int transform, const ds4_gpu_tensor *tab, bool compressed);
|
||||
/* Two weighted RMS norms rounded to bf16 in one dispatch. */
|
||||
int ds4_gpu_dsv41_norm_pair_rows(ds4_gpu_tensor *out0, const ds4_gpu_tensor *x0, uint64_t weight0_offset, uint32_t n0,
|
||||
ds4_gpu_tensor *out1, const ds4_gpu_tensor *x1, uint64_t weight1_offset, uint32_t n1,
|
||||
const void *model_map, uint64_t model_size, float eps, uint32_t rows);
|
||||
/* Gather 512-wide F32 KV rows; IDs must come from top-k over source_rows. */
|
||||
int ds4_gpu_dsv41_gather_kv(ds4_gpu_tensor *out, const ds4_gpu_tensor *source,
|
||||
const ds4_gpu_tensor *ids, uint32_t source_rows,
|
||||
uint32_t selected_rows);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,444 @@
|
||||
/* DeepSeek V4 Flash Vision-Exp operations shared by CUDA and ROCm. */
|
||||
|
||||
#ifndef DS4_DEEPSEEK4_VISION_TYPES_DEFINED
|
||||
#define DS4_DEEPSEEK4_VISION_TYPES_DEFINED
|
||||
#define DS4_DEEPSEEK4_VISION_LAYERS 32u
|
||||
#define DS4_DEEPSEEK4_LANGUAGE_LAYERS 43u
|
||||
#define DS4_DEEPSEEK4_MTP_LAYERS 3u
|
||||
|
||||
typedef struct {
|
||||
uint64_t norm1;
|
||||
uint64_t qkv_weight;
|
||||
uint64_t qkv_bias;
|
||||
uint64_t attn_proj_weight;
|
||||
uint64_t attn_proj_bias;
|
||||
uint64_t norm2;
|
||||
uint64_t mlp_w1;
|
||||
uint64_t mlp_w2;
|
||||
} ds4_deepseek4_vision_layer_weights;
|
||||
|
||||
typedef struct {
|
||||
uint64_t patch_weight;
|
||||
uint64_t patch_bias;
|
||||
uint64_t post_norm;
|
||||
uint64_t aligner_w1;
|
||||
uint64_t aligner_w1_bias;
|
||||
uint64_t aligner_w2;
|
||||
uint64_t aligner_w2_bias;
|
||||
uint64_t image_start;
|
||||
uint64_t image_pad;
|
||||
uint64_t image_newline;
|
||||
uint64_t image_end;
|
||||
uint64_t visual_router_bias[DS4_DEEPSEEK4_LANGUAGE_LAYERS];
|
||||
uint64_t mtp_visual_router_bias[DS4_DEEPSEEK4_MTP_LAYERS];
|
||||
uint64_t hash_router_bias[3];
|
||||
ds4_deepseek4_vision_layer_weights layer[DS4_DEEPSEEK4_VISION_LAYERS];
|
||||
} ds4_deepseek4_vision_weights;
|
||||
#endif
|
||||
|
||||
#ifndef DS4_DEEPSEEK4_VISION_STREAM
|
||||
#define DS4_DEEPSEEK4_VISION_STREAM 0
|
||||
#endif
|
||||
|
||||
__device__ __forceinline__ static float deepseek4_vision_round_bf16_dev(
|
||||
float value) {
|
||||
uint32_t bits = __float_as_uint(value);
|
||||
if ((bits & 0x7f800000u) == 0x7f800000u) return value;
|
||||
bits += 0x00007fffu + ((bits >> 16u) & 1u);
|
||||
return __uint_as_float(bits & 0xffff0000u);
|
||||
}
|
||||
|
||||
__global__ static void deepseek4_vision_round_bf16_kernel(
|
||||
float *x,
|
||||
uint64_t count) {
|
||||
const uint64_t i = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < count) x[i] = deepseek4_vision_round_bf16_dev(x[i]);
|
||||
}
|
||||
|
||||
__global__ static void deepseek4_vision_qkv_rope_kernel(
|
||||
float *q,
|
||||
float *k,
|
||||
float *v,
|
||||
const float *qkv,
|
||||
const uint16_t *bias,
|
||||
uint32_t rows,
|
||||
uint32_t grid_w) {
|
||||
const uint32_t row = blockIdx.x;
|
||||
const uint32_t head = blockIdx.y;
|
||||
const uint32_t lane = threadIdx.x;
|
||||
if (row >= rows || head >= 16u || lane >= 32u) return;
|
||||
const uint64_t qkv_base = (uint64_t)row * 3072u +
|
||||
(uint64_t)head * 64u;
|
||||
const uint64_t out_base = (uint64_t)row * 1024u +
|
||||
(uint64_t)head * 64u;
|
||||
const float q0 = deepseek4_vision_round_bf16_dev(
|
||||
qkv[qkv_base + lane] +
|
||||
glm53_vision_bf16(bias + (uint64_t)head * 64u + lane));
|
||||
const float q1 = deepseek4_vision_round_bf16_dev(
|
||||
qkv[qkv_base + lane + 32u] +
|
||||
glm53_vision_bf16(bias + (uint64_t)head * 64u + lane + 32u));
|
||||
const float k0 = deepseek4_vision_round_bf16_dev(
|
||||
qkv[qkv_base + 1024u + lane] +
|
||||
glm53_vision_bf16(bias + 1024u +
|
||||
(uint64_t)head * 64u + lane));
|
||||
const float k1 = deepseek4_vision_round_bf16_dev(
|
||||
qkv[qkv_base + 1024u + lane + 32u] +
|
||||
glm53_vision_bf16(bias + 1024u +
|
||||
(uint64_t)head * 64u + lane + 32u));
|
||||
const uint32_t y = row / grid_w;
|
||||
const uint32_t x = row - y * grid_w;
|
||||
const uint32_t pos = lane < 16u ? y : x;
|
||||
const uint32_t freq = lane & 15u;
|
||||
const float inv_freq = powf(10000.0f, -(float)freq / 16.0f);
|
||||
const float angle = (float)pos * inv_freq;
|
||||
const float cs = cosf(angle);
|
||||
const float sn = sinf(angle);
|
||||
q[out_base + lane] = deepseek4_vision_round_bf16_dev(q0 * cs - q1 * sn);
|
||||
q[out_base + lane + 32u] =
|
||||
deepseek4_vision_round_bf16_dev(q1 * cs + q0 * sn);
|
||||
k[out_base + lane] = deepseek4_vision_round_bf16_dev(k0 * cs - k1 * sn);
|
||||
k[out_base + lane + 32u] =
|
||||
deepseek4_vision_round_bf16_dev(k1 * cs + k0 * sn);
|
||||
v[out_base + lane] = deepseek4_vision_round_bf16_dev(
|
||||
qkv[qkv_base + 2048u + lane] +
|
||||
glm53_vision_bf16(bias + 2048u +
|
||||
(uint64_t)head * 64u + lane));
|
||||
v[out_base + lane + 32u] = deepseek4_vision_round_bf16_dev(
|
||||
qkv[qkv_base + 2048u + lane + 32u] +
|
||||
glm53_vision_bf16(bias + 2048u +
|
||||
(uint64_t)head * 64u + lane + 32u));
|
||||
}
|
||||
|
||||
__global__ static void deepseek4_vision_swiglu_split_kernel(
|
||||
float *out,
|
||||
const float *gate_up,
|
||||
uint64_t count,
|
||||
uint32_t width) {
|
||||
const uint64_t i = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i >= count) return;
|
||||
const uint32_t d = i % width;
|
||||
const uint64_t row = i / width;
|
||||
const uint64_t source = row * width * 2u + d;
|
||||
const float gate = gate_up[source];
|
||||
const float up = gate_up[source + width];
|
||||
out[i] = deepseek4_vision_round_bf16_dev(
|
||||
(gate / (1.0f + expf(-gate))) * up);
|
||||
}
|
||||
|
||||
__global__ static void deepseek4_vision_add_residual_kernel(
|
||||
float *x,
|
||||
const float *residual,
|
||||
uint64_t count) {
|
||||
const uint64_t i = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < count)
|
||||
x[i] = deepseek4_vision_round_bf16_dev(x[i] + residual[i]);
|
||||
}
|
||||
|
||||
__global__ static void deepseek4_vision_aligner_reorder_kernel(
|
||||
float *out,
|
||||
const float *x,
|
||||
uint32_t grid_h,
|
||||
uint32_t grid_w,
|
||||
uint32_t output_rows) {
|
||||
const uint32_t d = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
const uint32_t row = blockIdx.y;
|
||||
if (d >= 9216u || row >= output_rows) return;
|
||||
const uint32_t output_w = (grid_w + 2u) / 3u;
|
||||
const uint32_t block_y = row / output_w;
|
||||
const uint32_t block_x = row - block_y * output_w;
|
||||
const uint32_t channel = d / 9u;
|
||||
const uint32_t within = d - channel * 9u;
|
||||
const uint32_t source_y = block_y * 3u + within / 3u;
|
||||
const uint32_t source_x = block_x * 3u + within % 3u;
|
||||
float value = 0.0f;
|
||||
if (source_y < grid_h && source_x < grid_w) {
|
||||
const uint64_t source_row = (uint64_t)source_y * grid_w + source_x;
|
||||
value = x[source_row * 1024u + channel];
|
||||
}
|
||||
out[(uint64_t)row * 9216u + d] = value;
|
||||
}
|
||||
|
||||
__global__ static void deepseek4_vision_gelu_bias_kernel(
|
||||
float *out,
|
||||
const float *x,
|
||||
const uint16_t *bias,
|
||||
uint64_t count,
|
||||
uint32_t width) {
|
||||
const uint64_t i = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i >= count) return;
|
||||
const uint32_t d = i % width;
|
||||
const float value = deepseek4_vision_round_bf16_dev(
|
||||
x[i] + glm53_vision_bf16(bias + d));
|
||||
out[i] = deepseek4_vision_round_bf16_dev(
|
||||
0.5f * value *
|
||||
(1.0f + glm53_vision_erf_approx(
|
||||
value * 0.7071067811865475f)));
|
||||
}
|
||||
|
||||
static int deepseek4_vision_round_tensor(
|
||||
ds4_gpu_tensor *x,
|
||||
uint64_t count,
|
||||
const char *label) {
|
||||
deepseek4_vision_round_bf16_kernel<<<
|
||||
(unsigned)((count + 255u) / 256u), 256u, 0,
|
||||
DS4_DEEPSEEK4_VISION_STREAM>>>((float *)x->ptr, count);
|
||||
return glm53_vision_launch_ok(label);
|
||||
}
|
||||
|
||||
extern "C" int ds4_gpu_deepseek4_vision_encode(
|
||||
float *out,
|
||||
const float *patches,
|
||||
uint32_t grid_h,
|
||||
uint32_t grid_w,
|
||||
const void *model_map,
|
||||
uint64_t model_size,
|
||||
const ds4_deepseek4_vision_weights *weights) {
|
||||
if (!out || !patches || !model_map || !weights || grid_h == 0u ||
|
||||
grid_w == 0u || grid_h > UINT32_MAX / grid_w) return 0;
|
||||
const uint32_t rows = grid_h * grid_w;
|
||||
const uint32_t aligned_rows =
|
||||
((grid_h + 2u) / 3u) * ((grid_w + 2u) / 3u);
|
||||
const uint64_t row1024 = (uint64_t)rows * 1024u;
|
||||
const uint64_t row2816 = (uint64_t)rows * 2816u;
|
||||
const uint64_t row3072 = (uint64_t)rows * 3072u;
|
||||
const uint64_t row5632 = (uint64_t)rows * 5632u;
|
||||
const uint64_t aligned4096 = (uint64_t)aligned_rows * 4096u;
|
||||
const uint64_t aligned9216 = (uint64_t)aligned_rows * 9216u;
|
||||
if (row5632 > SIZE_MAX / sizeof(float) ||
|
||||
aligned9216 > SIZE_MAX / sizeof(float)) return 0;
|
||||
|
||||
ds4_gpu_tensor *patch = NULL, *a = NULL, *b = NULL, *qkv = NULL;
|
||||
ds4_gpu_tensor *q = NULL, *k = NULL, *v = NULL, *attn = NULL;
|
||||
ds4_gpu_tensor *mlp_w1 = NULL, *mlp_mid = NULL;
|
||||
ds4_gpu_tensor *align_in = NULL, *align_a = NULL, *align_b = NULL;
|
||||
ds4_gpu_tensor *cur = NULL, *tmp = NULL;
|
||||
const uint16_t *bias = NULL;
|
||||
int ok = 0;
|
||||
#define DSV4_VISION_ALLOC(name_, count_) do { \
|
||||
name_ = ds4_gpu_tensor_alloc((count_) * sizeof(float)); \
|
||||
if (!(name_)) goto cleanup; \
|
||||
} while (0)
|
||||
DSV4_VISION_ALLOC(patch, (uint64_t)rows * 588u);
|
||||
DSV4_VISION_ALLOC(a, row1024);
|
||||
DSV4_VISION_ALLOC(b, row1024);
|
||||
DSV4_VISION_ALLOC(qkv, row3072);
|
||||
DSV4_VISION_ALLOC(q, row1024);
|
||||
DSV4_VISION_ALLOC(k, row1024);
|
||||
DSV4_VISION_ALLOC(v, row1024);
|
||||
DSV4_VISION_ALLOC(attn, row1024);
|
||||
DSV4_VISION_ALLOC(mlp_w1, row5632);
|
||||
DSV4_VISION_ALLOC(mlp_mid, row2816);
|
||||
DSV4_VISION_ALLOC(align_in, aligned9216);
|
||||
DSV4_VISION_ALLOC(align_a, aligned4096);
|
||||
DSV4_VISION_ALLOC(align_b, aligned4096);
|
||||
#undef DSV4_VISION_ALLOC
|
||||
if (!ds4_gpu_tensor_write(
|
||||
patch, 0, patches,
|
||||
(uint64_t)rows * 588u * sizeof(float)) ||
|
||||
!ds4_gpu_begin_commands()) goto cleanup;
|
||||
ok = ds4_gpu_glm53_matmul_bf16(
|
||||
a, model_map, model_size, weights->patch_weight,
|
||||
588u, 1024u, patch, rows);
|
||||
if (ok) {
|
||||
bias = glm53_vision_weight(model_map, model_size,
|
||||
weights->patch_bias, 1024u, "DeepSeek vision patch bias");
|
||||
if (!bias) ok = 0;
|
||||
}
|
||||
if (ok) {
|
||||
glm53_vision_bias_kernel<<<
|
||||
(unsigned)((row1024 + 255u) / 256u), 256u, 0,
|
||||
DS4_DEEPSEEK4_VISION_STREAM>>>(
|
||||
(float *)a->ptr, bias, NULL, row1024, 1024u);
|
||||
ok = glm53_vision_launch_ok("DeepSeek vision patch bias");
|
||||
}
|
||||
if (ok) ok = deepseek4_vision_round_tensor(
|
||||
a, row1024, "DeepSeek vision patch round");
|
||||
|
||||
cur = a;
|
||||
tmp = b;
|
||||
for (uint32_t il = 0; ok && il < DS4_DEEPSEEK4_VISION_LAYERS; il++) {
|
||||
const ds4_deepseek4_vision_layer_weights *w = &weights->layer[il];
|
||||
const uint16_t *norm = glm53_vision_weight(
|
||||
model_map, model_size, w->norm1, 1024u,
|
||||
"DeepSeek vision norm1");
|
||||
if (!norm) { ok = 0; break; }
|
||||
glm53_vision_rms_kernel<<<rows, 256u, 0,
|
||||
DS4_DEEPSEEK4_VISION_STREAM>>>(
|
||||
(float *)tmp->ptr, (const float *)cur->ptr,
|
||||
norm, 1024u, 1.0e-6f);
|
||||
ok = glm53_vision_launch_ok("DeepSeek vision norm1");
|
||||
if (ok) ok = deepseek4_vision_round_tensor(
|
||||
tmp, row1024, "DeepSeek vision norm1 round");
|
||||
if (ok) ok = ds4_gpu_glm53_matmul_bf16(
|
||||
qkv, model_map, model_size, w->qkv_weight,
|
||||
1024u, 3072u, tmp, rows);
|
||||
const uint16_t *qkv_bias = NULL;
|
||||
if (ok) {
|
||||
qkv_bias = glm53_vision_weight(
|
||||
model_map, model_size, w->qkv_bias, 3072u,
|
||||
"DeepSeek vision QKV bias");
|
||||
if (!qkv_bias) ok = 0;
|
||||
}
|
||||
if (ok) {
|
||||
deepseek4_vision_qkv_rope_kernel<<<
|
||||
dim3(rows, 16u, 1u), 32u, 0,
|
||||
DS4_DEEPSEEK4_VISION_STREAM>>>(
|
||||
(float *)q->ptr, (float *)k->ptr, (float *)v->ptr,
|
||||
(const float *)qkv->ptr, qkv_bias, rows, grid_w);
|
||||
ok = glm53_vision_launch_ok("DeepSeek vision QKV");
|
||||
}
|
||||
if (ok) {
|
||||
glm53_vision_attention_kernel<<<
|
||||
dim3(rows, 16u, 1u), 32u, 0,
|
||||
DS4_DEEPSEEK4_VISION_STREAM>>>(
|
||||
(float *)attn->ptr, (const float *)q->ptr,
|
||||
(const float *)k->ptr, (const float *)v->ptr, rows);
|
||||
ok = glm53_vision_launch_ok("DeepSeek vision attention");
|
||||
}
|
||||
if (ok) ok = deepseek4_vision_round_tensor(
|
||||
attn, row1024, "DeepSeek vision attention round");
|
||||
if (ok) ok = ds4_gpu_glm53_matmul_bf16(
|
||||
tmp, model_map, model_size, w->attn_proj_weight,
|
||||
1024u, 1024u, attn, rows);
|
||||
if (ok) {
|
||||
bias = glm53_vision_weight(
|
||||
model_map, model_size, w->attn_proj_bias, 1024u,
|
||||
"DeepSeek vision attention bias");
|
||||
if (!bias) ok = 0;
|
||||
}
|
||||
if (ok) {
|
||||
glm53_vision_bias_kernel<<<
|
||||
(unsigned)((row1024 + 255u) / 256u), 256u, 0,
|
||||
DS4_DEEPSEEK4_VISION_STREAM>>>(
|
||||
(float *)tmp->ptr, bias, (const float *)cur->ptr,
|
||||
row1024, 1024u);
|
||||
ok = glm53_vision_launch_ok(
|
||||
"DeepSeek vision attention residual");
|
||||
}
|
||||
if (ok) ok = deepseek4_vision_round_tensor(
|
||||
tmp, row1024, "DeepSeek vision attention residual round");
|
||||
ds4_gpu_tensor *swap = cur; cur = tmp; tmp = swap;
|
||||
|
||||
if (ok) {
|
||||
norm = glm53_vision_weight(
|
||||
model_map, model_size, w->norm2, 1024u,
|
||||
"DeepSeek vision norm2");
|
||||
if (!norm) ok = 0;
|
||||
}
|
||||
if (ok) {
|
||||
glm53_vision_rms_kernel<<<rows, 256u, 0,
|
||||
DS4_DEEPSEEK4_VISION_STREAM>>>(
|
||||
(float *)tmp->ptr, (const float *)cur->ptr,
|
||||
norm, 1024u, 1.0e-6f);
|
||||
ok = glm53_vision_launch_ok("DeepSeek vision norm2");
|
||||
}
|
||||
if (ok) ok = deepseek4_vision_round_tensor(
|
||||
tmp, row1024, "DeepSeek vision norm2 round");
|
||||
if (ok) ok = ds4_gpu_glm53_matmul_bf16(
|
||||
mlp_w1, model_map, model_size, w->mlp_w1,
|
||||
1024u, 5632u, tmp, rows);
|
||||
if (ok) ok = deepseek4_vision_round_tensor(
|
||||
mlp_w1, row5632, "DeepSeek vision MLP input round");
|
||||
if (ok) {
|
||||
deepseek4_vision_swiglu_split_kernel<<<
|
||||
(unsigned)((row2816 + 255u) / 256u), 256u, 0,
|
||||
DS4_DEEPSEEK4_VISION_STREAM>>>(
|
||||
(float *)mlp_mid->ptr, (const float *)mlp_w1->ptr,
|
||||
row2816, 2816u);
|
||||
ok = glm53_vision_launch_ok("DeepSeek vision SwiGLU");
|
||||
}
|
||||
if (ok) ok = ds4_gpu_glm53_matmul_bf16(
|
||||
tmp, model_map, model_size, w->mlp_w2,
|
||||
2816u, 1024u, mlp_mid, rows);
|
||||
if (ok) ok = deepseek4_vision_round_tensor(
|
||||
tmp, row1024, "DeepSeek vision MLP output round");
|
||||
if (ok) {
|
||||
deepseek4_vision_add_residual_kernel<<<
|
||||
(unsigned)((row1024 + 255u) / 256u), 256u, 0,
|
||||
DS4_DEEPSEEK4_VISION_STREAM>>>(
|
||||
(float *)tmp->ptr, (const float *)cur->ptr, row1024);
|
||||
ok = glm53_vision_launch_ok("DeepSeek vision MLP residual");
|
||||
}
|
||||
swap = cur; cur = tmp; tmp = swap;
|
||||
}
|
||||
if (ok) {
|
||||
const uint16_t *norm = glm53_vision_weight(
|
||||
model_map, model_size, weights->post_norm, 1024u,
|
||||
"DeepSeek vision final norm");
|
||||
if (!norm) ok = 0;
|
||||
else {
|
||||
glm53_vision_rms_kernel<<<rows, 256u, 0,
|
||||
DS4_DEEPSEEK4_VISION_STREAM>>>(
|
||||
(float *)tmp->ptr, (const float *)cur->ptr,
|
||||
norm, 1024u, 1.0e-6f);
|
||||
ok = glm53_vision_launch_ok("DeepSeek vision final norm");
|
||||
}
|
||||
}
|
||||
if (ok) ok = deepseek4_vision_round_tensor(
|
||||
tmp, row1024, "DeepSeek vision final norm round");
|
||||
if (ok) {
|
||||
dim3 grid((9216u + 255u) / 256u, aligned_rows, 1u);
|
||||
deepseek4_vision_aligner_reorder_kernel<<<
|
||||
grid, 256u, 0, DS4_DEEPSEEK4_VISION_STREAM>>>(
|
||||
(float *)align_in->ptr, (const float *)tmp->ptr,
|
||||
grid_h, grid_w, aligned_rows);
|
||||
ok = glm53_vision_launch_ok("DeepSeek vision aligner reorder");
|
||||
}
|
||||
if (ok) ok = ds4_gpu_glm53_matmul_bf16(
|
||||
align_a, model_map, model_size, weights->aligner_w1,
|
||||
9216u, 4096u, align_in, aligned_rows);
|
||||
if (ok) {
|
||||
bias = glm53_vision_weight(
|
||||
model_map, model_size, weights->aligner_w1_bias, 4096u,
|
||||
"DeepSeek vision aligner hidden bias");
|
||||
if (!bias) ok = 0;
|
||||
}
|
||||
if (ok) {
|
||||
deepseek4_vision_gelu_bias_kernel<<<
|
||||
(unsigned)((aligned4096 + 255u) / 256u), 256u, 0,
|
||||
DS4_DEEPSEEK4_VISION_STREAM>>>(
|
||||
(float *)align_b->ptr, (const float *)align_a->ptr,
|
||||
bias, aligned4096, 4096u);
|
||||
ok = glm53_vision_launch_ok("DeepSeek vision aligner GELU");
|
||||
}
|
||||
if (ok) ok = ds4_gpu_glm53_matmul_bf16(
|
||||
align_a, model_map, model_size, weights->aligner_w2,
|
||||
4096u, 4096u, align_b, aligned_rows);
|
||||
if (ok) {
|
||||
bias = glm53_vision_weight(
|
||||
model_map, model_size, weights->aligner_w2_bias, 4096u,
|
||||
"DeepSeek vision aligner output bias");
|
||||
if (!bias) ok = 0;
|
||||
}
|
||||
if (ok) {
|
||||
glm53_vision_bias_kernel<<<
|
||||
(unsigned)((aligned4096 + 255u) / 256u), 256u, 0,
|
||||
DS4_DEEPSEEK4_VISION_STREAM>>>(
|
||||
(float *)align_a->ptr, bias, NULL, aligned4096, 4096u);
|
||||
ok = glm53_vision_launch_ok(
|
||||
"DeepSeek vision aligner output bias");
|
||||
}
|
||||
if (ok) ok = deepseek4_vision_round_tensor(
|
||||
align_a, aligned4096, "DeepSeek vision aligner output round");
|
||||
if (ds4_gpu_end_commands() == 0) ok = 0;
|
||||
if (ok) ok = ds4_gpu_tensor_read(
|
||||
align_a, 0, out, aligned4096 * sizeof(float));
|
||||
|
||||
cleanup:
|
||||
ds4_gpu_tensor_free(align_b);
|
||||
ds4_gpu_tensor_free(align_a);
|
||||
ds4_gpu_tensor_free(align_in);
|
||||
ds4_gpu_tensor_free(mlp_mid);
|
||||
ds4_gpu_tensor_free(mlp_w1);
|
||||
ds4_gpu_tensor_free(attn);
|
||||
ds4_gpu_tensor_free(v);
|
||||
ds4_gpu_tensor_free(k);
|
||||
ds4_gpu_tensor_free(q);
|
||||
ds4_gpu_tensor_free(qkv);
|
||||
ds4_gpu_tensor_free(b);
|
||||
ds4_gpu_tensor_free(a);
|
||||
ds4_gpu_tensor_free(patch);
|
||||
return ok;
|
||||
}
|
||||
|
||||
#undef DS4_DEEPSEEK4_VISION_STREAM
|
||||
+680
@@ -0,0 +1,680 @@
|
||||
#define _DARWIN_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
|
||||
#include "ds4_engram.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdatomic.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#ifdef __APPLE__
|
||||
#include <dispatch/dispatch.h>
|
||||
#include <pthread/qos.h>
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
bool ds4_engram_layout_valid(const ds4_engram_layout *l) {
|
||||
if (!l || !l->token_map || !l->vocab_size ||
|
||||
!l->compressed_vocab_size || l->compressed_vocab_size > INT32_MAX ||
|
||||
l->pad_id >= l->compressed_vocab_size) return false;
|
||||
for (uint32_t i = 0; i < l->vocab_size; i++)
|
||||
if (l->token_map[i] >= l->compressed_vocab_size) return false;
|
||||
for (int layer = 0; layer < DS4_ENGRAM_LAYERS; layer++) {
|
||||
for (int i = 0; i < DS4_ENGRAM_NGRAM; i++) {
|
||||
uint64_t m = l->multipliers[layer][i];
|
||||
if (!(m & 1) || m > (uint64_t)INT64_MAX / l->compressed_vocab_size)
|
||||
return false;
|
||||
}
|
||||
uint64_t total = 0;
|
||||
for (int i = 0; i < DS4_ENGRAM_COLS; i++) {
|
||||
if (l->primes[layer][i] < 2) return false;
|
||||
total += l->primes[layer][i];
|
||||
}
|
||||
if (total != l->rows[layer]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ds4_engram_history_reset(ds4_engram_history *h) {
|
||||
for (int i = 0; i < DS4_ENGRAM_NGRAM - 1; i++) h->tail[i] = DS4_ENGRAM_DEAD;
|
||||
}
|
||||
|
||||
bool ds4_engram_hash(const ds4_engram_layout *l, ds4_engram_history *h,
|
||||
const int *tokens, const uint8_t *mask, size_t count,
|
||||
uint32_t *rows) {
|
||||
if (!l || !h || !l->token_map || (count && (!tokens || !rows)) ||
|
||||
count > SIZE_MAX / (DS4_ENGRAM_LAYERS * DS4_ENGRAM_COLS * sizeof(*rows)))
|
||||
return false;
|
||||
for (int i = 0; i < DS4_ENGRAM_NGRAM - 1; i++) {
|
||||
if (h->tail[i] < DS4_ENGRAM_DEAD ||
|
||||
(h->tail[i] >= 0 && (uint32_t)h->tail[i] >= l->compressed_vocab_size))
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (tokens[i] < 0 || (uint32_t)tokens[i] >= l->vocab_size) return false;
|
||||
}
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
int32_t current = mask && !mask[i] ? DS4_ENGRAM_DEAD :
|
||||
(int32_t)l->token_map[tokens[i]];
|
||||
uint32_t ids[DS4_ENGRAM_NGRAM];
|
||||
bool blocked = false;
|
||||
for (int j = 0; j < DS4_ENGRAM_NGRAM; j++) {
|
||||
int32_t id = j ? h->tail[j - 1] : current;
|
||||
blocked |= id == DS4_ENGRAM_DEAD;
|
||||
ids[j] = blocked ? l->pad_id : (uint32_t)id;
|
||||
}
|
||||
for (int layer = 0; layer < DS4_ENGRAM_LAYERS; layer++) {
|
||||
uint64_t hash = (uint64_t)ids[0] * l->multipliers[layer][0];
|
||||
uint32_t offset = 0;
|
||||
for (int j = 1; j < DS4_ENGRAM_NGRAM; j++) {
|
||||
hash ^= (uint64_t)ids[j] * l->multipliers[layer][j];
|
||||
for (int head = 0; head < DS4_ENGRAM_HEADS; head++) {
|
||||
int col = (j - 1) * DS4_ENGRAM_HEADS + head;
|
||||
uint32_t prime = l->primes[layer][col];
|
||||
*rows++ = (uint32_t)(hash % prime) + offset;
|
||||
offset += prime;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int j = DS4_ENGRAM_NGRAM - 2; j > 0; j--) h->tail[j] = h->tail[j - 1];
|
||||
h->tail[0] = current;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ds4_engram_table_open(ds4_engram_table *t, const char *path,
|
||||
uint64_t offset, uint32_t rows) {
|
||||
if (!t) return false;
|
||||
*t = (ds4_engram_table){.fd = -1};
|
||||
uint64_t bytes = (uint64_t)rows * DS4_ENGRAM_ROW_BYTES;
|
||||
if (!path || !rows || offset > INT64_MAX || bytes > INT64_MAX - offset) {
|
||||
errno = EINVAL;
|
||||
return false;
|
||||
}
|
||||
int fd = open(path, O_RDONLY | O_CLOEXEC);
|
||||
if (fd < 0) return false;
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) != 0) goto fail;
|
||||
if (!S_ISREG(st.st_mode) || st.st_size < 0 || offset + bytes > (uint64_t)st.st_size) {
|
||||
errno = EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
#ifdef __APPLE__
|
||||
if (fcntl(fd, F_NOCACHE, 1) != 0 || fcntl(fd, F_RDAHEAD, 0) != 0) goto fail;
|
||||
#elif defined(POSIX_FADV_RANDOM)
|
||||
/* Hashed rows are scattered across the table. Read-ahead fetches unused
|
||||
* pages and competes with the model's limited memory headroom. */
|
||||
(void)posix_fadvise(fd, (off_t)offset, (off_t)bytes, POSIX_FADV_RANDOM);
|
||||
#endif
|
||||
*t = (ds4_engram_table){.fd = fd, .offset = offset, .rows = rows};
|
||||
return true;
|
||||
fail: {
|
||||
int saved = errno;
|
||||
close(fd);
|
||||
errno = saved;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ds4_engram_table_close(ds4_engram_table *t) {
|
||||
if (!t) return;
|
||||
if (t->fd >= 0) close(t->fd);
|
||||
*t = (ds4_engram_table){.fd = -1};
|
||||
}
|
||||
|
||||
static bool read_row(int fd, uint64_t offset, uint8_t row[DS4_ENGRAM_ROW_BYTES]) {
|
||||
size_t done = 0;
|
||||
while (done < DS4_ENGRAM_ROW_BYTES) {
|
||||
ssize_t n = pread(fd, row + done, DS4_ENGRAM_ROW_BYTES - done,
|
||||
(off_t)(offset + done));
|
||||
if (n < 0 && errno == EINTR) continue;
|
||||
if (n <= 0) {
|
||||
if (n == 0) errno = EIO;
|
||||
return false;
|
||||
}
|
||||
done += (size_t)n;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static float e4m3(uint8_t byte) {
|
||||
int exponent = (byte >> 3) & 15, mantissa = byte & 7;
|
||||
float value = exponent ? ldexpf((float)(8 + mantissa), exponent - 10) :
|
||||
ldexpf((float)mantissa, -9);
|
||||
return byte & 128 ? -value : value;
|
||||
}
|
||||
|
||||
/* All code/scale pairs fit in 128 KiB; this is a format lookup, not a cache
|
||||
* of model rows. Preserve the reference's F32 scaling and BF16 rounding. */
|
||||
static uint16_t engram_bf16[256][256];
|
||||
static pthread_once_t engram_bf16_once = PTHREAD_ONCE_INIT;
|
||||
|
||||
static void init_engram_bf16(void) {
|
||||
for (unsigned scale = 0; scale < 256; scale++) {
|
||||
for (unsigned code = 0; code < 256; code++) {
|
||||
uint16_t result = UINT16_MAX;
|
||||
if ((code & 127u) != 127u && scale != 255u) {
|
||||
float value = ldexpf(e4m3((uint8_t)code), (int)scale - 127);
|
||||
uint32_t bits;
|
||||
memcpy(&bits, &value, sizeof(bits));
|
||||
bits = (bits + 0x7fffu + ((bits >> 16) & 1u)) & 0xffff0000u;
|
||||
if ((bits & 0x7f800000u) != 0x7f800000u) result = (uint16_t)(bits >> 16);
|
||||
}
|
||||
engram_bf16[scale][code] = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ds4_engram_read(const ds4_engram_table *t, const uint32_t *rows,
|
||||
size_t count, float *out) {
|
||||
if (!t || t->fd < 0 || (count && (!rows || !out)) ||
|
||||
count > SIZE_MAX / (DS4_ENGRAM_DIM * sizeof(*out))) {
|
||||
errno = EINVAL;
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (rows[i] >= t->rows) {
|
||||
errno = EINVAL;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
pthread_once(&engram_bf16_once, init_engram_bf16);
|
||||
uint8_t raw[DS4_ENGRAM_ROW_BYTES];
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (!read_row(t->fd, t->offset + (uint64_t)rows[i] * sizeof(raw), raw)) return false;
|
||||
for (int j = 0; j < DS4_ENGRAM_DIM; j++) {
|
||||
const uint16_t value = engram_bf16[raw[DS4_ENGRAM_DIM + j / 32]][raw[j]];
|
||||
if (value == UINT16_MAX) {
|
||||
errno = EDOM;
|
||||
return false;
|
||||
}
|
||||
const uint32_t bits = (uint32_t)value << 16;
|
||||
memcpy(out + i * DS4_ENGRAM_DIM + j, &bits, sizeof(bits));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint32_t row, output;
|
||||
} engram_request;
|
||||
|
||||
static int request_order(const void *a, const void *b) {
|
||||
const engram_request *x = a, *y = b;
|
||||
return (x->row > y->row) - (x->row < y->row);
|
||||
}
|
||||
|
||||
enum { ENGRAM_READERS = 16 };
|
||||
#ifdef __APPLE__
|
||||
enum { ENGRAM_PARALLEL_MIN_ROWS = 8 };
|
||||
#else
|
||||
/* Unlike dispatch's shared pool, this path creates threads for each batch. */
|
||||
enum { ENGRAM_PARALLEL_MIN_ROWS = 256 };
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
const ds4_engram_table *table;
|
||||
const engram_request *request;
|
||||
float *out;
|
||||
size_t count, readers;
|
||||
int error[ENGRAM_READERS];
|
||||
} engram_batch;
|
||||
|
||||
static void read_batch_part(void *context, size_t part) {
|
||||
engram_batch *batch = context;
|
||||
const engram_request *request = batch->request;
|
||||
const size_t begin = batch->count * part / batch->readers;
|
||||
const size_t end = batch->count * (part + 1) / batch->readers;
|
||||
const float *previous = NULL;
|
||||
for (size_t i = begin; i < end; i++) {
|
||||
float *dst = batch->out + (size_t)request[i].output * DS4_ENGRAM_DIM;
|
||||
if (i > begin && request[i].row == request[i - 1].row) {
|
||||
memcpy(dst, previous, DS4_ENGRAM_DIM * sizeof(*dst));
|
||||
} else {
|
||||
if (!ds4_engram_read(batch->table, &request[i].row, 1, dst)) {
|
||||
batch->error[part] = errno ? errno : EIO;
|
||||
return;
|
||||
}
|
||||
previous = dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef __APPLE__
|
||||
typedef struct {
|
||||
engram_batch *batch;
|
||||
size_t part;
|
||||
} engram_reader;
|
||||
|
||||
static void *read_batch_thread(void *context) {
|
||||
engram_reader *reader = context;
|
||||
read_batch_part(reader->batch, reader->part);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool ds4_engram_read_batch(const ds4_engram_table *t, const uint32_t *rows,
|
||||
size_t tokens, size_t stride, float *out) {
|
||||
if (!t || t->fd < 0 || (tokens && (!rows || !out || stride < DS4_ENGRAM_COLS)) ||
|
||||
tokens > SIZE_MAX / (DS4_ENGRAM_COLS * DS4_ENGRAM_DIM * sizeof(*out)) ||
|
||||
(tokens && tokens - 1 > (SIZE_MAX / sizeof(*rows) - DS4_ENGRAM_COLS) / stride)) {
|
||||
errno = EINVAL;
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < tokens; i++) {
|
||||
for (size_t j = 0; j < DS4_ENGRAM_COLS; j++) {
|
||||
if (rows[i * stride + j] >= t->rows) {
|
||||
errno = EINVAL;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!tokens) return true;
|
||||
enum { BATCH_TOKENS = 2048 };
|
||||
const size_t cap = tokens < BATCH_TOKENS ? tokens : BATCH_TOKENS;
|
||||
engram_request *request = malloc(cap * DS4_ENGRAM_COLS * sizeof(*request));
|
||||
if (!request) return false;
|
||||
bool ok = true;
|
||||
for (size_t start = 0; ok && start < tokens; start += cap) {
|
||||
const size_t n = tokens - start < cap ? tokens - start : cap;
|
||||
const size_t count = n * DS4_ENGRAM_COLS;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
request[i] = (engram_request){
|
||||
rows[(start + i / DS4_ENGRAM_COLS) * stride + i % DS4_ENGRAM_COLS],
|
||||
(uint32_t)i
|
||||
};
|
||||
}
|
||||
qsort(request, count, sizeof(*request), request_order);
|
||||
engram_batch batch = {.table = t, .request = request, .count = count,
|
||||
.out = out + start * DS4_ENGRAM_COLS * DS4_ENGRAM_DIM, .readers = 1};
|
||||
/* Fixed concurrency hides random-read latency without caching the table.
|
||||
* Each worker owns disjoint output rows; all finish before GPU use. */
|
||||
if (count >= ENGRAM_PARALLEL_MIN_ROWS) {
|
||||
batch.readers = count < ENGRAM_READERS ? count : ENGRAM_READERS;
|
||||
#ifdef __APPLE__
|
||||
dispatch_apply_f(batch.readers,
|
||||
dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), &batch, read_batch_part);
|
||||
#else
|
||||
pthread_t threads[ENGRAM_READERS - 1];
|
||||
engram_reader readers[ENGRAM_READERS - 1];
|
||||
size_t started = 0;
|
||||
for (size_t part = 1; part < batch.readers; part++) {
|
||||
readers[started] = (engram_reader){&batch, part};
|
||||
if (pthread_create(&threads[started], NULL, read_batch_thread,
|
||||
&readers[started])) break;
|
||||
started++;
|
||||
}
|
||||
read_batch_part(&batch, 0);
|
||||
/* Thread exhaustion only reduces concurrency, not correctness. */
|
||||
for (size_t part = started + 1; part < batch.readers; part++)
|
||||
read_batch_part(&batch, part);
|
||||
for (size_t part = 0; part < started; part++)
|
||||
if (pthread_join(threads[part], NULL)) abort();
|
||||
#endif
|
||||
} else
|
||||
read_batch_part(&batch, 0);
|
||||
for (size_t i = 0; i < batch.readers; i++) {
|
||||
if (batch.error[i]) {
|
||||
errno = batch.error[i];
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
int saved = errno;
|
||||
free(request);
|
||||
errno = saved;
|
||||
return ok;
|
||||
}
|
||||
|
||||
struct ds4_engram_prefetch {
|
||||
const ds4_engram_table *table;
|
||||
const uint32_t *rows;
|
||||
size_t tokens, stride;
|
||||
float *out;
|
||||
bool ok;
|
||||
int error;
|
||||
#ifdef __APPLE__
|
||||
dispatch_group_t group;
|
||||
#else
|
||||
pthread_t thread;
|
||||
bool threaded;
|
||||
#endif
|
||||
};
|
||||
|
||||
static void prefetch_run(void *context) {
|
||||
ds4_engram_prefetch *p = context;
|
||||
p->ok = ds4_engram_read_batch(p->table, p->rows, p->tokens, p->stride, p->out);
|
||||
if (!p->ok) p->error = errno ? errno : EIO;
|
||||
}
|
||||
|
||||
#ifndef __APPLE__
|
||||
static void *prefetch_thread(void *context) {
|
||||
prefetch_run(context);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
ds4_engram_prefetch *ds4_engram_read_batch_start(const ds4_engram_table *table, const uint32_t *rows,
|
||||
size_t tokens, size_t stride, float *out) {
|
||||
ds4_engram_prefetch *p = calloc(1, sizeof(*p));
|
||||
if (!p) return NULL;
|
||||
*p = (ds4_engram_prefetch){.table = table, .rows = rows, .tokens = tokens, .stride = stride, .out = out};
|
||||
#ifdef __APPLE__
|
||||
p->group = dispatch_group_create();
|
||||
dispatch_group_async_f(p->group, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), p, prefetch_run);
|
||||
#else
|
||||
p->threaded = pthread_create(&p->thread, NULL, prefetch_thread, p) == 0;
|
||||
if (!p->threaded) prefetch_run(p);
|
||||
#endif
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Page-cache warmer. Sequential reads fill the kernel's cache with the table
|
||||
* once; the uncached row readers then hit it instead of the device. Spread
|
||||
* prefill rows are the worst access pattern for external SSDs: a USB drive
|
||||
* served ~6K random rows/s over the 189 GiB span, ~12 s per 2K prefill, but
|
||||
* streams the whole span at ~1 GB/s. Clean cached pages stay reclaimable, so
|
||||
* the warmer is a hint, never a reservation: it stops at memory pressure. */
|
||||
enum { WARM_THREADS = 4 };
|
||||
#define WARM_CHUNK ((uint64_t)8 << 20)
|
||||
|
||||
struct ds4_engram_warm {
|
||||
int fd;
|
||||
uint64_t begin, end, next;
|
||||
_Atomic bool stop;
|
||||
bool done, ok;
|
||||
double started, seconds;
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t cond;
|
||||
unsigned running;
|
||||
pthread_t thread[WARM_THREADS];
|
||||
unsigned threads;
|
||||
};
|
||||
|
||||
static double warm_now(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (double)ts.tv_sec + ts.tv_nsec * 1e-9;
|
||||
}
|
||||
|
||||
static bool warm_pressure(void) {
|
||||
#ifdef __APPLE__
|
||||
int level = 0;
|
||||
size_t len = sizeof(level);
|
||||
/* 1 normal, 2 warn, 4 critical. */
|
||||
return sysctlbyname("kern.memorystatus_vm_pressure_level", &level, &len, NULL, 0) == 0 &&
|
||||
level > 1;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void *warm_thread(void *arg) {
|
||||
ds4_engram_warm *w = arg;
|
||||
#ifdef __APPLE__
|
||||
pthread_set_qos_class_self_np(QOS_CLASS_UTILITY, 0);
|
||||
#endif
|
||||
uint8_t *buf = malloc(WARM_CHUNK);
|
||||
bool ok = buf != NULL;
|
||||
for (unsigned n = 0; ok && !w->stop; n++) {
|
||||
const uint64_t off = __atomic_fetch_add(&w->next, WARM_CHUNK, __ATOMIC_RELAXED);
|
||||
if (off >= w->end) break;
|
||||
if (!(n & 63) && warm_pressure()) {
|
||||
w->stop = true;
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
size_t left = (size_t)(w->end - off < WARM_CHUNK ? w->end - off : WARM_CHUNK);
|
||||
const uint64_t end = off + left;
|
||||
while (left) {
|
||||
/* A short read still warms those pages; only the file offset advances. */
|
||||
const ssize_t got = pread(w->fd, buf, left, (off_t)(end - left));
|
||||
if (got < 0 && errno == EINTR) continue;
|
||||
if (got <= 0 || (size_t)got > left) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
left -= (size_t)got;
|
||||
}
|
||||
}
|
||||
free(buf);
|
||||
pthread_mutex_lock(&w->lock);
|
||||
if (!ok) w->ok = false;
|
||||
if (--w->running == 0) {
|
||||
w->done = true;
|
||||
w->seconds = warm_now() - w->started;
|
||||
pthread_cond_broadcast(&w->cond);
|
||||
}
|
||||
pthread_mutex_unlock(&w->lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint64_t ds4_engram_physical_memory(void) {
|
||||
#ifdef __APPLE__
|
||||
uint64_t bytes = 0;
|
||||
size_t len = sizeof(bytes);
|
||||
return sysctlbyname("hw.memsize", &bytes, &len, NULL, 0) == 0 ? bytes : 0;
|
||||
#else
|
||||
const long pages = sysconf(_SC_PHYS_PAGES), page = sysconf(_SC_PAGESIZE);
|
||||
return pages > 0 && page > 0 ? (uint64_t)pages * (uint64_t)page : 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
ds4_engram_warm *ds4_engram_warm_start(const char *path, uint64_t offset, uint64_t bytes) {
|
||||
if (!path || !bytes || offset > INT64_MAX || bytes > INT64_MAX - offset) return NULL;
|
||||
ds4_engram_warm *w = calloc(1, sizeof(*w));
|
||||
if (!w) return NULL;
|
||||
w->fd = open(path, O_RDONLY | O_CLOEXEC);
|
||||
if (w->fd < 0) {
|
||||
free(w);
|
||||
return NULL;
|
||||
}
|
||||
w->begin = w->next = offset;
|
||||
w->end = offset + bytes;
|
||||
w->ok = true;
|
||||
w->started = warm_now();
|
||||
pthread_mutex_init(&w->lock, NULL);
|
||||
pthread_cond_init(&w->cond, NULL);
|
||||
for (unsigned i = 0; i < WARM_THREADS; i++) {
|
||||
pthread_mutex_lock(&w->lock);
|
||||
w->running++;
|
||||
pthread_mutex_unlock(&w->lock);
|
||||
if (pthread_create(&w->thread[i], NULL, warm_thread, w)) {
|
||||
pthread_mutex_lock(&w->lock);
|
||||
w->running--;
|
||||
pthread_mutex_unlock(&w->lock);
|
||||
break;
|
||||
}
|
||||
w->threads++;
|
||||
}
|
||||
if (!w->threads) {
|
||||
ds4_engram_warm_stop(w);
|
||||
return NULL;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
bool ds4_engram_warm_wait(ds4_engram_warm *w, double *seconds) {
|
||||
if (!w) return false;
|
||||
pthread_mutex_lock(&w->lock);
|
||||
while (!w->done) pthread_cond_wait(&w->cond, &w->lock);
|
||||
const bool ok = w->ok;
|
||||
if (seconds) *seconds = w->seconds;
|
||||
pthread_mutex_unlock(&w->lock);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void ds4_engram_warm_stop(ds4_engram_warm *w) {
|
||||
if (!w) return;
|
||||
w->stop = true;
|
||||
for (unsigned i = 0; i < w->threads; i++)
|
||||
if (pthread_join(w->thread[i], NULL)) abort();
|
||||
pthread_mutex_destroy(&w->lock);
|
||||
pthread_cond_destroy(&w->cond);
|
||||
close(w->fd);
|
||||
free(w);
|
||||
}
|
||||
|
||||
bool ds4_engram_read_batch_finish(ds4_engram_prefetch *p) {
|
||||
if (!p) return false;
|
||||
#ifdef __APPLE__
|
||||
dispatch_group_wait(p->group, DISPATCH_TIME_FOREVER);
|
||||
dispatch_release(p->group);
|
||||
#else
|
||||
if (p->threaded && pthread_join(p->thread, NULL)) abort();
|
||||
#endif
|
||||
const bool ok = p->ok;
|
||||
if (!ok) errno = p->error;
|
||||
free(p);
|
||||
return ok;
|
||||
}
|
||||
|
||||
enum { TOKEN_READERS = 4 };
|
||||
|
||||
typedef struct {
|
||||
pthread_t thread;
|
||||
ds4_engram_reader *reader;
|
||||
unsigned index;
|
||||
} engram_read_worker;
|
||||
|
||||
struct ds4_engram_reader {
|
||||
engram_read_worker workers[TOKEN_READERS];
|
||||
unsigned threads;
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t work, done;
|
||||
bool active, shutdown;
|
||||
uint64_t generation;
|
||||
unsigned completed[2];
|
||||
int errors[2];
|
||||
ds4_engram_table tables[2];
|
||||
uint32_t ids[2][DS4_ENGRAM_COLS];
|
||||
float *out;
|
||||
};
|
||||
|
||||
static void *read_token_thread(void *context) {
|
||||
const engram_read_worker *worker = context;
|
||||
ds4_engram_reader *r = worker->reader;
|
||||
const unsigned index = worker->index;
|
||||
uint64_t seen = 0;
|
||||
pthread_mutex_lock(&r->mutex);
|
||||
for (;;) {
|
||||
while (r->generation == seen && !r->shutdown) pthread_cond_wait(&r->work, &r->mutex);
|
||||
if (r->shutdown) break;
|
||||
seen = r->generation;
|
||||
pthread_mutex_unlock(&r->mutex);
|
||||
for (unsigned table = 0; table < 2; table++) {
|
||||
const unsigned first = index * DS4_ENGRAM_COLS / TOKEN_READERS;
|
||||
const unsigned end = (index + 1u) * DS4_ENGRAM_COLS / TOKEN_READERS;
|
||||
const bool ok = ds4_engram_read(&r->tables[table], r->ids[table] + first,
|
||||
end - first, r->out + (table * DS4_ENGRAM_COLS + first) * DS4_ENGRAM_DIM);
|
||||
const int error = ok ? 0 : errno ? errno : EIO;
|
||||
pthread_mutex_lock(&r->mutex);
|
||||
if (!r->errors[table]) r->errors[table] = error;
|
||||
if (++r->completed[table] == TOKEN_READERS) {
|
||||
if (table == 1) r->active = false;
|
||||
pthread_cond_broadcast(&r->done);
|
||||
}
|
||||
pthread_mutex_unlock(&r->mutex);
|
||||
}
|
||||
pthread_mutex_lock(&r->mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&r->mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ds4_engram_reader *ds4_engram_reader_create(void) {
|
||||
ds4_engram_reader *r = calloc(1, sizeof(*r));
|
||||
if (!r) return NULL;
|
||||
int error = pthread_mutex_init(&r->mutex, NULL);
|
||||
if (error) goto fail;
|
||||
error = pthread_cond_init(&r->work, NULL);
|
||||
if (error) goto mutex;
|
||||
error = pthread_cond_init(&r->done, NULL);
|
||||
if (error) goto work;
|
||||
for (unsigned i = 0; i < TOKEN_READERS; i++) {
|
||||
r->workers[i].reader = r;
|
||||
r->workers[i].index = i;
|
||||
error = pthread_create(&r->workers[i].thread, NULL, read_token_thread, &r->workers[i]);
|
||||
if (error) break;
|
||||
r->threads++;
|
||||
}
|
||||
if (!error) return r;
|
||||
pthread_mutex_lock(&r->mutex);
|
||||
r->shutdown = true;
|
||||
pthread_cond_broadcast(&r->work);
|
||||
pthread_mutex_unlock(&r->mutex);
|
||||
for (unsigned i = 0; i < r->threads; i++)
|
||||
if (pthread_join(r->workers[i].thread, NULL)) abort();
|
||||
pthread_cond_destroy(&r->done);
|
||||
work:
|
||||
pthread_cond_destroy(&r->work);
|
||||
mutex:
|
||||
pthread_mutex_destroy(&r->mutex);
|
||||
fail:
|
||||
free(r);
|
||||
errno = error;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool ds4_engram_reader_start(ds4_engram_reader *r,
|
||||
const ds4_engram_table tables[2], const uint32_t ids[2][DS4_ENGRAM_COLS], float *out) {
|
||||
if (!r || !tables || !ids || !out) { errno = EINVAL; return false; }
|
||||
for (unsigned t = 0; t < 2; t++) {
|
||||
if (tables[t].fd < 0) { errno = EINVAL; return false; }
|
||||
for (unsigned col = 0; col < DS4_ENGRAM_COLS; col++)
|
||||
if (ids[t][col] >= tables[t].rows) { errno = EINVAL; return false; }
|
||||
}
|
||||
pthread_mutex_lock(&r->mutex);
|
||||
if (r->active || r->shutdown) {
|
||||
pthread_mutex_unlock(&r->mutex);
|
||||
errno = EBUSY;
|
||||
return false;
|
||||
}
|
||||
memcpy(r->tables, tables, sizeof(r->tables));
|
||||
memcpy(r->ids, ids, sizeof(r->ids));
|
||||
memset(r->errors, 0, sizeof(r->errors));
|
||||
r->out = out;
|
||||
memset(r->completed, 0, sizeof(r->completed));
|
||||
r->generation++;
|
||||
r->active = true;
|
||||
pthread_cond_broadcast(&r->work);
|
||||
pthread_mutex_unlock(&r->mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ds4_engram_reader_wait(ds4_engram_reader *r, unsigned table) {
|
||||
if (!r || table >= 2) { errno = EINVAL; return false; }
|
||||
pthread_mutex_lock(&r->mutex);
|
||||
if (!r->active && !r->completed[1]) {
|
||||
pthread_mutex_unlock(&r->mutex);
|
||||
errno = EINVAL;
|
||||
return false;
|
||||
}
|
||||
while (r->completed[table] < TOKEN_READERS) pthread_cond_wait(&r->done, &r->mutex);
|
||||
const int error = r->errors[table];
|
||||
pthread_mutex_unlock(&r->mutex);
|
||||
if (error) errno = error;
|
||||
return !error;
|
||||
}
|
||||
|
||||
void ds4_engram_reader_free(ds4_engram_reader *r) {
|
||||
if (!r) return;
|
||||
pthread_mutex_lock(&r->mutex);
|
||||
r->shutdown = true;
|
||||
pthread_cond_broadcast(&r->work);
|
||||
pthread_mutex_unlock(&r->mutex);
|
||||
for (unsigned i = 0; i < r->threads; i++)
|
||||
if (pthread_join(r->workers[i].thread, NULL)) abort();
|
||||
pthread_cond_destroy(&r->done);
|
||||
pthread_cond_destroy(&r->work);
|
||||
pthread_mutex_destroy(&r->mutex);
|
||||
free(r);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
#ifndef DS4_ENGRAM_H
|
||||
#define DS4_ENGRAM_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
enum {
|
||||
DS4_ENGRAM_LAYERS = 2,
|
||||
DS4_ENGRAM_NGRAM = 4,
|
||||
DS4_ENGRAM_HEADS = 8,
|
||||
DS4_ENGRAM_COLS = 24,
|
||||
DS4_ENGRAM_DIM = 256,
|
||||
DS4_ENGRAM_ROW_BYTES = 264,
|
||||
DS4_ENGRAM_DEAD = -1
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const uint32_t *token_map;
|
||||
uint32_t vocab_size, compressed_vocab_size, pad_id;
|
||||
uint32_t rows[DS4_ENGRAM_LAYERS];
|
||||
uint64_t multipliers[DS4_ENGRAM_LAYERS][DS4_ENGRAM_NGRAM];
|
||||
uint32_t primes[DS4_ENGRAM_LAYERS][DS4_ENGRAM_COLS];
|
||||
} ds4_engram_layout;
|
||||
|
||||
/* Newest first; copying this value also snapshots the complete hash state. */
|
||||
typedef struct {
|
||||
int32_t tail[DS4_ENGRAM_NGRAM - 1];
|
||||
} ds4_engram_history;
|
||||
|
||||
bool ds4_engram_layout_valid(const ds4_engram_layout *layout);
|
||||
void ds4_engram_history_reset(ds4_engram_history *history);
|
||||
/* Validate the layout once at model load. Mask zero breaks all n-grams that
|
||||
* cross that position. Output is [token][layer][column], including masked rows;
|
||||
* the graph must suppress Engram at masked positions, as in the reference. */
|
||||
bool ds4_engram_hash(const ds4_engram_layout *layout,
|
||||
ds4_engram_history *history,
|
||||
const int *tokens, const uint8_t *mask, size_t count,
|
||||
uint32_t *rows);
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
uint64_t offset;
|
||||
uint32_t rows;
|
||||
} ds4_engram_table;
|
||||
|
||||
/* A separate uncached file descriptor, never an mmap or Metal model view.
|
||||
* Each GGUF I8 row is 256 E4M3 bytes followed by 8 original E8M0 scales. */
|
||||
bool ds4_engram_table_open(ds4_engram_table *table, const char *path,
|
||||
uint64_t offset, uint32_t rows);
|
||||
void ds4_engram_table_close(ds4_engram_table *table);
|
||||
/* Output uses F32 storage for the reference's BF16-rounded values. No whole
|
||||
* table allocation; caller owns count * DIM floats. Failure invalidates output. */
|
||||
bool ds4_engram_read(const ds4_engram_table *table, const uint32_t *rows,
|
||||
size_t count, float *out);
|
||||
/* Read COLS rows per token, restoring token order after deduplicated disk reads.
|
||||
* Input stride is in row IDs; output is packed [token][COLS][DIM]. Temporary
|
||||
* storage is bounded to 384 KiB, independent of the table and prefix size.
|
||||
* On macOS, large batches use bounded concurrent pread readers. */
|
||||
bool ds4_engram_read_batch(const ds4_engram_table *table, const uint32_t *rows,
|
||||
size_t tokens, size_t stride, float *out);
|
||||
|
||||
/* The same read on a worker: `rows` and `out` stay the caller's until finish,
|
||||
* which joins the worker and returns its result. */
|
||||
typedef struct ds4_engram_prefetch ds4_engram_prefetch;
|
||||
ds4_engram_prefetch *ds4_engram_read_batch_start(const ds4_engram_table *table, const uint32_t *rows,
|
||||
size_t tokens, size_t stride, float *out);
|
||||
bool ds4_engram_read_batch_finish(ds4_engram_prefetch *prefetch);
|
||||
|
||||
/* One reusable reader for a token's two tables. Tables/IDs are copied at
|
||||
* start; descriptors and output must remain valid until wait(reader, 1) or
|
||||
* free, even if wait(reader, 0) reports an error. No table data is cached.
|
||||
* One caller owns the reader. Creation failure permits synchronous fallback. */
|
||||
typedef struct ds4_engram_reader ds4_engram_reader;
|
||||
ds4_engram_reader *ds4_engram_reader_create(void);
|
||||
bool ds4_engram_reader_start(ds4_engram_reader *reader,
|
||||
const ds4_engram_table tables[2], const uint32_t ids[2][DS4_ENGRAM_COLS], float *out);
|
||||
bool ds4_engram_reader_wait(ds4_engram_reader *reader, unsigned table);
|
||||
void ds4_engram_reader_free(ds4_engram_reader *reader);
|
||||
|
||||
/* Background sequential read of [offset, offset + bytes) into the kernel page
|
||||
* cache, so the uncached row readers above hit memory. No table allocation;
|
||||
* stops early at memory pressure. Wait reports whether it covered the span. */
|
||||
typedef struct ds4_engram_warm ds4_engram_warm;
|
||||
uint64_t ds4_engram_physical_memory(void);
|
||||
ds4_engram_warm *ds4_engram_warm_start(const char *path, uint64_t offset, uint64_t bytes);
|
||||
bool ds4_engram_warm_wait(ds4_engram_warm *warm, double *seconds);
|
||||
void ds4_engram_warm_stop(ds4_engram_warm *warm);
|
||||
|
||||
#endif
|
||||
+787
-101
File diff suppressed because it is too large.
Load diff
+1118
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,51 @@
|
||||
#ifndef DS4_EVAL_CASES_H
|
||||
#define DS4_EVAL_CASES_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define EVAL_MAX_CHOICES 10
|
||||
#define EVAL_MAX_ALIASES 4
|
||||
|
||||
typedef enum {
|
||||
EVAL_ANSWER_AUTO = 0,
|
||||
EVAL_ANSWER_CHOICE,
|
||||
EVAL_ANSWER_INTEGER,
|
||||
EVAL_ANSWER_RATIONAL,
|
||||
EVAL_ANSWER_EXACT_TEXT,
|
||||
EVAL_ANSWER_ORDERED_SEQUENCE,
|
||||
EVAL_ANSWER_LINE_SET,
|
||||
} eval_answer_kind;
|
||||
|
||||
enum {
|
||||
EVAL_SUITE_CORE = 1u << 0,
|
||||
EVAL_SUITE_HARD = 1u << 1,
|
||||
EVAL_SUITE_HARD_SMOKE = 1u << 2,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char *source;
|
||||
const char *id;
|
||||
const char *domain;
|
||||
const char *title;
|
||||
const char *question;
|
||||
const char *choice[EVAL_MAX_CHOICES];
|
||||
const char *answer;
|
||||
const char *alias[EVAL_MAX_ALIASES];
|
||||
eval_answer_kind answer_kind;
|
||||
uint32_t suites;
|
||||
uint32_t max_tokens;
|
||||
} eval_case;
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char *url;
|
||||
const char *license;
|
||||
} eval_source;
|
||||
|
||||
extern const eval_case eval_hard_cases[];
|
||||
extern const size_t eval_hard_case_count;
|
||||
extern const eval_source eval_hard_sources[];
|
||||
extern const size_t eval_hard_source_count;
|
||||
|
||||
#endif
|
||||
@@ -261,6 +261,7 @@ __global__ static void glm53_vision_layernorm_gelu_kernel(
|
||||
__syncthreads();
|
||||
}
|
||||
const float mean = partial[0] / (float)width;
|
||||
__syncthreads();
|
||||
float var = 0.0f;
|
||||
for (uint32_t d = tid; d < width; d += blockDim.x) {
|
||||
const float centered = xr[d] - mean;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef DS4_GPU_COPY_H
|
||||
#define DS4_GPU_COPY_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Device copy spans, shared by ds4_gpu.h and the CUDA backend, which does
|
||||
* not include ds4_gpu.h. The tensor stays opaque here. */
|
||||
#ifndef DS4_GPU_TENSOR_DEFINED
|
||||
#define DS4_GPU_TENSOR_DEFINED
|
||||
typedef struct ds4_gpu_tensor ds4_gpu_tensor;
|
||||
#endif
|
||||
|
||||
/* One device copy, with the arguments of ds4_gpu_tensor_copy. */
|
||||
typedef struct {
|
||||
ds4_gpu_tensor *dst;
|
||||
uint64_t dst_offset;
|
||||
const ds4_gpu_tensor *src;
|
||||
uint64_t src_offset;
|
||||
uint64_t bytes;
|
||||
} ds4_gpu_copy_span;
|
||||
|
||||
#define DS4_GPU_COPY_SPANS_MAX 316u
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,63 @@
|
||||
#ifndef DS4_GPU_TP_H
|
||||
#define DS4_GPU_TP_H
|
||||
|
||||
#include <stdint.h>
|
||||
#ifndef DS4_GPU_TENSOR_DEFINED
|
||||
#define DS4_GPU_TENSOR_DEFINED
|
||||
typedef struct ds4_gpu_tensor ds4_gpu_tensor;
|
||||
#endif
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Metal queues each gate on its service thread and orders GPU arrival/release
|
||||
* with events or flags. CUDA waits for its local stream and calls the exchange
|
||||
* synchronously. Callbacks return nonzero on success; both ranks must issue
|
||||
* the same gate sequence. Shutdown precedes transport/slab destruction. */
|
||||
typedef int (*ds4_gpu_tp_exchange_fn)(void *ud, uint32_t layer, uint32_t gate, uint64_t seq);
|
||||
typedef int (*ds4_gpu_tp_batch_exchange_fn)(void *ud, uint32_t layer,
|
||||
uint32_t rows, uint64_t seq);
|
||||
typedef int (*ds4_gpu_tp_big_exchange_fn)(void *ud, uint32_t layer,
|
||||
uint64_t seq, const void *out,
|
||||
void *in, uint64_t bytes);
|
||||
|
||||
/* CUDA: optional pinned slab shared by an integrated GPU and the transport.
|
||||
* NULL requests the ordinary device tensor plus host staging fallback. */
|
||||
ds4_gpu_tensor *ds4_gpu_tp_slab_alloc(uint64_t bytes);
|
||||
|
||||
int ds4_gpu_tp_init(uint32_t rank, ds4_gpu_tensor *slab,
|
||||
uint64_t gpu_flags_off, uint64_t out_off, uint64_t vec_bytes,
|
||||
ds4_gpu_tp_exchange_fn fn, void *ud);
|
||||
void ds4_gpu_tp_shutdown(void);
|
||||
int ds4_gpu_tp_failed(void);
|
||||
int ds4_gpu_tp_gate_encode(uint32_t layer, uint32_t gate);
|
||||
void ds4_gpu_tp_set_batch_exchange(ds4_gpu_tp_batch_exchange_fn fn);
|
||||
int ds4_gpu_tp_batch_gate_encode(uint32_t layer, uint32_t rows);
|
||||
void ds4_gpu_tp_set_big_exchange(ds4_gpu_tp_big_exchange_fn fn);
|
||||
int ds4_gpu_tp_big_gate_encode(uint32_t layer, uint32_t rows,
|
||||
const ds4_gpu_tensor *out_t,
|
||||
ds4_gpu_tensor *in_t, uint64_t bytes);
|
||||
|
||||
/* Metal multi-session tapes reuse slab slots and therefore require event
|
||||
* arrival. Single-session flag gates may flush in layer order. */
|
||||
void ds4_gpu_tp_set_session_batch_mode(int enabled);
|
||||
/* Metal decode gates publish and wait inside the command buffer (no flush per gate). */
|
||||
int ds4_gpu_tp_decode_inline_gates(void);
|
||||
int ds4_gpu_tp_decode_split_flush_safe(void);
|
||||
/* Weight ranges consumed by the next Metal poll gate of the given kind. */
|
||||
int ds4_gpu_tp_gate_prefetch_plan(uint32_t gate,
|
||||
const void *model_map, uint64_t model_size,
|
||||
const uint64_t *offsets, const uint64_t *bytes,
|
||||
uint32_t count);
|
||||
/* Coordinator-only drafting is replicated; verification remains sharded. */
|
||||
void ds4_gpu_tp_suspend_expert_sharding(int suspend);
|
||||
/* No-op when TP is not bound. */
|
||||
void ds4_gpu_tp_keepalive_pause(int paused);
|
||||
/* GLM attention head ownership; the caller zeros unowned heads and exchanges
|
||||
* the output-projection partials at the big gate. */
|
||||
void ds4_gpu_tp_set_attn_head_split(int enabled);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
+31
-14
@@ -148,7 +148,7 @@ static void print_model_runtime(FILE *fp, const help_colors *c,
|
||||
title(fp, c, "Model And Runtime");
|
||||
opt(fp, c, "-m, --model FILE", "GGUF model path. Default: ds4flash.gguf");
|
||||
if (tool == DS4_HELP_DS4 || tool == DS4_HELP_AGENT || tool == DS4_HELP_SERVER) {
|
||||
opt(fp, c, "--vision FILE", "GLM 5.3 vision encoder GGUF.");
|
||||
opt(fp, c, "--vision FILE", "Vision encoder GGUF for the selected model.");
|
||||
}
|
||||
#ifdef DS4_ROCM_BUILD
|
||||
opt(fp, c, "--metal | --rocm | --cpu", "Select the backend explicitly.");
|
||||
@@ -172,13 +172,13 @@ static void print_model_runtime(FILE *fp, const help_colors *c,
|
||||
opt(fp, c, "--power N", "GPU duty-cycle target, 1..100. Default: 100");
|
||||
opt(fp, c, "--ssd-streaming", "Metal/CUDA/ROCm: opt in to SSD-backed model streaming instead of full residency.");
|
||||
opt(fp, c, "--ssd-streaming-cold", "SSD streaming: skip default popularity-based expert-cache preload.");
|
||||
opt(fp, c, "--ssd-streaming-cache-experts N|NGB", "SSD streaming: N is an exact dynamic expert count; NGB is a routed memory budget that also reserves two full prefill layers. Auto: 80% working set minus non-routed weights; GLM Metal caps lower.");
|
||||
opt(fp, c, "--ssd-streaming-cache-experts N|NGB", "SSD streaming cache target. N requests dynamic expert slots; NGB also reserves two full prefill layers. Either may be reduced to fit the model, graph, context, and backend working set.");
|
||||
opt(fp, c, "--ssd-streaming-full-layers N", "GLM Metal streaming: keep the first N routed layers fully resident. Default: auto from NGB expert budget; use 0 to disable.");
|
||||
opt(fp, c, "--ssd-streaming-preload-experts N", "SSD streaming: upfront popularity preload count. DeepSeek auto-seeds by default; GLM demand-fills unless N is explicit.");
|
||||
opt(fp, c, "--simulate-used-memory NGB", "Diagnostic: lock N GiB before model load to simulate a smaller-memory machine.");
|
||||
opt(fp, c, "--prefill-chunk N", "Graph prefill chunk size. Default: CUDA TP 2048; PRO long prompts 8192; others 4096.");
|
||||
if (full) {
|
||||
if (tool == DS4_HELP_EVAL) {
|
||||
if (tool == DS4_HELP_EVAL || tool == DS4_HELP_BENCH) {
|
||||
opt(fp, c, "--mtp-model FILE", "External MTP or DSpark support GGUF.");
|
||||
}
|
||||
if (tool == DS4_HELP_DS4 || tool == DS4_HELP_AGENT || tool == DS4_HELP_SERVER) {
|
||||
@@ -188,12 +188,15 @@ static void print_model_runtime(FILE *fp, const help_colors *c,
|
||||
opt(fp, c, "--mtp-margin F", "Verifier confidence margin for fast MTP acceptance. Default: 3");
|
||||
opt(fp, c, "--mtp-timing", "Enable embedded MTP and print acceptance/timing counters.");
|
||||
opt(fp, c, "--dspark", "Enable DSpark using the support GGUF passed with --mtp-model.");
|
||||
opt(fp, c, "--dspark-confidence F", "Enable DSpark with confidence pruning threshold 0..1. Greedy/opportunistic default: Metal 0.6, CUDA/ROCm 0.7; exact sampling: 0.8");
|
||||
opt(fp, c, "--dspark-confidence F", "Enable DSpark with confidence pruning threshold 0..1. Greedy/opportunistic default: resident Spark Q2 0.4, two-Spark Flash Q2/MXFP4 0, Metal 0.6, other CUDA/ROCm 0.7; exact sampling: 0.8");
|
||||
opt(fp, c, "--mtp-exact-sampling", "Preserve the ordinary temperature distribution instead of accepting target-matching greedy drafts directly.");
|
||||
opt(fp, c, "--dspark-strict", "Load DSpark support but keep target-only decode.");
|
||||
} else if (tool == DS4_HELP_BENCH) {
|
||||
opt(fp, c, "--dspark", "Benchmark greedy DSpark using the support GGUF passed with --mtp-model.");
|
||||
opt(fp, c, "--dspark-confidence F", "DSpark confidence pruning threshold 0..1.");
|
||||
}
|
||||
opt(fp, c, "--quality", "Prefer exact kernels where faster approximate paths exist.");
|
||||
opt(fp, c, "--warm-weights", "Touch mapped tensor pages at startup to reduce first-use stalls.");
|
||||
opt(fp, c, "--warm-weights", "Touch resident weights at startup to reduce first-use stalls.");
|
||||
if (tool == DS4_HELP_DS4 || tool == DS4_HELP_BENCH) {
|
||||
opt(fp, c, "--expert-profile FILE", "Metal-only: write routed expert locality/cache simulation JSON.");
|
||||
}
|
||||
@@ -201,7 +204,7 @@ static void print_model_runtime(FILE *fp, const help_colors *c,
|
||||
fputc('\n', fp);
|
||||
}
|
||||
|
||||
static void print_sampling(FILE *fp, const help_colors *c, bool full) {
|
||||
static void print_sampling(FILE *fp, const help_colors *c, bool full, ds4_help_tool tool) {
|
||||
title(fp, c, "Prompt And Sampling");
|
||||
opt(fp, c, "-n, --tokens N", "Maximum generated tokens.");
|
||||
opt(fp, c, "--temp F", "Sampling temperature. 0 is greedy/deterministic.");
|
||||
@@ -209,8 +212,10 @@ static void print_sampling(FILE *fp, const help_colors *c, bool full) {
|
||||
opt(fp, c, "--min-p F", "Keep tokens scoring at least F times the top token.");
|
||||
opt(fp, c, "--seed N", "Sampling seed for reproducible non-greedy runs.");
|
||||
para(fp, c, "GLM CLI and agent runs default to temperature 1.0, top-p 0.95, and min-p 0 unless those options are set explicitly.");
|
||||
opt(fp, c, "--think", "Use normal thinking mode.");
|
||||
opt(fp, c, "--think-max", "Use Think Max when context is large enough.");
|
||||
opt(fp, c, "--think", "Use normal thinking mode (V4.1: effort 75).");
|
||||
opt(fp, c, "--think-max", "Use maximum thinking (V4.1: 100; V4: requires ctx >= 393216).");
|
||||
if (tool == DS4_HELP_DS4 || tool == DS4_HELP_AGENT)
|
||||
opt(fp, c, "--think-level N", "V4.1 thinking effort, 1..100; 0 disables thinking.");
|
||||
opt(fp, c, "--nothink", "Disable thinking and ask for direct replies.");
|
||||
if (full) {
|
||||
opt(fp, c, "-sys, --system TEXT", "System prompt. Empty string disables the default where supported.");
|
||||
@@ -264,6 +269,7 @@ static void print_cli_specific(FILE *fp, const help_colors *c, bool full) {
|
||||
opt(fp, c, "ds4", "Start the interactive prompt.");
|
||||
opt(fp, c, "ds4 -p TEXT", "Run one prompt and exit.");
|
||||
opt(fp, c, "ds4 --prompt-file FILE", "Run a long prompt from a file and exit.");
|
||||
opt(fp, c, "--prefix-file FILE", "Preload complete alternating USER:/ASSISTANT: turns before the live conversation.");
|
||||
fputc('\n', fp);
|
||||
if (full) {
|
||||
print_cli_diagnostics(fp, c);
|
||||
@@ -284,6 +290,7 @@ static void print_cli_diagnostics(FILE *fp, const help_colors *c) {
|
||||
opt(fp, c, "--imatrix-out FILE", "Write llama-compatible routed-MoE imatrix .dat.");
|
||||
opt(fp, c, "--imatrix-max-prompts N", "Stop imatrix collection after N prompts.");
|
||||
opt(fp, c, "--imatrix-max-tokens N", "Stop imatrix collection after N prompt tokens.");
|
||||
opt(fp, c, "--imatrix-min-expert-samples N", "Continue until every routed expert has N samples.");
|
||||
opt(fp, c, "--head-test", "Run the output HC/logits head after the native slice.");
|
||||
opt(fp, c, "--first-token-test", "Run exact CPU whole-model pass for the first prompt token.");
|
||||
opt(fp, c, "--metal-graph-test", "Compare first GPU-resident graph stages with CPU.");
|
||||
@@ -295,7 +302,7 @@ static void print_cli_diagnostics(FILE *fp, const help_colors *c) {
|
||||
static void print_cli_commands(FILE *fp, const help_colors *c) {
|
||||
title_red(fp, c, "Interactive Commands");
|
||||
opt(fp, c, "/help", "Show interactive commands.");
|
||||
opt(fp, c, "/think, /think-max, /nothink", "Switch thinking mode.");
|
||||
opt(fp, c, "/think [N], /think-max, /nothink", "Switch thinking mode; V4.1 accepts effort 0..100.");
|
||||
opt(fp, c, "/ctx N", "Restart the interactive session with a new context size.");
|
||||
opt(fp, c, "/power N", "Set GPU duty cycle percentage, 1..100.");
|
||||
opt(fp, c, "/read FILE", "Submit a text file, PNG, or JPEG as the next user message.");
|
||||
@@ -308,6 +315,7 @@ static void print_agent_specific(FILE *fp, const help_colors *c) {
|
||||
title(fp, c, "Agent Options");
|
||||
opt(fp, c, "-p, --prompt TEXT", "Submit an initial prompt after startup.");
|
||||
opt(fp, c, "--prompt-file FILE", "Read the initial prompt from FILE.");
|
||||
opt(fp, c, "--prefix-file FILE", "Preload complete alternating USER:/ASSISTANT: turns before the live task.");
|
||||
opt(fp, c, "--non-interactive", "Run without TUI. With an initial prompt: one turn; otherwise: repeated stdin prompts.");
|
||||
opt(fp, c, "--raw-prompt", "Non-interactive initial prompt only: omit agent chat/tool text.");
|
||||
opt(fp, c, "--edit-upto", "Enable anchored [upto] edits and automatic marker insertion.");
|
||||
@@ -326,6 +334,8 @@ static void print_agent_sessions(FILE *fp, const help_colors *c) {
|
||||
opt(fp, c, "/del ID", "Delete a saved session.");
|
||||
opt(fp, c, "/strip ID", "Remove KV payload; the text history can be rebuilt later.");
|
||||
opt(fp, c, "/history [N]", "Show N recent user turns from the current session.");
|
||||
opt(fp, c, "/hints on|off", "Enable or disable brief programming hints. New and resumed sessions start off.");
|
||||
opt(fp, c, "/think [N]", "V4.1: set effort 0..100 (default 75) after this turn; rebuilds the prefix.");
|
||||
opt(fp, c, "/power N", "Set GPU duty cycle percentage, 1..100.");
|
||||
opt(fp, c, "/new", "Start a fresh session from the system prompt.");
|
||||
opt(fp, c, "/quit, /exit", "Exit.");
|
||||
@@ -391,9 +401,16 @@ static void print_bench_specific(FILE *fp, const help_colors *c) {
|
||||
|
||||
static void print_eval_specific(FILE *fp, const help_colors *c) {
|
||||
title(fp, c, "Evaluation");
|
||||
opt(fp, c, "-n, --tokens N", "Max generated tokens per question. Default: 16000");
|
||||
opt(fp, c, "--questions N", "Run only the first N embedded questions.");
|
||||
opt(fp, c, "--suite NAME", "core, hard, all, or hard-smoke. Default: core");
|
||||
opt(fp, c, "--source NAME", "Run only cases from this source.");
|
||||
opt(fp, c, "--domain NAME", "Run only cases in this domain.");
|
||||
opt(fp, c, "--case-id ID", "Run the case with this source ID.");
|
||||
opt(fp, c, "--list-cases", "List selected cases without loading a model.");
|
||||
opt(fp, c, "--validate-cases", "Validate all embedded cases and exit.");
|
||||
opt(fp, c, "-n, --tokens N", "Override the generation budget for every question.");
|
||||
opt(fp, c, "--questions N", "Run only the first N selected questions.");
|
||||
opt(fp, c, "--case-sequence LIST", "Run 1-based case numbers in this comma-separated order.");
|
||||
opt(fp, c, "--retry-incomplete", "Retry a missing final answer once with twice the budget.");
|
||||
opt(fp, c, "--trace FILE", "Write questions, outputs, and grading decisions.");
|
||||
opt(fp, c, "--regrade-trace FILE", "Regrade a prior trace without loading the model.");
|
||||
opt(fp, c, "--soft-limit-reply-budget N", "Soft close thinking near the end of reply budget. Default: 1024");
|
||||
@@ -514,7 +531,7 @@ static void print_examples(FILE *fp, const help_colors *c, ds4_help_tool tool, c
|
||||
static void print_topic(FILE *fp, const help_colors *c, ds4_help_tool tool, const char *topic) {
|
||||
if (streq(topic, "all")) {
|
||||
print_model_runtime(fp, c, tool, true);
|
||||
if (tool_has_topic(tool, "sampling")) print_sampling(fp, c, true);
|
||||
if (tool_has_topic(tool, "sampling")) print_sampling(fp, c, true, tool);
|
||||
if (tool_has_topic(tool, "steering")) print_steering(fp, c);
|
||||
print_distributed(fp, c);
|
||||
if (tool == DS4_HELP_DS4) {
|
||||
@@ -536,7 +553,7 @@ static void print_topic(FILE *fp, const help_colors *c, ds4_help_tool tool, cons
|
||||
}
|
||||
|
||||
if (streq(topic, "runtime")) print_model_runtime(fp, c, tool, true);
|
||||
else if (streq(topic, "sampling")) print_sampling(fp, c, true);
|
||||
else if (streq(topic, "sampling")) print_sampling(fp, c, true, tool);
|
||||
else if (streq(topic, "steering")) print_steering(fp, c);
|
||||
else if (streq(topic, "distributed")) print_distributed(fp, c);
|
||||
else if (tool == DS4_HELP_DS4 && streq(topic, "diagnostics")) print_cli_diagnostics(fp, c);
|
||||
@@ -561,7 +578,7 @@ static void print_default(FILE *fp, const help_colors *c, ds4_help_tool tool) {
|
||||
|
||||
if (tool == DS4_HELP_DS4) {
|
||||
print_cli_specific(fp, c, true);
|
||||
print_sampling(fp, c, false);
|
||||
print_sampling(fp, c, false, tool);
|
||||
} else if (tool == DS4_HELP_SERVER) {
|
||||
print_server_api(fp, c);
|
||||
print_kv_cache(fp, c);
|
||||
|
||||
+643
-9
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -387,14 +389,20 @@ static double ds4_cubic(double x, double a) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
static void ds4_resize_rgb_bicubic(
|
||||
const uint8_t *src,
|
||||
uint32_t src_width,
|
||||
uint32_t src_height,
|
||||
float *dst,
|
||||
uint32_t dst_width,
|
||||
uint32_t dst_height,
|
||||
uint32_t dst_stride) {
|
||||
typedef struct {
|
||||
const uint8_t *src;
|
||||
float *dst;
|
||||
uint32_t src_width, src_height, dst_width, dst_height, dst_stride;
|
||||
uint32_t first_y, end_y;
|
||||
} ds4_resize_rows;
|
||||
|
||||
static void *ds4_resize_bicubic_rows(void *context) {
|
||||
const ds4_resize_rows *rows = context;
|
||||
const uint8_t *src = rows->src;
|
||||
float *dst = rows->dst;
|
||||
const uint32_t src_width = rows->src_width, src_height = rows->src_height;
|
||||
const uint32_t dst_width = rows->dst_width, dst_height = rows->dst_height;
|
||||
const uint32_t dst_stride = rows->dst_stride;
|
||||
double scale_x = (double)src_width / dst_width;
|
||||
double scale_y = (double)src_height / dst_height;
|
||||
double filter_x = scale_x >= 1.0 ? 1.0 / scale_x : 1.0;
|
||||
@@ -402,7 +410,7 @@ static void ds4_resize_rgb_bicubic(
|
||||
double support_x = scale_x >= 1.0 ? 2.0 * scale_x : 2.0;
|
||||
double support_y = scale_y >= 1.0 ? 2.0 * scale_y : 2.0;
|
||||
|
||||
for (uint32_t dy = 0; dy < dst_height; dy++) {
|
||||
for (uint32_t dy = rows->first_y; dy < rows->end_y; dy++) {
|
||||
double center_y = scale_y * ((double)dy + 0.5);
|
||||
int y0 = (int)(center_y - support_y + 0.5);
|
||||
int y1 = (int)(center_y + support_y + 0.5);
|
||||
@@ -440,6 +448,44 @@ static void ds4_resize_rgb_bicubic(
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void ds4_resize_rgb_bicubic(
|
||||
const uint8_t *src,
|
||||
uint32_t src_width,
|
||||
uint32_t src_height,
|
||||
float *dst,
|
||||
uint32_t dst_width,
|
||||
uint32_t dst_height,
|
||||
uint32_t dst_stride) {
|
||||
enum { MAX_WORKERS = 8 };
|
||||
/* Separate output row ranges use the original horizontal and vertical
|
||||
* coordinates, including when the destination is a padded canvas. */
|
||||
const uint64_t work = (uint64_t)(src_width > dst_width ? src_width : dst_width) *
|
||||
(src_height > dst_height ? src_height : dst_height);
|
||||
uint32_t workers = work / 65536u < MAX_WORKERS ? (uint32_t)(work / 65536u) : MAX_WORKERS;
|
||||
if (workers > dst_height) workers = dst_height;
|
||||
ds4_resize_rows whole = {src, dst, src_width, src_height, dst_width, dst_height,
|
||||
dst_stride, 0, dst_height};
|
||||
if (workers < 2u) {
|
||||
ds4_resize_bicubic_rows(&whole);
|
||||
return;
|
||||
}
|
||||
ds4_resize_rows rows[MAX_WORKERS];
|
||||
pthread_t threads[MAX_WORKERS - 1];
|
||||
uint32_t started = 0;
|
||||
for (; started + 1u < workers; started++) {
|
||||
rows[started] = whole;
|
||||
rows[started].first_y = (uint64_t)dst_height * started / workers;
|
||||
rows[started].end_y = (uint64_t)dst_height * (started + 1u) / workers;
|
||||
if (pthread_create(&threads[started], NULL, ds4_resize_bicubic_rows, &rows[started])) break;
|
||||
}
|
||||
whole.first_y = (uint64_t)dst_height * started / workers;
|
||||
ds4_resize_bicubic_rows(&whole);
|
||||
for (uint32_t i = 0; i < started; i++) {
|
||||
if (pthread_join(threads[i], NULL)) abort();
|
||||
}
|
||||
}
|
||||
|
||||
int ds4_image_preprocess_glm53(
|
||||
@@ -563,8 +609,596 @@ int ds4_image_preprocess_glm53(
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ds4_image_preprocess_qwen4(
|
||||
ds4_image_patches *out,
|
||||
const ds4_image *image,
|
||||
uint32_t min_image_tokens,
|
||||
uint32_t max_image_tokens,
|
||||
char *error,
|
||||
size_t error_cap) {
|
||||
const uint32_t patch = 16, factor = 32;
|
||||
if (!out) return 0;
|
||||
memset(out, 0, sizeof(*out));
|
||||
if (!image || !image->rgb || image->width == 0 || image->height == 0 ||
|
||||
min_image_tokens == 0 || max_image_tokens < min_image_tokens) {
|
||||
ds4_image_error(error, error_cap, "invalid Qwen3.8 image preprocessing parameters");
|
||||
return 0;
|
||||
}
|
||||
const double h = image->height, w = image->width;
|
||||
if (fmax(h, w) / fmin(h, w) > 200.0) {
|
||||
ds4_image_error(error, error_cap, "image aspect ratio must be below 200");
|
||||
return 0;
|
||||
}
|
||||
/* Qwen2VL smart_resize: round to the factor, then scale into [min, max] pixels */
|
||||
const double min_pixels = (double)min_image_tokens * factor * factor;
|
||||
const double max_pixels = (double)max_image_tokens * factor * factor;
|
||||
double h_bar = fmax(factor, round(h / factor) * factor);
|
||||
double w_bar = fmax(factor, round(w / factor) * factor);
|
||||
if (h_bar * w_bar > max_pixels) {
|
||||
const double beta = sqrt(h * w / max_pixels);
|
||||
h_bar = fmax(factor, floor(h / beta / factor) * factor);
|
||||
w_bar = fmax(factor, floor(w / beta / factor) * factor);
|
||||
} else if (h_bar * w_bar < min_pixels) {
|
||||
const double beta = sqrt(min_pixels / (h * w));
|
||||
h_bar = ceil(h * beta / factor) * factor;
|
||||
w_bar = ceil(w * beta / factor) * factor;
|
||||
}
|
||||
const uint32_t target_height = (uint32_t)h_bar, target_width = (uint32_t)w_bar;
|
||||
if (target_width > DS4_IMAGE_MAX_DIMENSION || target_height > DS4_IMAGE_MAX_DIMENSION ||
|
||||
(uint64_t)target_width * target_height > DS4_IMAGE_MAX_PIXELS) {
|
||||
ds4_image_error(error, error_cap, "resized image is too large");
|
||||
return 0;
|
||||
}
|
||||
float *canvas = malloc((size_t)target_height * target_width * 3 * sizeof(float));
|
||||
if (!canvas) {
|
||||
ds4_image_error(error, error_cap, "unable to allocate resized image");
|
||||
return 0;
|
||||
}
|
||||
if (target_width == image->width && target_height == image->height) {
|
||||
for (size_t i = 0; i < (size_t)target_height * target_width * 3; i++) canvas[i] = image->rgb[i];
|
||||
} else {
|
||||
ds4_resize_rgb_bicubic(image->rgb, image->width, image->height,
|
||||
canvas, target_width, target_height, target_width);
|
||||
}
|
||||
for (size_t i = 0; i < (size_t)target_height * target_width * 3; i++) {
|
||||
canvas[i] = (canvas[i] / 255.0f - 0.5f) / 0.5f;
|
||||
}
|
||||
const uint32_t grid_height = target_height / patch, grid_width = target_width / patch;
|
||||
const uint32_t patch_count = grid_height * grid_width;
|
||||
const size_t patch_values = (size_t)patch_count * 3 * patch * patch;
|
||||
float *patches = malloc(patch_values * sizeof(float));
|
||||
if (!patches) {
|
||||
free(canvas);
|
||||
ds4_image_error(error, error_cap, "unable to allocate vision patches");
|
||||
return 0;
|
||||
}
|
||||
size_t index = 0;
|
||||
for (uint32_t block_y = 0; block_y < grid_height / 2; block_y++) {
|
||||
for (uint32_t block_x = 0; block_x < grid_width / 2; block_x++) {
|
||||
for (uint32_t merge_y = 0; merge_y < 2; merge_y++) {
|
||||
for (uint32_t merge_x = 0; merge_x < 2; merge_x++) {
|
||||
const uint32_t patch_y = block_y * 2 + merge_y, patch_x = block_x * 2 + merge_x;
|
||||
for (uint32_t channel = 0; channel < 3; channel++) {
|
||||
for (uint32_t y = 0; y < patch; y++) {
|
||||
for (uint32_t x = 0; x < patch; x++) {
|
||||
patches[index++] = canvas[((size_t)(patch_y * patch + y) * target_width +
|
||||
patch_x * patch + x) * 3 + channel];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
free(canvas);
|
||||
out->content_width = target_width;
|
||||
out->content_height = target_height;
|
||||
out->padded_width = target_width;
|
||||
out->padded_height = target_height;
|
||||
out->grid_width = grid_width;
|
||||
out->grid_height = grid_height;
|
||||
out->patch_count = patch_count;
|
||||
out->image_token_count = patch_count / 4;
|
||||
out->patches = patches;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ds4_image_patches_free(ds4_image_patches *patches) {
|
||||
if (!patches) return;
|
||||
free(patches->patches);
|
||||
memset(patches, 0, sizeof(*patches));
|
||||
}
|
||||
|
||||
static uint32_t ds4_deepseek4_grid_tokens(
|
||||
uint32_t height,
|
||||
uint32_t width,
|
||||
uint32_t *grid_height,
|
||||
uint32_t *grid_width,
|
||||
bool v41) {
|
||||
const uint32_t h = (height / 14u + 2u) / 3u;
|
||||
const uint32_t w = (width / 14u + 2u) / 3u;
|
||||
uint64_t count = (uint64_t)h * (w + 1u) + 2u;
|
||||
if (!v41) {
|
||||
if (h & 1u) count += w + 1u;
|
||||
if ((((h + 1u) / 2u) * (w + 1u)) & 1u) count += 2u;
|
||||
}
|
||||
if (grid_height) *grid_height = h;
|
||||
if (grid_width) *grid_width = w;
|
||||
return count <= UINT32_MAX ? (uint32_t)count : UINT32_MAX;
|
||||
}
|
||||
|
||||
static int ds4_deepseek4_solve_resize(
|
||||
uint32_t height,
|
||||
uint32_t width,
|
||||
uint32_t budget,
|
||||
uint32_t *best_height,
|
||||
uint32_t *best_width,
|
||||
bool v41) {
|
||||
const double ratio = (double)height / width;
|
||||
const double max_w_float = sqrt(((double)budget - 2.0) / ratio + 0.25) - 0.5;
|
||||
const double max_h_float = max_w_float * ratio;
|
||||
uint32_t max_h, max_w;
|
||||
if (max_w_float < 1.0) {
|
||||
max_w = 1;
|
||||
max_h = (budget - 2u) / (max_w + 1u);
|
||||
if (!v41) max_h &= ~1u;
|
||||
if (max_h == 0) return 0;
|
||||
*best_width = max_w * 42u;
|
||||
*best_height = max_h * 42u;
|
||||
} else if (max_h_float < (v41 ? 1.0 : 2.0)) {
|
||||
max_h = v41 ? 1u : 2u;
|
||||
max_w = (budget - 2u) / max_h - 1u;
|
||||
if (max_w <= 1u) return 0;
|
||||
*best_width = max_w * 42u;
|
||||
*best_height = max_h * 42u;
|
||||
} else {
|
||||
max_w = (uint32_t)floor(max_w_float);
|
||||
max_h = (uint32_t)floor(max_h_float);
|
||||
if (!v41) max_h &= ~1u;
|
||||
if (max_w == 0 || max_h == 0) return 0;
|
||||
const double scale = fmin((double)max_w * 42.0 / width,
|
||||
(double)max_h * 42.0 / height);
|
||||
*best_width = (uint32_t)floor(width * scale / 14.0) * 14u;
|
||||
*best_height = (uint32_t)floor(height * scale / 14.0) * 14u;
|
||||
}
|
||||
return *best_width != 0 && *best_height != 0;
|
||||
}
|
||||
|
||||
static int ds4_deepseek4_safe_resize(
|
||||
uint32_t height,
|
||||
uint32_t width,
|
||||
uint32_t *best_height,
|
||||
uint32_t *best_width,
|
||||
uint32_t *llm_height,
|
||||
uint32_t *llm_width,
|
||||
bool v41) {
|
||||
const uint32_t max_tokens = v41 ? 1024u : 381u;
|
||||
uint32_t budget = max_tokens;
|
||||
uint32_t tokens = ds4_deepseek4_grid_tokens(
|
||||
*best_height, *best_width, llm_height, llm_width, v41);
|
||||
while (tokens > max_tokens) {
|
||||
if (budget <= 4u ||
|
||||
!ds4_deepseek4_solve_resize(height, width, budget,
|
||||
best_height, best_width, v41)) {
|
||||
return 0;
|
||||
}
|
||||
tokens = ds4_deepseek4_grid_tokens(
|
||||
*best_height, *best_width, llm_height, llm_width, v41);
|
||||
budget--;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ds4_image_preprocess_deepseek(
|
||||
ds4_deepseek4_image_patches *out,
|
||||
const ds4_image *image,
|
||||
char *error,
|
||||
size_t error_cap,
|
||||
bool v41) {
|
||||
if (!out) return 0;
|
||||
memset(out, 0, sizeof(*out));
|
||||
if (!image || !image->rgb || image->width == 0 || image->height == 0) {
|
||||
ds4_image_error(error, error_cap,
|
||||
"invalid DeepSeek V4 image preprocessing input");
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t planned_width = image->width;
|
||||
uint32_t planned_height = image->height;
|
||||
if (!v41 && (uint64_t)planned_width > (uint64_t)planned_height * 8u) {
|
||||
planned_width = planned_height * 8u;
|
||||
}
|
||||
const uint64_t planned_pixels =
|
||||
(uint64_t)planned_width * planned_height;
|
||||
const uint32_t min_pixels = v41 ? 295936u : 147456u;
|
||||
if (planned_pixels < min_pixels) {
|
||||
const double scale = sqrt((double)min_pixels / planned_pixels);
|
||||
planned_width = (uint32_t)(planned_width * scale);
|
||||
planned_height = (uint32_t)(planned_height * scale);
|
||||
if (planned_width == 0) planned_width = 1;
|
||||
if (planned_height == 0) planned_height = 1;
|
||||
}
|
||||
|
||||
uint32_t best_width = ds4_align_u32(planned_width, 14u);
|
||||
uint32_t best_height = ds4_align_u32(planned_height, 14u);
|
||||
uint32_t llm_height = 0, llm_width = 0;
|
||||
if (!ds4_deepseek4_safe_resize(planned_height, planned_width,
|
||||
&best_height, &best_width,
|
||||
&llm_height, &llm_width, v41)) {
|
||||
ds4_image_error(error, error_cap,
|
||||
"unable to fit image within the vision token budget");
|
||||
return 0;
|
||||
}
|
||||
if (best_width > DS4_IMAGE_MAX_DIMENSION ||
|
||||
best_height > DS4_IMAGE_MAX_DIMENSION ||
|
||||
(uint64_t)best_width * best_height > DS4_IMAGE_MAX_PIXELS) {
|
||||
ds4_image_error(error, error_cap,
|
||||
"resized image exceeds the decoded image limits");
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t canvas_values = (size_t)best_width * best_height * 3u;
|
||||
float *canvas = malloc(canvas_values * sizeof(*canvas));
|
||||
if (!canvas) {
|
||||
ds4_image_error(error, error_cap, "unable to allocate resized image");
|
||||
return 0;
|
||||
}
|
||||
for (size_t i = 0; i < canvas_values; i++) canvas[i] = 127.0f;
|
||||
|
||||
uint32_t content_width = best_width;
|
||||
uint32_t content_height = best_height;
|
||||
uint32_t offset_x = 0, offset_y = 0;
|
||||
const bool force_resize =
|
||||
!v41 && (uint64_t)image->width >= (uint64_t)image->height * 8u;
|
||||
if (!force_resize) {
|
||||
const double scale = fmin((double)best_width / image->width,
|
||||
(double)best_height / image->height);
|
||||
content_width = (uint32_t)lrint(image->width * scale);
|
||||
content_height = (uint32_t)lrint(image->height * scale);
|
||||
if (content_width < 1) content_width = 1;
|
||||
if (content_height < 1) content_height = 1;
|
||||
if (content_width > best_width) content_width = best_width;
|
||||
if (content_height > best_height) content_height = best_height;
|
||||
offset_x = (uint32_t)lrint((best_width - content_width) * 0.5);
|
||||
offset_y = (uint32_t)lrint((best_height - content_height) * 0.5);
|
||||
}
|
||||
|
||||
float *resized = canvas + ((size_t)offset_y * best_width + offset_x) * 3u;
|
||||
ds4_resize_rgb_bicubic(image->rgb, image->width, image->height,
|
||||
resized, content_width, content_height, best_width);
|
||||
for (size_t i = 0; i < canvas_values; i++) {
|
||||
canvas[i] = canvas[i] / 127.5f - 1.0f;
|
||||
}
|
||||
|
||||
const uint32_t grid_height = best_height / 14u;
|
||||
const uint32_t grid_width = best_width / 14u;
|
||||
const uint32_t patch_count = grid_height * grid_width;
|
||||
const size_t patch_values = (size_t)patch_count * 3u * 14u * 14u;
|
||||
float *patches = malloc(patch_values * sizeof(*patches));
|
||||
if (!patches) {
|
||||
free(canvas);
|
||||
ds4_image_error(error, error_cap, "unable to allocate vision patches");
|
||||
return 0;
|
||||
}
|
||||
size_t index = 0;
|
||||
for (uint32_t patch_y = 0; patch_y < grid_height; patch_y++) {
|
||||
for (uint32_t patch_x = 0; patch_x < grid_width; patch_x++) {
|
||||
for (uint32_t channel = 0; channel < 3u; channel++) {
|
||||
for (uint32_t y = 0; y < 14u; y++) {
|
||||
for (uint32_t x = 0; x < 14u; x++) {
|
||||
const float *pixel = canvas +
|
||||
((size_t)(patch_y * 14u + y) * best_width +
|
||||
patch_x * 14u + x) * 3u;
|
||||
patches[index++] = pixel[channel];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
free(canvas);
|
||||
if (index != patch_values) {
|
||||
free(patches);
|
||||
ds4_image_error(error, error_cap,
|
||||
"internal DeepSeek vision patch layout mismatch");
|
||||
return 0;
|
||||
}
|
||||
|
||||
out->content_width = content_width;
|
||||
out->content_height = content_height;
|
||||
out->padded_width = best_width;
|
||||
out->padded_height = best_height;
|
||||
out->grid_width = grid_width;
|
||||
out->grid_height = grid_height;
|
||||
out->llm_grid_width = llm_width;
|
||||
out->llm_grid_height = llm_height;
|
||||
out->patch_count = patch_count;
|
||||
out->patches = patches;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ds4_image_preprocess_deepseek4(
|
||||
ds4_deepseek4_image_patches *out, const ds4_image *image,
|
||||
char *error, size_t error_cap) {
|
||||
return ds4_image_preprocess_deepseek(out, image, error, error_cap, false);
|
||||
}
|
||||
|
||||
int ds4_image_preprocess_deepseek41(
|
||||
ds4_deepseek4_image_patches *out, const ds4_image *image,
|
||||
char *error, size_t error_cap) {
|
||||
return ds4_image_preprocess_deepseek(out, image, error, error_cap, true);
|
||||
}
|
||||
|
||||
int ds4_deepseek41_image_layout_build(
|
||||
ds4_deepseek4_image_layout *out, uint32_t height, uint32_t width,
|
||||
char *error, size_t error_cap) {
|
||||
if (!out) return 0;
|
||||
memset(out, 0, sizeof(*out));
|
||||
const uint64_t count = (uint64_t)height * ((uint64_t)width + 1u) + 2u;
|
||||
if (!height || !width || count > 1024u) {
|
||||
ds4_image_error(error, error_cap, "invalid V4.1 image token grid");
|
||||
return 0;
|
||||
}
|
||||
uint8_t *types = malloc((size_t)count);
|
||||
uint32_t *perm = malloc((size_t)height * width * sizeof(*perm));
|
||||
if (!types || !perm) {
|
||||
free(types); free(perm);
|
||||
ds4_image_error(error, error_cap, "unable to allocate V4.1 image layout");
|
||||
return 0;
|
||||
}
|
||||
uint32_t token = 0, image = 0;
|
||||
types[token++] = DS4_DEEPSEEK4_IMAGE_START;
|
||||
for (uint32_t row = 0; row < height; row++) {
|
||||
for (uint32_t col = 0; col < width; col++) {
|
||||
types[token++] = DS4_DEEPSEEK4_IMAGE;
|
||||
perm[image] = image;
|
||||
image++;
|
||||
}
|
||||
types[token++] = DS4_DEEPSEEK4_IMAGE_NEWLINE;
|
||||
}
|
||||
types[token++] = DS4_DEEPSEEK4_IMAGE_END;
|
||||
*out = (ds4_deepseek4_image_layout){token, image, types, perm};
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ds4_deepseek4_image_layout_build(
|
||||
ds4_deepseek4_image_layout *out,
|
||||
uint32_t grid_height,
|
||||
uint32_t grid_width,
|
||||
uint32_t start_pos,
|
||||
char *error,
|
||||
size_t error_cap) {
|
||||
if (!out) return 0;
|
||||
memset(out, 0, sizeof(*out));
|
||||
if (grid_height == 0 || grid_width == 0) {
|
||||
ds4_image_error(error, error_cap, "invalid DeepSeek image token grid");
|
||||
return 0;
|
||||
}
|
||||
const uint32_t pad_height = grid_height & 1u;
|
||||
const uint32_t rows = grid_height + pad_height;
|
||||
const uint32_t row_length = grid_width + 1u;
|
||||
const uint32_t compress_pad = 3u - start_pos % 4u;
|
||||
const uint32_t pad_last = (((rows / 2u) * row_length) & 1u) * 2u;
|
||||
const uint64_t body_count64 = (uint64_t)rows * row_length;
|
||||
const uint64_t token_count64 =
|
||||
compress_pad + 1u + body_count64 + pad_last + 1u;
|
||||
const uint64_t image_count64 = (uint64_t)grid_height * grid_width;
|
||||
if (token_count64 > 384u || token_count64 > UINT32_MAX ||
|
||||
image_count64 > UINT32_MAX) {
|
||||
ds4_image_error(error, error_cap,
|
||||
"DeepSeek image token layout exceeds its model limit");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const uint32_t body_count = (uint32_t)body_count64;
|
||||
uint8_t *body_types = malloc(body_count);
|
||||
int32_t *body_images = malloc((size_t)body_count * sizeof(*body_images));
|
||||
uint8_t *types = malloc((size_t)token_count64);
|
||||
uint32_t *perm = malloc((size_t)image_count64 * sizeof(*perm));
|
||||
if (!body_types || !body_images || !types || !perm) {
|
||||
free(body_types);
|
||||
free(body_images);
|
||||
free(types);
|
||||
free(perm);
|
||||
ds4_image_error(error, error_cap,
|
||||
"unable to allocate DeepSeek image token layout");
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (uint32_t row = 0; row < rows; row++) {
|
||||
for (uint32_t column = 0; column < row_length; column++) {
|
||||
const uint32_t index = row * row_length + column;
|
||||
if (row < grid_height && column < grid_width) {
|
||||
body_types[index] = DS4_DEEPSEEK4_IMAGE;
|
||||
body_images[index] = (int32_t)(row * grid_width + column);
|
||||
} else {
|
||||
body_types[index] = row < grid_height
|
||||
? DS4_DEEPSEEK4_IMAGE_NEWLINE
|
||||
: DS4_DEEPSEEK4_IMAGE_PAD;
|
||||
body_images[index] = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t type_pos = 0, perm_pos = 0;
|
||||
for (uint32_t i = 0; i < compress_pad; i++)
|
||||
types[type_pos++] = DS4_DEEPSEEK4_IMAGE_PAD;
|
||||
types[type_pos++] = DS4_DEEPSEEK4_IMAGE_START;
|
||||
for (uint32_t pair = 0; pair < rows / 2u; pair++) {
|
||||
for (uint32_t column = 0; column < row_length; column++) {
|
||||
for (uint32_t row_in_pair = 0; row_in_pair < 2u; row_in_pair++) {
|
||||
const uint32_t source =
|
||||
(pair * 2u + row_in_pair) * row_length + column;
|
||||
types[type_pos++] = body_types[source];
|
||||
if (body_images[source] >= 0)
|
||||
perm[perm_pos++] = (uint32_t)body_images[source];
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < pad_last; i++)
|
||||
types[type_pos++] = DS4_DEEPSEEK4_IMAGE_PAD;
|
||||
types[type_pos++] = DS4_DEEPSEEK4_IMAGE_END;
|
||||
free(body_images);
|
||||
free(body_types);
|
||||
if (type_pos != token_count64 || perm_pos != image_count64) {
|
||||
free(types);
|
||||
free(perm);
|
||||
ds4_image_error(error, error_cap,
|
||||
"internal DeepSeek image token layout mismatch");
|
||||
return 0;
|
||||
}
|
||||
out->token_count = type_pos;
|
||||
out->image_count = perm_pos;
|
||||
out->types = types;
|
||||
out->perm = perm;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ds4_deepseek4_image_patches_free(ds4_deepseek4_image_patches *patches) {
|
||||
if (!patches) return;
|
||||
free(patches->patches);
|
||||
memset(patches, 0, sizeof(*patches));
|
||||
}
|
||||
|
||||
void ds4_deepseek4_image_layout_free(ds4_deepseek4_image_layout *layout) {
|
||||
if (!layout) return;
|
||||
free(layout->types);
|
||||
free(layout->perm);
|
||||
memset(layout, 0, sizeof(*layout));
|
||||
}
|
||||
|
||||
int ds4_deepseek4_next_image_span(
|
||||
const int *tokens,
|
||||
uint32_t token_count,
|
||||
uint32_t vocab_size,
|
||||
uint32_t *cursor,
|
||||
uint32_t *block_start,
|
||||
uint32_t *image_start,
|
||||
uint32_t *image_end) {
|
||||
if (!tokens || !cursor || !block_start || !image_start || !image_end ||
|
||||
vocab_size == 0 || *cursor > token_count) return -1;
|
||||
|
||||
uint32_t i = *cursor;
|
||||
while (i < token_count && tokens[i] >= 0 &&
|
||||
(uint32_t)tokens[i] < vocab_size) i++;
|
||||
if (i == token_count) {
|
||||
*cursor = i;
|
||||
return 0;
|
||||
}
|
||||
if (tokens[i] < 0) return -1;
|
||||
|
||||
const uint32_t block = i;
|
||||
const int pad = (int)vocab_size + DS4_DEEPSEEK4_IMAGE_PAD;
|
||||
while (i < token_count && tokens[i] == pad) i++;
|
||||
if (i == token_count ||
|
||||
tokens[i] != (int)vocab_size + DS4_DEEPSEEK4_IMAGE_START) {
|
||||
return -1;
|
||||
}
|
||||
const uint32_t start = i++;
|
||||
while (i < token_count) {
|
||||
const int token = tokens[i];
|
||||
if (token < (int)vocab_size ||
|
||||
token > (int)vocab_size + DS4_DEEPSEEK4_IMAGE_END ||
|
||||
token == (int)vocab_size + DS4_DEEPSEEK4_IMAGE_START) {
|
||||
return -1;
|
||||
}
|
||||
if (token == (int)vocab_size + DS4_DEEPSEEK4_IMAGE_END) {
|
||||
*block_start = block;
|
||||
*image_start = start;
|
||||
*image_end = i;
|
||||
*cursor = i + 1u;
|
||||
return 1;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ds4_deepseek4_prefill_chunk(
|
||||
const int *tokens,
|
||||
uint32_t token_count,
|
||||
uint32_t vocab_size,
|
||||
uint32_t pos0,
|
||||
uint32_t cap,
|
||||
uint32_t *chunk) {
|
||||
if (!tokens || !chunk || vocab_size == 0 || cap == 0 ||
|
||||
pos0 >= token_count) return 0;
|
||||
uint32_t limit = pos0 +
|
||||
(token_count - pos0 < cap ? token_count - pos0 : cap);
|
||||
uint32_t cursor = pos0;
|
||||
for (;;) {
|
||||
uint32_t block_start, image_start, image_end;
|
||||
const int parsed = ds4_deepseek4_next_image_span(
|
||||
tokens, token_count, vocab_size, &cursor,
|
||||
&block_start, &image_start, &image_end);
|
||||
(void)image_start;
|
||||
if (parsed < 0) return 0;
|
||||
if (parsed == 0 || block_start >= limit) break;
|
||||
const uint32_t after_image = image_end + 1u;
|
||||
if (after_image > limit) {
|
||||
if (after_image - pos0 > cap) {
|
||||
if (block_start == pos0) return 0;
|
||||
limit = block_start;
|
||||
break;
|
||||
}
|
||||
limit = after_image;
|
||||
}
|
||||
}
|
||||
*chunk = limit - pos0;
|
||||
return *chunk != 0;
|
||||
}
|
||||
|
||||
int ds4_deepseek4_attention_bounds(
|
||||
const int *tokens,
|
||||
uint32_t token_count,
|
||||
uint32_t vocab_size,
|
||||
uint32_t pos0,
|
||||
uint32_t n_raw,
|
||||
uint32_t window,
|
||||
uint32_t *bounds) {
|
||||
if (!tokens || !bounds || token_count == 0 || vocab_size == 0 ||
|
||||
pos0 > UINT32_MAX - token_count || n_raw == 0 ||
|
||||
n_raw > pos0 + token_count) return 0;
|
||||
const uint32_t first_raw_pos = pos0 + token_count - n_raw;
|
||||
const uint32_t last_raw_pos = first_raw_pos + n_raw - 1u;
|
||||
for (uint32_t q = 0; q < token_count; q++) {
|
||||
const uint32_t qpos = pos0 + q;
|
||||
uint32_t lo = first_raw_pos;
|
||||
if (window != 0 && qpos + 1u > window) {
|
||||
const uint32_t window_lo = qpos + 1u - window;
|
||||
if (window_lo > lo) lo = window_lo;
|
||||
}
|
||||
bounds[2u * q] = lo;
|
||||
bounds[2u * q + 1u] = qpos < last_raw_pos ? qpos : last_raw_pos;
|
||||
}
|
||||
|
||||
uint32_t cursor = 0;
|
||||
for (;;) {
|
||||
uint32_t block_start, image_start, image_end;
|
||||
const int parsed = ds4_deepseek4_next_image_span(
|
||||
tokens, token_count, vocab_size, &cursor,
|
||||
&block_start, &image_start, &image_end);
|
||||
(void)block_start;
|
||||
if (parsed < 0) return 0;
|
||||
if (parsed == 0) break;
|
||||
for (uint32_t q = image_start; q <= image_end; q++) {
|
||||
const uint32_t qpos = pos0 + q;
|
||||
const uint32_t left = q - image_start < 383u
|
||||
? q - image_start : 383u;
|
||||
const uint32_t right = image_end - q < 384u
|
||||
? image_end - q : 384u;
|
||||
const uint32_t left_add = window != 0 && left >= window
|
||||
? left - (window - 1u) : 0u;
|
||||
uint32_t lo = 0;
|
||||
if (window != 0) {
|
||||
const uint32_t back = window - 1u + left_add;
|
||||
lo = qpos > back ? qpos - back : 0u;
|
||||
}
|
||||
uint32_t hi = qpos + right;
|
||||
if (lo < first_raw_pos) lo = first_raw_pos;
|
||||
if (hi > last_raw_pos) hi = last_raw_pos;
|
||||
bounds[2u * q] = lo;
|
||||
bounds[2u * q + 1u] = hi;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
+102
@@ -4,6 +4,10 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define DS4_IMAGE_MAX_ENCODED_BYTES (64u * 1024u * 1024u)
|
||||
#define DS4_IMAGE_MAX_DIMENSION 16384u
|
||||
#define DS4_IMAGE_MAX_PIXELS (64u * 1024u * 1024u)
|
||||
@@ -27,6 +31,34 @@ typedef struct {
|
||||
float *patches;
|
||||
} ds4_image_patches;
|
||||
|
||||
typedef struct {
|
||||
uint32_t content_width;
|
||||
uint32_t content_height;
|
||||
uint32_t padded_width;
|
||||
uint32_t padded_height;
|
||||
uint32_t grid_width;
|
||||
uint32_t grid_height;
|
||||
uint32_t llm_grid_width;
|
||||
uint32_t llm_grid_height;
|
||||
uint32_t patch_count;
|
||||
float *patches;
|
||||
} ds4_deepseek4_image_patches;
|
||||
|
||||
typedef enum {
|
||||
DS4_DEEPSEEK4_IMAGE_START = 0,
|
||||
DS4_DEEPSEEK4_IMAGE_PAD = 1,
|
||||
DS4_DEEPSEEK4_IMAGE = 2,
|
||||
DS4_DEEPSEEK4_IMAGE_NEWLINE = 3,
|
||||
DS4_DEEPSEEK4_IMAGE_END = 4,
|
||||
} ds4_deepseek4_image_token_type;
|
||||
|
||||
typedef struct {
|
||||
uint32_t token_count;
|
||||
uint32_t image_count;
|
||||
uint8_t *types;
|
||||
uint32_t *perm;
|
||||
} ds4_deepseek4_image_layout;
|
||||
|
||||
int ds4_image_decode_memory(
|
||||
ds4_image *out,
|
||||
const uint8_t *encoded,
|
||||
@@ -50,6 +82,76 @@ int ds4_image_preprocess_glm53(
|
||||
char *error,
|
||||
size_t error_cap);
|
||||
|
||||
/* Qwen3-VL style: resize to multiples of 32 within the token budget, normalize
|
||||
* with mean/std 0.5, emit 3*16*16 patches in 2x2 merge-window order. */
|
||||
int ds4_image_preprocess_qwen4(
|
||||
ds4_image_patches *out,
|
||||
const ds4_image *image,
|
||||
uint32_t min_image_tokens,
|
||||
uint32_t max_image_tokens,
|
||||
char *error,
|
||||
size_t error_cap);
|
||||
|
||||
void ds4_image_patches_free(ds4_image_patches *patches);
|
||||
|
||||
int ds4_image_preprocess_deepseek4(
|
||||
ds4_deepseek4_image_patches *out,
|
||||
const ds4_image *image,
|
||||
char *error,
|
||||
size_t error_cap);
|
||||
|
||||
int ds4_image_preprocess_deepseek41(
|
||||
ds4_deepseek4_image_patches *out, const ds4_image *image,
|
||||
char *error, size_t error_cap);
|
||||
|
||||
int ds4_deepseek41_image_layout_build(
|
||||
ds4_deepseek4_image_layout *out, uint32_t height, uint32_t width,
|
||||
char *error, size_t error_cap);
|
||||
|
||||
int ds4_deepseek4_image_layout_build(
|
||||
ds4_deepseek4_image_layout *out,
|
||||
uint32_t grid_height,
|
||||
uint32_t grid_width,
|
||||
uint32_t start_pos,
|
||||
char *error,
|
||||
size_t error_cap);
|
||||
|
||||
void ds4_deepseek4_image_patches_free(ds4_deepseek4_image_patches *patches);
|
||||
void ds4_deepseek4_image_layout_free(ds4_deepseek4_image_layout *layout);
|
||||
|
||||
/* Find the next DeepSeek image block at or after *cursor. A block includes
|
||||
* the alignment PAD tokens immediately before IMAGE_START. Returns 1 when a
|
||||
* block was found, 0 at end of input, and -1 for malformed synthetic tokens. */
|
||||
int ds4_deepseek4_next_image_span(
|
||||
const int *tokens,
|
||||
uint32_t token_count,
|
||||
uint32_t vocab_size,
|
||||
uint32_t *cursor,
|
||||
uint32_t *block_start,
|
||||
uint32_t *image_start,
|
||||
uint32_t *image_end);
|
||||
|
||||
/* Choose a prefill chunk without splitting a DeepSeek image block. */
|
||||
int ds4_deepseek4_prefill_chunk(
|
||||
const int *tokens,
|
||||
uint32_t token_count,
|
||||
uint32_t vocab_size,
|
||||
uint32_t pos0,
|
||||
uint32_t cap,
|
||||
uint32_t *chunk);
|
||||
|
||||
/* Fill [lo, hi] absolute raw-key bounds for every query row. */
|
||||
int ds4_deepseek4_attention_bounds(
|
||||
const int *tokens,
|
||||
uint32_t token_count,
|
||||
uint32_t vocab_size,
|
||||
uint32_t pos0,
|
||||
uint32_t n_raw,
|
||||
uint32_t window,
|
||||
uint32_t *bounds);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+9
-3
@@ -414,6 +414,12 @@ void ds4_kvstore_fill_header(uint8_t h[DS4_KVSTORE_FIXED_HEADER],
|
||||
kv_le_put64(h + 40, payload_bytes);
|
||||
}
|
||||
|
||||
/* routed-expert quantizations whose checkpoints the store keys on */
|
||||
bool ds4_kvstore_quant_bits_supported(int quant_bits) {
|
||||
return quant_bits == 2 || quant_bits == 4 || quant_bits == 5 ||
|
||||
quant_bits == 6 || quant_bits == 8;
|
||||
}
|
||||
|
||||
bool ds4_kvstore_read_header(FILE *fp, ds4_kvstore_entry *e,
|
||||
uint32_t *text_bytes) {
|
||||
uint8_t h[DS4_KVSTORE_FIXED_HEADER];
|
||||
@@ -436,7 +442,7 @@ bool ds4_kvstore_read_header(FILE *fp, ds4_kvstore_entry *e,
|
||||
if (fread(tb, 1, sizeof(tb), fp) != sizeof(tb)) return false;
|
||||
*text_bytes = ds4_kvstore_le_get32(tb);
|
||||
e->text_bytes = *text_bytes;
|
||||
return e->tokens != 0 && (e->quant_bits == 2 || e->quant_bits == 4);
|
||||
return e->tokens != 0 && ds4_kvstore_quant_bits_supported(e->quant_bits);
|
||||
}
|
||||
|
||||
bool ds4_kvstore_read_entry_file(const char *path, const char sha[41],
|
||||
@@ -940,7 +946,7 @@ bool ds4_kvstore_store_live_prefix_text(ds4_kvstore *kc,
|
||||
ds4_kvstore_tokens_copy_prefix(&store_tokens, tokens, store_len);
|
||||
|
||||
const int quant_bits = ds4_engine_routed_quant_bits(engine);
|
||||
if (quant_bits != 2 && quant_bits != 4) {
|
||||
if (!ds4_kvstore_quant_bits_supported(quant_bits)) {
|
||||
ds4_tokens_free(&store_tokens);
|
||||
return false;
|
||||
}
|
||||
@@ -1224,7 +1230,7 @@ int ds4_kvstore_try_load_text(ds4_kvstore *kc,
|
||||
if (effective_prompt) effective_prompt->len = 0;
|
||||
if (!kc->enabled || !prompt_text) return 0;
|
||||
const int quant_bits = ds4_engine_routed_quant_bits(engine);
|
||||
if (quant_bits != 2 && quant_bits != 4) return 0;
|
||||
if (!ds4_kvstore_quant_bits_supported(quant_bits)) return 0;
|
||||
const int model_id = ds4_engine_model_id(engine);
|
||||
const size_t prompt_bytes = strlen(prompt_text);
|
||||
int idx = ds4_kvstore_find_text_prefix(kc, prompt_text, model_id, quant_bits,
|
||||
|
||||
@@ -214,4 +214,6 @@ char *ds4_kvstore_path_for_sha(ds4_kvstore *kc, const char sha[41]);
|
||||
void ds4_kvstore_le_put32(uint8_t *p, uint32_t v);
|
||||
uint32_t ds4_kvstore_le_get32(const uint8_t *p);
|
||||
|
||||
bool ds4_kvstore_quant_bits_supported(int quant_bits);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef DS4_LINUX_MEMORY_H
|
||||
#define DS4_LINUX_MEMORY_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* CMA pages can back movable allocations, but not ordinary pinned GPU pages. */
|
||||
static inline bool ds4_linux_nonmovable_memory_parse(FILE *fp, uint64_t *bytes) {
|
||||
char line[256];
|
||||
uint64_t available = 0, cma = 0;
|
||||
bool found = false;
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
unsigned long long kib;
|
||||
if (sscanf(line, "MemAvailable: %llu kB", &kib) == 1) {
|
||||
if (kib > UINT64_MAX / 1024u) return false;
|
||||
available = (uint64_t)kib * 1024u;
|
||||
found = true;
|
||||
} else if (sscanf(line, "CmaFree: %llu kB", &kib) == 1) {
|
||||
if (kib > UINT64_MAX / 1024u) return false;
|
||||
cma = (uint64_t)kib * 1024u;
|
||||
}
|
||||
}
|
||||
if (!found || ferror(fp)) return false;
|
||||
*bytes = available > cma ? available - cma : 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool ds4_linux_nonmovable_memory(uint64_t *bytes) {
|
||||
FILE *fp = fopen("/proc/meminfo", "r");
|
||||
if (!fp) return false;
|
||||
const bool ok = ds4_linux_nonmovable_memory_parse(fp, bytes);
|
||||
fclose(fp);
|
||||
return ok;
|
||||
}
|
||||
|
||||
#endif
|
||||
+10442
-742
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,31 @@
|
||||
#ifndef DS4_METAL_DEVICE_H
|
||||
#define DS4_METAL_DEVICE_H
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Share the Qwen M1 Max defaults with M2-M4 while keeping other M1 devices
|
||||
* and newer generations on their existing paths. Bound the generation token
|
||||
* so names such as M20 do not inherit these defaults. */
|
||||
static inline int ds4_metal_device_name_uses_qwen_legacy_tuning(const char *name) {
|
||||
return name && (strcmp(name, "Apple M1 Max") == 0 ||
|
||||
(strncmp(name, "Apple M", 7) == 0 &&
|
||||
name[7] >= '2' && name[7] <= '4' &&
|
||||
(name[8] == '\0' || name[8] == ' ')));
|
||||
}
|
||||
|
||||
/* M3 Ultra already has dedicated MoE defaults; preserve those selections. */
|
||||
static inline int ds4_metal_device_name_uses_qwen_legacy_moe_tuning(const char *name) {
|
||||
return ds4_metal_device_name_uses_qwen_legacy_tuning(name) &&
|
||||
strcmp(name, "Apple M3 Ultra") != 0;
|
||||
}
|
||||
|
||||
/* Share the M5 tuning policy with M6. Keep the generation token bounded so
|
||||
* untested future names such as M50 and M60 do not inherit these defaults.
|
||||
* Callers must still check runtime and kernel capabilities where required. */
|
||||
static inline int ds4_metal_device_name_is_m5_or_m6(const char *name) {
|
||||
return name && strncmp(name, "Apple M", 7) == 0 &&
|
||||
(name[7] == '5' || name[7] == '6') &&
|
||||
(name[8] == '\0' || name[8] == ' ');
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,249 @@
|
||||
#include "ds4_prompt_prefix.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static void prefix_error(char *error, size_t error_cap, const char *fmt, ...) {
|
||||
if (!error || error_cap == 0) return;
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(error, error_cap, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
static bool prefix_marker_at(const char *data, size_t len, size_t pos,
|
||||
ds4_prompt_prefix_role *role,
|
||||
size_t *marker_len) {
|
||||
static const char user[] = "USER:";
|
||||
static const char assistant[] = "ASSISTANT:";
|
||||
if (len - pos >= sizeof(user) - 1 &&
|
||||
memcmp(data + pos, user, sizeof(user) - 1) == 0) {
|
||||
*role = DS4_PROMPT_PREFIX_USER;
|
||||
*marker_len = sizeof(user) - 1;
|
||||
return true;
|
||||
}
|
||||
if (len - pos >= sizeof(assistant) - 1 &&
|
||||
memcmp(data + pos, assistant, sizeof(assistant) - 1) == 0) {
|
||||
*role = DS4_PROMPT_PREFIX_ASSISTANT;
|
||||
*marker_len = sizeof(assistant) - 1;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static char *prefix_copy_content(const char *data, size_t start, size_t end) {
|
||||
size_t len = end - start;
|
||||
char *content = malloc(len + 1);
|
||||
if (!content) return NULL;
|
||||
memcpy(content, data + start, len);
|
||||
content[len] = '\0';
|
||||
return content;
|
||||
}
|
||||
|
||||
void ds4_prompt_prefix_free(ds4_prompt_prefix *prefix) {
|
||||
if (!prefix) return;
|
||||
for (size_t i = 0; i < prefix->count; i++)
|
||||
free(prefix->turns[i].content);
|
||||
free(prefix->turns);
|
||||
memset(prefix, 0, sizeof(*prefix));
|
||||
}
|
||||
|
||||
int ds4_prompt_prefix_parse(ds4_prompt_prefix *out,
|
||||
const char *data,
|
||||
size_t len,
|
||||
char *error,
|
||||
size_t error_cap) {
|
||||
if (!out) {
|
||||
prefix_error(error, error_cap, "invalid prefix output");
|
||||
return -1;
|
||||
}
|
||||
memset(out, 0, sizeof(*out));
|
||||
if (!data || len == 0) {
|
||||
prefix_error(error, error_cap, "prefix file is empty");
|
||||
return -1;
|
||||
}
|
||||
if (memchr(data, '\0', len)) {
|
||||
prefix_error(error, error_cap, "prefix file contains a NUL byte");
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t pos = 0;
|
||||
if (len >= 3 && (unsigned char)data[0] == 0xef &&
|
||||
(unsigned char)data[1] == 0xbb &&
|
||||
(unsigned char)data[2] == 0xbf) {
|
||||
pos = 3;
|
||||
}
|
||||
size_t line = 1;
|
||||
|
||||
while (pos < len) {
|
||||
ds4_prompt_prefix_role role;
|
||||
size_t marker_len = 0;
|
||||
if (!prefix_marker_at(data, len, pos, &role, &marker_len)) {
|
||||
prefix_error(error, error_cap,
|
||||
"line %zu must start with USER: or ASSISTANT:", line);
|
||||
ds4_prompt_prefix_free(out);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const ds4_prompt_prefix_role expected =
|
||||
(out->count % 2 == 0) ? DS4_PROMPT_PREFIX_USER
|
||||
: DS4_PROMPT_PREFIX_ASSISTANT;
|
||||
if (role != expected) {
|
||||
prefix_error(error, error_cap, "line %zu: expected %s:", line,
|
||||
expected == DS4_PROMPT_PREFIX_USER ? "USER"
|
||||
: "ASSISTANT");
|
||||
ds4_prompt_prefix_free(out);
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t content_start = pos + marker_len;
|
||||
if (content_start < len &&
|
||||
(data[content_start] == ' ' || data[content_start] == '\t')) {
|
||||
content_start++;
|
||||
}
|
||||
|
||||
size_t next = len;
|
||||
size_t next_line = line;
|
||||
size_t scan_line = line;
|
||||
for (size_t i = content_start; i < len; i++) {
|
||||
if (data[i] != '\n') continue;
|
||||
scan_line++;
|
||||
ds4_prompt_prefix_role ignored_role;
|
||||
size_t ignored_len;
|
||||
if (i + 1 < len &&
|
||||
prefix_marker_at(data, len, i + 1,
|
||||
&ignored_role, &ignored_len)) {
|
||||
next = i + 1;
|
||||
next_line = scan_line;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
size_t content_end = next;
|
||||
if (content_end > content_start && data[content_end - 1] == '\n') {
|
||||
content_end--;
|
||||
if (content_end > content_start && data[content_end - 1] == '\r')
|
||||
content_end--;
|
||||
}
|
||||
if (content_end == content_start) {
|
||||
prefix_error(error, error_cap, "line %zu has an empty %s turn",
|
||||
line, role == DS4_PROMPT_PREFIX_USER ? "USER"
|
||||
: "ASSISTANT");
|
||||
ds4_prompt_prefix_free(out);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (out->count == SIZE_MAX / sizeof(out->turns[0])) {
|
||||
prefix_error(error, error_cap, "too many turns in prefix file");
|
||||
ds4_prompt_prefix_free(out);
|
||||
return -1;
|
||||
}
|
||||
ds4_prompt_prefix_turn *turns =
|
||||
realloc(out->turns, (out->count + 1) * sizeof(out->turns[0]));
|
||||
if (!turns) {
|
||||
prefix_error(error, error_cap, "out of memory reading prefix file");
|
||||
ds4_prompt_prefix_free(out);
|
||||
return -1;
|
||||
}
|
||||
out->turns = turns;
|
||||
char *content = prefix_copy_content(data, content_start, content_end);
|
||||
if (!content) {
|
||||
prefix_error(error, error_cap, "out of memory reading prefix file");
|
||||
ds4_prompt_prefix_free(out);
|
||||
return -1;
|
||||
}
|
||||
out->turns[out->count++] = (ds4_prompt_prefix_turn) {
|
||||
.role = role,
|
||||
.content = content,
|
||||
};
|
||||
|
||||
if (next == len) {
|
||||
pos = len;
|
||||
} else {
|
||||
pos = next;
|
||||
line = next_line;
|
||||
}
|
||||
}
|
||||
|
||||
if (out->count == 0) {
|
||||
prefix_error(error, error_cap, "prefix file contains no turns");
|
||||
return -1;
|
||||
}
|
||||
if (out->turns[out->count - 1].role != DS4_PROMPT_PREFIX_ASSISTANT) {
|
||||
prefix_error(error, error_cap,
|
||||
"prefix file must end with an ASSISTANT turn");
|
||||
ds4_prompt_prefix_free(out);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ds4_prompt_prefix_load(ds4_prompt_prefix *out,
|
||||
const char *path,
|
||||
char *error,
|
||||
size_t error_cap) {
|
||||
if (!out || !path || !path[0]) {
|
||||
prefix_error(error, error_cap, "invalid prefix file path");
|
||||
return -1;
|
||||
}
|
||||
memset(out, 0, sizeof(*out));
|
||||
|
||||
FILE *fp = fopen(path, "rb");
|
||||
if (!fp) {
|
||||
prefix_error(error, error_cap, "cannot open prefix file %s: %s",
|
||||
path, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (fseek(fp, 0, SEEK_END) != 0) {
|
||||
prefix_error(error, error_cap, "cannot seek prefix file %s: %s",
|
||||
path, strerror(errno));
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
long size = ftell(fp);
|
||||
if (size < 0 || (uintmax_t)size > SIZE_MAX - 1) {
|
||||
prefix_error(error, error_cap, "cannot size prefix file %s", path);
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
if (fseek(fp, 0, SEEK_SET) != 0) {
|
||||
prefix_error(error, error_cap, "cannot rewind prefix file %s: %s",
|
||||
path, strerror(errno));
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t len = (size_t)size;
|
||||
char *data = malloc(len + 1);
|
||||
if (!data) {
|
||||
prefix_error(error, error_cap, "out of memory reading prefix file");
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
if (len && fread(data, 1, len, fp) != len) {
|
||||
prefix_error(error, error_cap, "cannot read prefix file %s", path);
|
||||
free(data);
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
if (fclose(fp) != 0) {
|
||||
prefix_error(error, error_cap, "cannot close prefix file %s: %s",
|
||||
path, strerror(errno));
|
||||
free(data);
|
||||
return -1;
|
||||
}
|
||||
data[len] = '\0';
|
||||
|
||||
int rc = ds4_prompt_prefix_parse(out, data, len, error, error_cap);
|
||||
free(data);
|
||||
if (rc != 0 && error && error_cap && error[0]) {
|
||||
char detail[256];
|
||||
snprintf(detail, sizeof(detail), "%s", error);
|
||||
prefix_error(error, error_cap, "%s: %s", path, detail);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef DS4_PROMPT_PREFIX_H
|
||||
#define DS4_PROMPT_PREFIX_H
|
||||
|
||||
#include "ds4.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
typedef enum {
|
||||
DS4_PROMPT_PREFIX_USER,
|
||||
DS4_PROMPT_PREFIX_ASSISTANT,
|
||||
} ds4_prompt_prefix_role;
|
||||
|
||||
typedef struct {
|
||||
ds4_prompt_prefix_role role;
|
||||
char *content;
|
||||
} ds4_prompt_prefix_turn;
|
||||
|
||||
typedef struct {
|
||||
ds4_prompt_prefix_turn *turns;
|
||||
size_t count;
|
||||
} ds4_prompt_prefix;
|
||||
|
||||
int ds4_prompt_prefix_parse(ds4_prompt_prefix *out,
|
||||
const char *data,
|
||||
size_t len,
|
||||
char *error,
|
||||
size_t error_cap);
|
||||
int ds4_prompt_prefix_load(ds4_prompt_prefix *out,
|
||||
const char *path,
|
||||
char *error,
|
||||
size_t error_cap);
|
||||
void ds4_prompt_prefix_free(ds4_prompt_prefix *prefix);
|
||||
|
||||
static inline void ds4_prompt_prefix_append(ds4_engine *engine,
|
||||
ds4_tokens *tokens,
|
||||
const ds4_prompt_prefix *prefix) {
|
||||
if (!prefix) return;
|
||||
for (size_t i = 0; i < prefix->count; i++) {
|
||||
const ds4_prompt_prefix_turn *turn = &prefix->turns[i];
|
||||
const bool assistant = turn->role == DS4_PROMPT_PREFIX_ASSISTANT;
|
||||
ds4_chat_append_message(engine, tokens,
|
||||
assistant ? "assistant" : "user",
|
||||
turn->content);
|
||||
if (assistant && !ds4_engine_is_glm_dsa(engine))
|
||||
ds4_tokens_push(tokens, ds4_token_eos(engine));
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
+2356
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,310 @@
|
||||
/* Generated by gguf-tools/gen_qwen4_unicode.py from the `regex` module's
|
||||
* Unicode tables (regex 2026.9.3); codepoint classes for the
|
||||
* Qwen3.8 pre-tokenizer. */
|
||||
|
||||
typedef struct { uint32_t lo, hi; } ds4_qwen4_urange;
|
||||
|
||||
static const ds4_qwen4_urange ds4_qwen4_letter_ranges[] = {
|
||||
{0x00041, 0x0005a}, {0x00061, 0x0007a}, {0x000aa, 0x000aa}, {0x000b5, 0x000b5},
|
||||
{0x000ba, 0x000ba}, {0x000c0, 0x000d6}, {0x000d8, 0x000f6}, {0x000f8, 0x002c1},
|
||||
{0x002c6, 0x002d1}, {0x002e0, 0x002e4}, {0x002ec, 0x002ec}, {0x002ee, 0x002ee},
|
||||
{0x00370, 0x00374}, {0x00376, 0x00377}, {0x0037a, 0x0037d}, {0x0037f, 0x0037f},
|
||||
{0x00386, 0x00386}, {0x00388, 0x0038a}, {0x0038c, 0x0038c}, {0x0038e, 0x003a1},
|
||||
{0x003a3, 0x003f5}, {0x003f7, 0x00481}, {0x0048a, 0x0052f}, {0x00531, 0x00556},
|
||||
{0x00559, 0x00559}, {0x00560, 0x00588}, {0x005d0, 0x005ea}, {0x005ef, 0x005f2},
|
||||
{0x00620, 0x0064a}, {0x0066e, 0x0066f}, {0x00671, 0x006d3}, {0x006d5, 0x006d5},
|
||||
{0x006e5, 0x006e6}, {0x006ee, 0x006ef}, {0x006fa, 0x006fc}, {0x006ff, 0x006ff},
|
||||
{0x00710, 0x00710}, {0x00712, 0x0072f}, {0x0074d, 0x007a5}, {0x007b1, 0x007b1},
|
||||
{0x007ca, 0x007ea}, {0x007f4, 0x007f5}, {0x007fa, 0x007fa}, {0x00800, 0x00815},
|
||||
{0x0081a, 0x0081a}, {0x00824, 0x00824}, {0x00828, 0x00828}, {0x00840, 0x00858},
|
||||
{0x00860, 0x0086a}, {0x00870, 0x00887}, {0x00889, 0x0088f}, {0x008a0, 0x008c9},
|
||||
{0x00904, 0x00939}, {0x0093d, 0x0093d}, {0x00950, 0x00950}, {0x00958, 0x00961},
|
||||
{0x00971, 0x00980}, {0x00985, 0x0098c}, {0x0098f, 0x00990}, {0x00993, 0x009a8},
|
||||
{0x009aa, 0x009b0}, {0x009b2, 0x009b2}, {0x009b6, 0x009b9}, {0x009bd, 0x009bd},
|
||||
{0x009ce, 0x009ce}, {0x009dc, 0x009dd}, {0x009df, 0x009e1}, {0x009f0, 0x009f1},
|
||||
{0x009fc, 0x009fc}, {0x00a05, 0x00a0a}, {0x00a0f, 0x00a10}, {0x00a13, 0x00a28},
|
||||
{0x00a2a, 0x00a30}, {0x00a32, 0x00a33}, {0x00a35, 0x00a36}, {0x00a38, 0x00a39},
|
||||
{0x00a59, 0x00a5c}, {0x00a5e, 0x00a5e}, {0x00a72, 0x00a74}, {0x00a85, 0x00a8d},
|
||||
{0x00a8f, 0x00a91}, {0x00a93, 0x00aa8}, {0x00aaa, 0x00ab0}, {0x00ab2, 0x00ab3},
|
||||
{0x00ab5, 0x00ab9}, {0x00abd, 0x00abd}, {0x00ad0, 0x00ad0}, {0x00ae0, 0x00ae1},
|
||||
{0x00af9, 0x00af9}, {0x00b05, 0x00b0c}, {0x00b0f, 0x00b10}, {0x00b13, 0x00b28},
|
||||
{0x00b2a, 0x00b30}, {0x00b32, 0x00b33}, {0x00b35, 0x00b39}, {0x00b3d, 0x00b3d},
|
||||
{0x00b5c, 0x00b5d}, {0x00b5f, 0x00b61}, {0x00b71, 0x00b71}, {0x00b83, 0x00b83},
|
||||
{0x00b85, 0x00b8a}, {0x00b8e, 0x00b90}, {0x00b92, 0x00b95}, {0x00b99, 0x00b9a},
|
||||
{0x00b9c, 0x00b9c}, {0x00b9e, 0x00b9f}, {0x00ba3, 0x00ba4}, {0x00ba8, 0x00baa},
|
||||
{0x00bae, 0x00bb9}, {0x00bd0, 0x00bd0}, {0x00c05, 0x00c0c}, {0x00c0e, 0x00c10},
|
||||
{0x00c12, 0x00c28}, {0x00c2a, 0x00c39}, {0x00c3d, 0x00c3d}, {0x00c58, 0x00c5a},
|
||||
{0x00c5c, 0x00c5d}, {0x00c60, 0x00c61}, {0x00c80, 0x00c80}, {0x00c85, 0x00c8c},
|
||||
{0x00c8e, 0x00c90}, {0x00c92, 0x00ca8}, {0x00caa, 0x00cb3}, {0x00cb5, 0x00cb9},
|
||||
{0x00cbd, 0x00cbd}, {0x00cdc, 0x00cde}, {0x00ce0, 0x00ce1}, {0x00cf1, 0x00cf2},
|
||||
{0x00d04, 0x00d0c}, {0x00d0e, 0x00d10}, {0x00d12, 0x00d3a}, {0x00d3d, 0x00d3d},
|
||||
{0x00d4e, 0x00d4e}, {0x00d54, 0x00d56}, {0x00d5f, 0x00d61}, {0x00d7a, 0x00d7f},
|
||||
{0x00d85, 0x00d96}, {0x00d9a, 0x00db1}, {0x00db3, 0x00dbb}, {0x00dbd, 0x00dbd},
|
||||
{0x00dc0, 0x00dc6}, {0x00e01, 0x00e30}, {0x00e32, 0x00e33}, {0x00e40, 0x00e46},
|
||||
{0x00e81, 0x00e82}, {0x00e84, 0x00e84}, {0x00e86, 0x00e8a}, {0x00e8c, 0x00ea3},
|
||||
{0x00ea5, 0x00ea5}, {0x00ea7, 0x00eb0}, {0x00eb2, 0x00eb3}, {0x00ebd, 0x00ebd},
|
||||
{0x00ec0, 0x00ec4}, {0x00ec6, 0x00ec6}, {0x00edc, 0x00edf}, {0x00f00, 0x00f00},
|
||||
{0x00f40, 0x00f47}, {0x00f49, 0x00f6c}, {0x00f88, 0x00f8c}, {0x01000, 0x0102a},
|
||||
{0x0103f, 0x0103f}, {0x01050, 0x01055}, {0x0105a, 0x0105d}, {0x01061, 0x01061},
|
||||
{0x01065, 0x01066}, {0x0106e, 0x01070}, {0x01075, 0x01081}, {0x0108e, 0x0108e},
|
||||
{0x010a0, 0x010c5}, {0x010c7, 0x010c7}, {0x010cd, 0x010cd}, {0x010d0, 0x010fa},
|
||||
{0x010fc, 0x01248}, {0x0124a, 0x0124d}, {0x01250, 0x01256}, {0x01258, 0x01258},
|
||||
{0x0125a, 0x0125d}, {0x01260, 0x01288}, {0x0128a, 0x0128d}, {0x01290, 0x012b0},
|
||||
{0x012b2, 0x012b5}, {0x012b8, 0x012be}, {0x012c0, 0x012c0}, {0x012c2, 0x012c5},
|
||||
{0x012c8, 0x012d6}, {0x012d8, 0x01310}, {0x01312, 0x01315}, {0x01318, 0x0135a},
|
||||
{0x01380, 0x0138f}, {0x013a0, 0x013f5}, {0x013f8, 0x013fd}, {0x01401, 0x0166c},
|
||||
{0x0166f, 0x0167f}, {0x01681, 0x0169a}, {0x016a0, 0x016ea}, {0x016f1, 0x016f8},
|
||||
{0x01700, 0x01711}, {0x0171f, 0x01731}, {0x01740, 0x01751}, {0x01760, 0x0176c},
|
||||
{0x0176e, 0x01770}, {0x01780, 0x017b3}, {0x017d7, 0x017d7}, {0x017dc, 0x017dc},
|
||||
{0x01820, 0x01878}, {0x01880, 0x01884}, {0x01887, 0x018a8}, {0x018aa, 0x018aa},
|
||||
{0x018b0, 0x018f5}, {0x01900, 0x0191e}, {0x01950, 0x0196d}, {0x01970, 0x01974},
|
||||
{0x01980, 0x019ab}, {0x019b0, 0x019c9}, {0x01a00, 0x01a16}, {0x01a20, 0x01a54},
|
||||
{0x01aa7, 0x01aa7}, {0x01b05, 0x01b33}, {0x01b45, 0x01b4c}, {0x01b83, 0x01ba0},
|
||||
{0x01bae, 0x01baf}, {0x01bba, 0x01be5}, {0x01c00, 0x01c23}, {0x01c4d, 0x01c4f},
|
||||
{0x01c5a, 0x01c7d}, {0x01c80, 0x01c8a}, {0x01c90, 0x01cba}, {0x01cbd, 0x01cbf},
|
||||
{0x01ce9, 0x01cec}, {0x01cee, 0x01cf3}, {0x01cf5, 0x01cf6}, {0x01cfa, 0x01cfa},
|
||||
{0x01d00, 0x01dbf}, {0x01e00, 0x01f15}, {0x01f18, 0x01f1d}, {0x01f20, 0x01f45},
|
||||
{0x01f48, 0x01f4d}, {0x01f50, 0x01f57}, {0x01f59, 0x01f59}, {0x01f5b, 0x01f5b},
|
||||
{0x01f5d, 0x01f5d}, {0x01f5f, 0x01f7d}, {0x01f80, 0x01fb4}, {0x01fb6, 0x01fbc},
|
||||
{0x01fbe, 0x01fbe}, {0x01fc2, 0x01fc4}, {0x01fc6, 0x01fcc}, {0x01fd0, 0x01fd3},
|
||||
{0x01fd6, 0x01fdb}, {0x01fe0, 0x01fec}, {0x01ff2, 0x01ff4}, {0x01ff6, 0x01ffc},
|
||||
{0x02071, 0x02071}, {0x0207f, 0x0207f}, {0x02090, 0x0209c}, {0x02102, 0x02102},
|
||||
{0x02107, 0x02107}, {0x0210a, 0x02113}, {0x02115, 0x02115}, {0x02119, 0x0211d},
|
||||
{0x02124, 0x02124}, {0x02126, 0x02126}, {0x02128, 0x02128}, {0x0212a, 0x0212d},
|
||||
{0x0212f, 0x02139}, {0x0213c, 0x0213f}, {0x02145, 0x02149}, {0x0214e, 0x0214e},
|
||||
{0x02183, 0x02184}, {0x02c00, 0x02ce4}, {0x02ceb, 0x02cee}, {0x02cf2, 0x02cf3},
|
||||
{0x02d00, 0x02d25}, {0x02d27, 0x02d27}, {0x02d2d, 0x02d2d}, {0x02d30, 0x02d67},
|
||||
{0x02d6f, 0x02d6f}, {0x02d80, 0x02d96}, {0x02da0, 0x02da6}, {0x02da8, 0x02dae},
|
||||
{0x02db0, 0x02db6}, {0x02db8, 0x02dbe}, {0x02dc0, 0x02dc6}, {0x02dc8, 0x02dce},
|
||||
{0x02dd0, 0x02dd6}, {0x02dd8, 0x02dde}, {0x02e2f, 0x02e2f}, {0x03005, 0x03006},
|
||||
{0x03031, 0x03035}, {0x0303b, 0x0303c}, {0x03041, 0x03096}, {0x0309d, 0x0309f},
|
||||
{0x030a1, 0x030fa}, {0x030fc, 0x030ff}, {0x03105, 0x0312f}, {0x03131, 0x0318e},
|
||||
{0x031a0, 0x031bf}, {0x031f0, 0x031ff}, {0x03400, 0x04dbf}, {0x04e00, 0x0a48c},
|
||||
{0x0a4d0, 0x0a4fd}, {0x0a500, 0x0a60c}, {0x0a610, 0x0a61f}, {0x0a62a, 0x0a62b},
|
||||
{0x0a640, 0x0a66e}, {0x0a67f, 0x0a69d}, {0x0a6a0, 0x0a6e5}, {0x0a717, 0x0a71f},
|
||||
{0x0a722, 0x0a788}, {0x0a78b, 0x0a7dc}, {0x0a7f1, 0x0a801}, {0x0a803, 0x0a805},
|
||||
{0x0a807, 0x0a80a}, {0x0a80c, 0x0a822}, {0x0a840, 0x0a873}, {0x0a882, 0x0a8b3},
|
||||
{0x0a8f2, 0x0a8f7}, {0x0a8fb, 0x0a8fb}, {0x0a8fd, 0x0a8fe}, {0x0a90a, 0x0a925},
|
||||
{0x0a930, 0x0a946}, {0x0a960, 0x0a97c}, {0x0a984, 0x0a9b2}, {0x0a9cf, 0x0a9cf},
|
||||
{0x0a9e0, 0x0a9e4}, {0x0a9e6, 0x0a9ef}, {0x0a9fa, 0x0a9fe}, {0x0aa00, 0x0aa28},
|
||||
{0x0aa40, 0x0aa42}, {0x0aa44, 0x0aa4b}, {0x0aa60, 0x0aa76}, {0x0aa7a, 0x0aa7a},
|
||||
{0x0aa7e, 0x0aaaf}, {0x0aab1, 0x0aab1}, {0x0aab5, 0x0aab6}, {0x0aab9, 0x0aabd},
|
||||
{0x0aac0, 0x0aac0}, {0x0aac2, 0x0aac2}, {0x0aadb, 0x0aadd}, {0x0aae0, 0x0aaea},
|
||||
{0x0aaf2, 0x0aaf4}, {0x0ab01, 0x0ab06}, {0x0ab09, 0x0ab0e}, {0x0ab11, 0x0ab16},
|
||||
{0x0ab20, 0x0ab26}, {0x0ab28, 0x0ab2e}, {0x0ab30, 0x0ab5a}, {0x0ab5c, 0x0ab69},
|
||||
{0x0ab70, 0x0abe2}, {0x0ac00, 0x0d7a3}, {0x0d7b0, 0x0d7c6}, {0x0d7cb, 0x0d7fb},
|
||||
{0x0f900, 0x0fa6d}, {0x0fa70, 0x0fad9}, {0x0fb00, 0x0fb06}, {0x0fb13, 0x0fb17},
|
||||
{0x0fb1d, 0x0fb1d}, {0x0fb1f, 0x0fb28}, {0x0fb2a, 0x0fb36}, {0x0fb38, 0x0fb3c},
|
||||
{0x0fb3e, 0x0fb3e}, {0x0fb40, 0x0fb41}, {0x0fb43, 0x0fb44}, {0x0fb46, 0x0fbb1},
|
||||
{0x0fbd3, 0x0fd3d}, {0x0fd50, 0x0fd8f}, {0x0fd92, 0x0fdc7}, {0x0fdf0, 0x0fdfb},
|
||||
{0x0fe70, 0x0fe74}, {0x0fe76, 0x0fefc}, {0x0ff21, 0x0ff3a}, {0x0ff41, 0x0ff5a},
|
||||
{0x0ff66, 0x0ffbe}, {0x0ffc2, 0x0ffc7}, {0x0ffca, 0x0ffcf}, {0x0ffd2, 0x0ffd7},
|
||||
{0x0ffda, 0x0ffdc}, {0x10000, 0x1000b}, {0x1000d, 0x10026}, {0x10028, 0x1003a},
|
||||
{0x1003c, 0x1003d}, {0x1003f, 0x1004d}, {0x10050, 0x1005d}, {0x10080, 0x100fa},
|
||||
{0x10280, 0x1029c}, {0x102a0, 0x102d0}, {0x10300, 0x1031f}, {0x1032d, 0x10340},
|
||||
{0x10342, 0x10349}, {0x10350, 0x10375}, {0x10380, 0x1039d}, {0x103a0, 0x103c3},
|
||||
{0x103c8, 0x103cf}, {0x10400, 0x1049d}, {0x104b0, 0x104d3}, {0x104d8, 0x104fb},
|
||||
{0x10500, 0x10527}, {0x10530, 0x10563}, {0x10570, 0x1057a}, {0x1057c, 0x1058a},
|
||||
{0x1058c, 0x10592}, {0x10594, 0x10595}, {0x10597, 0x105a1}, {0x105a3, 0x105b1},
|
||||
{0x105b3, 0x105b9}, {0x105bb, 0x105bc}, {0x105c0, 0x105f3}, {0x10600, 0x10736},
|
||||
{0x10740, 0x10755}, {0x10760, 0x10767}, {0x10780, 0x10785}, {0x10787, 0x107b0},
|
||||
{0x107b2, 0x107ba}, {0x10800, 0x10805}, {0x10808, 0x10808}, {0x1080a, 0x10835},
|
||||
{0x10837, 0x10838}, {0x1083c, 0x1083c}, {0x1083f, 0x10855}, {0x10860, 0x10876},
|
||||
{0x10880, 0x1089e}, {0x108e0, 0x108f2}, {0x108f4, 0x108f5}, {0x10900, 0x10915},
|
||||
{0x10920, 0x10939}, {0x10940, 0x10959}, {0x10980, 0x109b7}, {0x109be, 0x109bf},
|
||||
{0x10a00, 0x10a00}, {0x10a10, 0x10a13}, {0x10a15, 0x10a17}, {0x10a19, 0x10a35},
|
||||
{0x10a60, 0x10a7c}, {0x10a80, 0x10a9c}, {0x10ac0, 0x10ac7}, {0x10ac9, 0x10ae4},
|
||||
{0x10b00, 0x10b35}, {0x10b40, 0x10b55}, {0x10b60, 0x10b72}, {0x10b80, 0x10b91},
|
||||
{0x10c00, 0x10c48}, {0x10c80, 0x10cb2}, {0x10cc0, 0x10cf2}, {0x10d00, 0x10d23},
|
||||
{0x10d4a, 0x10d65}, {0x10d6f, 0x10d85}, {0x10e80, 0x10ea9}, {0x10eb0, 0x10eb1},
|
||||
{0x10ec2, 0x10ec7}, {0x10f00, 0x10f1c}, {0x10f27, 0x10f27}, {0x10f30, 0x10f45},
|
||||
{0x10f70, 0x10f81}, {0x10fb0, 0x10fc4}, {0x10fe0, 0x10ff6}, {0x11003, 0x11037},
|
||||
{0x11071, 0x11072}, {0x11075, 0x11075}, {0x11083, 0x110af}, {0x110d0, 0x110e8},
|
||||
{0x11103, 0x11126}, {0x11144, 0x11144}, {0x11147, 0x11147}, {0x11150, 0x11172},
|
||||
{0x11176, 0x11176}, {0x11183, 0x111b2}, {0x111c1, 0x111c4}, {0x111da, 0x111da},
|
||||
{0x111dc, 0x111dc}, {0x11200, 0x11211}, {0x11213, 0x1122b}, {0x1123f, 0x11240},
|
||||
{0x11280, 0x11286}, {0x11288, 0x11288}, {0x1128a, 0x1128d}, {0x1128f, 0x1129d},
|
||||
{0x1129f, 0x112a8}, {0x112b0, 0x112de}, {0x11305, 0x1130c}, {0x1130f, 0x11310},
|
||||
{0x11313, 0x11328}, {0x1132a, 0x11330}, {0x11332, 0x11333}, {0x11335, 0x11339},
|
||||
{0x1133d, 0x1133d}, {0x11350, 0x11350}, {0x1135d, 0x11361}, {0x11380, 0x11389},
|
||||
{0x1138b, 0x1138b}, {0x1138e, 0x1138e}, {0x11390, 0x113b5}, {0x113b7, 0x113b7},
|
||||
{0x113d1, 0x113d1}, {0x113d3, 0x113d3}, {0x11400, 0x11434}, {0x11447, 0x1144a},
|
||||
{0x1145f, 0x11461}, {0x11480, 0x114af}, {0x114c4, 0x114c5}, {0x114c7, 0x114c7},
|
||||
{0x11580, 0x115ae}, {0x115d8, 0x115db}, {0x11600, 0x1162f}, {0x11644, 0x11644},
|
||||
{0x11680, 0x116aa}, {0x116b8, 0x116b8}, {0x11700, 0x1171a}, {0x11740, 0x11746},
|
||||
{0x11800, 0x1182b}, {0x118a0, 0x118df}, {0x118ff, 0x11906}, {0x11909, 0x11909},
|
||||
{0x1190c, 0x11913}, {0x11915, 0x11916}, {0x11918, 0x1192f}, {0x1193f, 0x1193f},
|
||||
{0x11941, 0x11941}, {0x119a0, 0x119a7}, {0x119aa, 0x119d0}, {0x119e1, 0x119e1},
|
||||
{0x119e3, 0x119e3}, {0x11a00, 0x11a00}, {0x11a0b, 0x11a32}, {0x11a3a, 0x11a3a},
|
||||
{0x11a50, 0x11a50}, {0x11a5c, 0x11a89}, {0x11a9d, 0x11a9d}, {0x11ab0, 0x11af8},
|
||||
{0x11bc0, 0x11be0}, {0x11c00, 0x11c08}, {0x11c0a, 0x11c2e}, {0x11c40, 0x11c40},
|
||||
{0x11c72, 0x11c8f}, {0x11d00, 0x11d06}, {0x11d08, 0x11d09}, {0x11d0b, 0x11d30},
|
||||
{0x11d46, 0x11d46}, {0x11d60, 0x11d65}, {0x11d67, 0x11d68}, {0x11d6a, 0x11d89},
|
||||
{0x11d98, 0x11d98}, {0x11db0, 0x11ddb}, {0x11ee0, 0x11ef2}, {0x11f02, 0x11f02},
|
||||
{0x11f04, 0x11f10}, {0x11f12, 0x11f33}, {0x11fb0, 0x11fb0}, {0x12000, 0x12399},
|
||||
{0x12480, 0x12543}, {0x12f90, 0x12ff0}, {0x13000, 0x1342f}, {0x13441, 0x13446},
|
||||
{0x13460, 0x143fa}, {0x14400, 0x14646}, {0x16100, 0x1611d}, {0x16800, 0x16a38},
|
||||
{0x16a40, 0x16a5e}, {0x16a70, 0x16abe}, {0x16ad0, 0x16aed}, {0x16b00, 0x16b2f},
|
||||
{0x16b40, 0x16b43}, {0x16b63, 0x16b77}, {0x16b7d, 0x16b8f}, {0x16d40, 0x16d6c},
|
||||
{0x16e40, 0x16e7f}, {0x16ea0, 0x16eb8}, {0x16ebb, 0x16ed3}, {0x16f00, 0x16f4a},
|
||||
{0x16f50, 0x16f50}, {0x16f93, 0x16f9f}, {0x16fe0, 0x16fe1}, {0x16fe3, 0x16fe3},
|
||||
{0x16ff2, 0x16ff3}, {0x17000, 0x18cd5}, {0x18cff, 0x18d1e}, {0x18d80, 0x18df2},
|
||||
{0x1aff0, 0x1aff3}, {0x1aff5, 0x1affb}, {0x1affd, 0x1affe}, {0x1b000, 0x1b122},
|
||||
{0x1b132, 0x1b132}, {0x1b150, 0x1b152}, {0x1b155, 0x1b155}, {0x1b164, 0x1b167},
|
||||
{0x1b170, 0x1b2fb}, {0x1bc00, 0x1bc6a}, {0x1bc70, 0x1bc7c}, {0x1bc80, 0x1bc88},
|
||||
{0x1bc90, 0x1bc99}, {0x1d400, 0x1d454}, {0x1d456, 0x1d49c}, {0x1d49e, 0x1d49f},
|
||||
{0x1d4a2, 0x1d4a2}, {0x1d4a5, 0x1d4a6}, {0x1d4a9, 0x1d4ac}, {0x1d4ae, 0x1d4b9},
|
||||
{0x1d4bb, 0x1d4bb}, {0x1d4bd, 0x1d4c3}, {0x1d4c5, 0x1d505}, {0x1d507, 0x1d50a},
|
||||
{0x1d50d, 0x1d514}, {0x1d516, 0x1d51c}, {0x1d51e, 0x1d539}, {0x1d53b, 0x1d53e},
|
||||
{0x1d540, 0x1d544}, {0x1d546, 0x1d546}, {0x1d54a, 0x1d550}, {0x1d552, 0x1d6a5},
|
||||
{0x1d6a8, 0x1d6c0}, {0x1d6c2, 0x1d6da}, {0x1d6dc, 0x1d6fa}, {0x1d6fc, 0x1d714},
|
||||
{0x1d716, 0x1d734}, {0x1d736, 0x1d74e}, {0x1d750, 0x1d76e}, {0x1d770, 0x1d788},
|
||||
{0x1d78a, 0x1d7a8}, {0x1d7aa, 0x1d7c2}, {0x1d7c4, 0x1d7cb}, {0x1df00, 0x1df1e},
|
||||
{0x1df25, 0x1df2a}, {0x1e030, 0x1e06d}, {0x1e100, 0x1e12c}, {0x1e137, 0x1e13d},
|
||||
{0x1e14e, 0x1e14e}, {0x1e290, 0x1e2ad}, {0x1e2c0, 0x1e2eb}, {0x1e4d0, 0x1e4eb},
|
||||
{0x1e5d0, 0x1e5ed}, {0x1e5f0, 0x1e5f0}, {0x1e6c0, 0x1e6de}, {0x1e6e0, 0x1e6e2},
|
||||
{0x1e6e4, 0x1e6e5}, {0x1e6e7, 0x1e6ed}, {0x1e6f0, 0x1e6f4}, {0x1e6fe, 0x1e6ff},
|
||||
{0x1e7e0, 0x1e7e6}, {0x1e7e8, 0x1e7eb}, {0x1e7ed, 0x1e7ee}, {0x1e7f0, 0x1e7fe},
|
||||
{0x1e800, 0x1e8c4}, {0x1e900, 0x1e943}, {0x1e94b, 0x1e94b}, {0x1ee00, 0x1ee03},
|
||||
{0x1ee05, 0x1ee1f}, {0x1ee21, 0x1ee22}, {0x1ee24, 0x1ee24}, {0x1ee27, 0x1ee27},
|
||||
{0x1ee29, 0x1ee32}, {0x1ee34, 0x1ee37}, {0x1ee39, 0x1ee39}, {0x1ee3b, 0x1ee3b},
|
||||
{0x1ee42, 0x1ee42}, {0x1ee47, 0x1ee47}, {0x1ee49, 0x1ee49}, {0x1ee4b, 0x1ee4b},
|
||||
{0x1ee4d, 0x1ee4f}, {0x1ee51, 0x1ee52}, {0x1ee54, 0x1ee54}, {0x1ee57, 0x1ee57},
|
||||
{0x1ee59, 0x1ee59}, {0x1ee5b, 0x1ee5b}, {0x1ee5d, 0x1ee5d}, {0x1ee5f, 0x1ee5f},
|
||||
{0x1ee61, 0x1ee62}, {0x1ee64, 0x1ee64}, {0x1ee67, 0x1ee6a}, {0x1ee6c, 0x1ee72},
|
||||
{0x1ee74, 0x1ee77}, {0x1ee79, 0x1ee7c}, {0x1ee7e, 0x1ee7e}, {0x1ee80, 0x1ee89},
|
||||
{0x1ee8b, 0x1ee9b}, {0x1eea1, 0x1eea3}, {0x1eea5, 0x1eea9}, {0x1eeab, 0x1eebb},
|
||||
{0x20000, 0x2a6df}, {0x2a700, 0x2b81d}, {0x2b820, 0x2cead}, {0x2ceb0, 0x2ebe0},
|
||||
{0x2ebf0, 0x2ee5d}, {0x2f800, 0x2fa1d}, {0x30000, 0x3134a}, {0x31350, 0x33479},
|
||||
};
|
||||
|
||||
static const ds4_qwen4_urange ds4_qwen4_mark_ranges[] = {
|
||||
{0x00300, 0x0036f}, {0x00483, 0x00489}, {0x00591, 0x005bd}, {0x005bf, 0x005bf},
|
||||
{0x005c1, 0x005c2}, {0x005c4, 0x005c5}, {0x005c7, 0x005c7}, {0x00610, 0x0061a},
|
||||
{0x0064b, 0x0065f}, {0x00670, 0x00670}, {0x006d6, 0x006dc}, {0x006df, 0x006e4},
|
||||
{0x006e7, 0x006e8}, {0x006ea, 0x006ed}, {0x00711, 0x00711}, {0x00730, 0x0074a},
|
||||
{0x007a6, 0x007b0}, {0x007eb, 0x007f3}, {0x007fd, 0x007fd}, {0x00816, 0x00819},
|
||||
{0x0081b, 0x00823}, {0x00825, 0x00827}, {0x00829, 0x0082d}, {0x00859, 0x0085b},
|
||||
{0x00897, 0x0089f}, {0x008ca, 0x008e1}, {0x008e3, 0x00903}, {0x0093a, 0x0093c},
|
||||
{0x0093e, 0x0094f}, {0x00951, 0x00957}, {0x00962, 0x00963}, {0x00981, 0x00983},
|
||||
{0x009bc, 0x009bc}, {0x009be, 0x009c4}, {0x009c7, 0x009c8}, {0x009cb, 0x009cd},
|
||||
{0x009d7, 0x009d7}, {0x009e2, 0x009e3}, {0x009fe, 0x009fe}, {0x00a01, 0x00a03},
|
||||
{0x00a3c, 0x00a3c}, {0x00a3e, 0x00a42}, {0x00a47, 0x00a48}, {0x00a4b, 0x00a4d},
|
||||
{0x00a51, 0x00a51}, {0x00a70, 0x00a71}, {0x00a75, 0x00a75}, {0x00a81, 0x00a83},
|
||||
{0x00abc, 0x00abc}, {0x00abe, 0x00ac5}, {0x00ac7, 0x00ac9}, {0x00acb, 0x00acd},
|
||||
{0x00ae2, 0x00ae3}, {0x00afa, 0x00aff}, {0x00b01, 0x00b03}, {0x00b3c, 0x00b3c},
|
||||
{0x00b3e, 0x00b44}, {0x00b47, 0x00b48}, {0x00b4b, 0x00b4d}, {0x00b55, 0x00b57},
|
||||
{0x00b62, 0x00b63}, {0x00b82, 0x00b82}, {0x00bbe, 0x00bc2}, {0x00bc6, 0x00bc8},
|
||||
{0x00bca, 0x00bcd}, {0x00bd7, 0x00bd7}, {0x00c00, 0x00c04}, {0x00c3c, 0x00c3c},
|
||||
{0x00c3e, 0x00c44}, {0x00c46, 0x00c48}, {0x00c4a, 0x00c4d}, {0x00c55, 0x00c56},
|
||||
{0x00c62, 0x00c63}, {0x00c81, 0x00c83}, {0x00cbc, 0x00cbc}, {0x00cbe, 0x00cc4},
|
||||
{0x00cc6, 0x00cc8}, {0x00cca, 0x00ccd}, {0x00cd5, 0x00cd6}, {0x00ce2, 0x00ce3},
|
||||
{0x00cf3, 0x00cf3}, {0x00d00, 0x00d03}, {0x00d3b, 0x00d3c}, {0x00d3e, 0x00d44},
|
||||
{0x00d46, 0x00d48}, {0x00d4a, 0x00d4d}, {0x00d57, 0x00d57}, {0x00d62, 0x00d63},
|
||||
{0x00d81, 0x00d83}, {0x00dca, 0x00dca}, {0x00dcf, 0x00dd4}, {0x00dd6, 0x00dd6},
|
||||
{0x00dd8, 0x00ddf}, {0x00df2, 0x00df3}, {0x00e31, 0x00e31}, {0x00e34, 0x00e3a},
|
||||
{0x00e47, 0x00e4e}, {0x00eb1, 0x00eb1}, {0x00eb4, 0x00ebc}, {0x00ec8, 0x00ece},
|
||||
{0x00f18, 0x00f19}, {0x00f35, 0x00f35}, {0x00f37, 0x00f37}, {0x00f39, 0x00f39},
|
||||
{0x00f3e, 0x00f3f}, {0x00f71, 0x00f84}, {0x00f86, 0x00f87}, {0x00f8d, 0x00f97},
|
||||
{0x00f99, 0x00fbc}, {0x00fc6, 0x00fc6}, {0x0102b, 0x0103e}, {0x01056, 0x01059},
|
||||
{0x0105e, 0x01060}, {0x01062, 0x01064}, {0x01067, 0x0106d}, {0x01071, 0x01074},
|
||||
{0x01082, 0x0108d}, {0x0108f, 0x0108f}, {0x0109a, 0x0109d}, {0x0135d, 0x0135f},
|
||||
{0x01712, 0x01715}, {0x01732, 0x01734}, {0x01752, 0x01753}, {0x01772, 0x01773},
|
||||
{0x017b4, 0x017d3}, {0x017dd, 0x017dd}, {0x0180b, 0x0180d}, {0x0180f, 0x0180f},
|
||||
{0x01885, 0x01886}, {0x018a9, 0x018a9}, {0x01920, 0x0192b}, {0x01930, 0x0193b},
|
||||
{0x01a17, 0x01a1b}, {0x01a55, 0x01a5e}, {0x01a60, 0x01a7c}, {0x01a7f, 0x01a7f},
|
||||
{0x01ab0, 0x01add}, {0x01ae0, 0x01aeb}, {0x01b00, 0x01b04}, {0x01b34, 0x01b44},
|
||||
{0x01b6b, 0x01b73}, {0x01b80, 0x01b82}, {0x01ba1, 0x01bad}, {0x01be6, 0x01bf3},
|
||||
{0x01c24, 0x01c37}, {0x01cd0, 0x01cd2}, {0x01cd4, 0x01ce8}, {0x01ced, 0x01ced},
|
||||
{0x01cf4, 0x01cf4}, {0x01cf7, 0x01cf9}, {0x01dc0, 0x01dff}, {0x020d0, 0x020f0},
|
||||
{0x02cef, 0x02cf1}, {0x02d7f, 0x02d7f}, {0x02de0, 0x02dff}, {0x0302a, 0x0302f},
|
||||
{0x03099, 0x0309a}, {0x0a66f, 0x0a672}, {0x0a674, 0x0a67d}, {0x0a69e, 0x0a69f},
|
||||
{0x0a6f0, 0x0a6f1}, {0x0a802, 0x0a802}, {0x0a806, 0x0a806}, {0x0a80b, 0x0a80b},
|
||||
{0x0a823, 0x0a827}, {0x0a82c, 0x0a82c}, {0x0a880, 0x0a881}, {0x0a8b4, 0x0a8c5},
|
||||
{0x0a8e0, 0x0a8f1}, {0x0a8ff, 0x0a8ff}, {0x0a926, 0x0a92d}, {0x0a947, 0x0a953},
|
||||
{0x0a980, 0x0a983}, {0x0a9b3, 0x0a9c0}, {0x0a9e5, 0x0a9e5}, {0x0aa29, 0x0aa36},
|
||||
{0x0aa43, 0x0aa43}, {0x0aa4c, 0x0aa4d}, {0x0aa7b, 0x0aa7d}, {0x0aab0, 0x0aab0},
|
||||
{0x0aab2, 0x0aab4}, {0x0aab7, 0x0aab8}, {0x0aabe, 0x0aabf}, {0x0aac1, 0x0aac1},
|
||||
{0x0aaeb, 0x0aaef}, {0x0aaf5, 0x0aaf6}, {0x0abe3, 0x0abea}, {0x0abec, 0x0abed},
|
||||
{0x0fb1e, 0x0fb1e}, {0x0fe00, 0x0fe0f}, {0x0fe20, 0x0fe2f}, {0x101fd, 0x101fd},
|
||||
{0x102e0, 0x102e0}, {0x10376, 0x1037a}, {0x10a01, 0x10a03}, {0x10a05, 0x10a06},
|
||||
{0x10a0c, 0x10a0f}, {0x10a38, 0x10a3a}, {0x10a3f, 0x10a3f}, {0x10ae5, 0x10ae6},
|
||||
{0x10d24, 0x10d27}, {0x10d69, 0x10d6d}, {0x10eab, 0x10eac}, {0x10efa, 0x10eff},
|
||||
{0x10f46, 0x10f50}, {0x10f82, 0x10f85}, {0x11000, 0x11002}, {0x11038, 0x11046},
|
||||
{0x11070, 0x11070}, {0x11073, 0x11074}, {0x1107f, 0x11082}, {0x110b0, 0x110ba},
|
||||
{0x110c2, 0x110c2}, {0x11100, 0x11102}, {0x11127, 0x11134}, {0x11145, 0x11146},
|
||||
{0x11173, 0x11173}, {0x11180, 0x11182}, {0x111b3, 0x111c0}, {0x111c9, 0x111cc},
|
||||
{0x111ce, 0x111cf}, {0x1122c, 0x11237}, {0x1123e, 0x1123e}, {0x11241, 0x11241},
|
||||
{0x112df, 0x112ea}, {0x11300, 0x11303}, {0x1133b, 0x1133c}, {0x1133e, 0x11344},
|
||||
{0x11347, 0x11348}, {0x1134b, 0x1134d}, {0x11357, 0x11357}, {0x11362, 0x11363},
|
||||
{0x11366, 0x1136c}, {0x11370, 0x11374}, {0x113b8, 0x113c0}, {0x113c2, 0x113c2},
|
||||
{0x113c5, 0x113c5}, {0x113c7, 0x113ca}, {0x113cc, 0x113d0}, {0x113d2, 0x113d2},
|
||||
{0x113e1, 0x113e2}, {0x11435, 0x11446}, {0x1145e, 0x1145e}, {0x114b0, 0x114c3},
|
||||
{0x115af, 0x115b5}, {0x115b8, 0x115c0}, {0x115dc, 0x115dd}, {0x11630, 0x11640},
|
||||
{0x116ab, 0x116b7}, {0x1171d, 0x1172b}, {0x1182c, 0x1183a}, {0x11930, 0x11935},
|
||||
{0x11937, 0x11938}, {0x1193b, 0x1193e}, {0x11940, 0x11940}, {0x11942, 0x11943},
|
||||
{0x119d1, 0x119d7}, {0x119da, 0x119e0}, {0x119e4, 0x119e4}, {0x11a01, 0x11a0a},
|
||||
{0x11a33, 0x11a39}, {0x11a3b, 0x11a3e}, {0x11a47, 0x11a47}, {0x11a51, 0x11a5b},
|
||||
{0x11a8a, 0x11a99}, {0x11b60, 0x11b67}, {0x11c2f, 0x11c36}, {0x11c38, 0x11c3f},
|
||||
{0x11c92, 0x11ca7}, {0x11ca9, 0x11cb6}, {0x11d31, 0x11d36}, {0x11d3a, 0x11d3a},
|
||||
{0x11d3c, 0x11d3d}, {0x11d3f, 0x11d45}, {0x11d47, 0x11d47}, {0x11d8a, 0x11d8e},
|
||||
{0x11d90, 0x11d91}, {0x11d93, 0x11d97}, {0x11ef3, 0x11ef6}, {0x11f00, 0x11f01},
|
||||
{0x11f03, 0x11f03}, {0x11f34, 0x11f3a}, {0x11f3e, 0x11f42}, {0x11f5a, 0x11f5a},
|
||||
{0x13440, 0x13440}, {0x13447, 0x13455}, {0x1611e, 0x1612f}, {0x16af0, 0x16af4},
|
||||
{0x16b30, 0x16b36}, {0x16f4f, 0x16f4f}, {0x16f51, 0x16f87}, {0x16f8f, 0x16f92},
|
||||
{0x16fe4, 0x16fe4}, {0x16ff0, 0x16ff1}, {0x1bc9d, 0x1bc9e}, {0x1cf00, 0x1cf2d},
|
||||
{0x1cf30, 0x1cf46}, {0x1d165, 0x1d169}, {0x1d16d, 0x1d172}, {0x1d17b, 0x1d182},
|
||||
{0x1d185, 0x1d18b}, {0x1d1aa, 0x1d1ad}, {0x1d242, 0x1d244}, {0x1da00, 0x1da36},
|
||||
{0x1da3b, 0x1da6c}, {0x1da75, 0x1da75}, {0x1da84, 0x1da84}, {0x1da9b, 0x1da9f},
|
||||
{0x1daa1, 0x1daaf}, {0x1e000, 0x1e006}, {0x1e008, 0x1e018}, {0x1e01b, 0x1e021},
|
||||
{0x1e023, 0x1e024}, {0x1e026, 0x1e02a}, {0x1e08f, 0x1e08f}, {0x1e130, 0x1e136},
|
||||
{0x1e2ae, 0x1e2ae}, {0x1e2ec, 0x1e2ef}, {0x1e4ec, 0x1e4ef}, {0x1e5ee, 0x1e5ef},
|
||||
{0x1e6e3, 0x1e6e3}, {0x1e6e6, 0x1e6e6}, {0x1e6ee, 0x1e6ef}, {0x1e6f5, 0x1e6f5},
|
||||
{0x1e8d0, 0x1e8d6}, {0x1e944, 0x1e94a}, {0xe0100, 0xe01ef},
|
||||
};
|
||||
|
||||
static const ds4_qwen4_urange ds4_qwen4_number_ranges[] = {
|
||||
{0x00030, 0x00039}, {0x000b2, 0x000b3}, {0x000b9, 0x000b9}, {0x000bc, 0x000be},
|
||||
{0x00660, 0x00669}, {0x006f0, 0x006f9}, {0x007c0, 0x007c9}, {0x00966, 0x0096f},
|
||||
{0x009e6, 0x009ef}, {0x009f4, 0x009f9}, {0x00a66, 0x00a6f}, {0x00ae6, 0x00aef},
|
||||
{0x00b66, 0x00b6f}, {0x00b72, 0x00b77}, {0x00be6, 0x00bf2}, {0x00c66, 0x00c6f},
|
||||
{0x00c78, 0x00c7e}, {0x00ce6, 0x00cef}, {0x00d58, 0x00d5e}, {0x00d66, 0x00d78},
|
||||
{0x00de6, 0x00def}, {0x00e50, 0x00e59}, {0x00ed0, 0x00ed9}, {0x00f20, 0x00f33},
|
||||
{0x01040, 0x01049}, {0x01090, 0x01099}, {0x01369, 0x0137c}, {0x016ee, 0x016f0},
|
||||
{0x017e0, 0x017e9}, {0x017f0, 0x017f9}, {0x01810, 0x01819}, {0x01946, 0x0194f},
|
||||
{0x019d0, 0x019da}, {0x01a80, 0x01a89}, {0x01a90, 0x01a99}, {0x01b50, 0x01b59},
|
||||
{0x01bb0, 0x01bb9}, {0x01c40, 0x01c49}, {0x01c50, 0x01c59}, {0x02070, 0x02070},
|
||||
{0x02074, 0x02079}, {0x02080, 0x02089}, {0x02150, 0x02182}, {0x02185, 0x02189},
|
||||
{0x02460, 0x0249b}, {0x024ea, 0x024ff}, {0x02776, 0x02793}, {0x02cfd, 0x02cfd},
|
||||
{0x03007, 0x03007}, {0x03021, 0x03029}, {0x03038, 0x0303a}, {0x03192, 0x03195},
|
||||
{0x03220, 0x03229}, {0x03248, 0x0324f}, {0x03251, 0x0325f}, {0x03280, 0x03289},
|
||||
{0x032b1, 0x032bf}, {0x0a620, 0x0a629}, {0x0a6e6, 0x0a6ef}, {0x0a830, 0x0a835},
|
||||
{0x0a8d0, 0x0a8d9}, {0x0a900, 0x0a909}, {0x0a9d0, 0x0a9d9}, {0x0a9f0, 0x0a9f9},
|
||||
{0x0aa50, 0x0aa59}, {0x0abf0, 0x0abf9}, {0x0ff10, 0x0ff19}, {0x10107, 0x10133},
|
||||
{0x10140, 0x10178}, {0x1018a, 0x1018b}, {0x102e1, 0x102fb}, {0x10320, 0x10323},
|
||||
{0x10341, 0x10341}, {0x1034a, 0x1034a}, {0x103d1, 0x103d5}, {0x104a0, 0x104a9},
|
||||
{0x10858, 0x1085f}, {0x10879, 0x1087f}, {0x108a7, 0x108af}, {0x108fb, 0x108ff},
|
||||
{0x10916, 0x1091b}, {0x109bc, 0x109bd}, {0x109c0, 0x109cf}, {0x109d2, 0x109ff},
|
||||
{0x10a40, 0x10a48}, {0x10a7d, 0x10a7e}, {0x10a9d, 0x10a9f}, {0x10aeb, 0x10aef},
|
||||
{0x10b58, 0x10b5f}, {0x10b78, 0x10b7f}, {0x10ba9, 0x10baf}, {0x10cfa, 0x10cff},
|
||||
{0x10d30, 0x10d39}, {0x10d40, 0x10d49}, {0x10e60, 0x10e7e}, {0x10f1d, 0x10f26},
|
||||
{0x10f51, 0x10f54}, {0x10fc5, 0x10fcb}, {0x11052, 0x1106f}, {0x110f0, 0x110f9},
|
||||
{0x11136, 0x1113f}, {0x111d0, 0x111d9}, {0x111e1, 0x111f4}, {0x112f0, 0x112f9},
|
||||
{0x11450, 0x11459}, {0x114d0, 0x114d9}, {0x11650, 0x11659}, {0x116c0, 0x116c9},
|
||||
{0x116d0, 0x116e3}, {0x11730, 0x1173b}, {0x118e0, 0x118f2}, {0x11950, 0x11959},
|
||||
{0x11bf0, 0x11bf9}, {0x11c50, 0x11c6c}, {0x11d50, 0x11d59}, {0x11da0, 0x11da9},
|
||||
{0x11de0, 0x11de9}, {0x11f50, 0x11f59}, {0x11fc0, 0x11fd4}, {0x12400, 0x1246e},
|
||||
{0x16130, 0x16139}, {0x16a60, 0x16a69}, {0x16ac0, 0x16ac9}, {0x16b50, 0x16b59},
|
||||
{0x16b5b, 0x16b61}, {0x16d70, 0x16d79}, {0x16e80, 0x16e96}, {0x16ff4, 0x16ff6},
|
||||
{0x1ccf0, 0x1ccf9}, {0x1d2c0, 0x1d2d3}, {0x1d2e0, 0x1d2f3}, {0x1d360, 0x1d378},
|
||||
{0x1d7ce, 0x1d7ff}, {0x1e140, 0x1e149}, {0x1e2f0, 0x1e2f9}, {0x1e4f0, 0x1e4f9},
|
||||
{0x1e5f1, 0x1e5fa}, {0x1e8c7, 0x1e8cf}, {0x1e950, 0x1e959}, {0x1ec71, 0x1ecab},
|
||||
{0x1ecad, 0x1ecaf}, {0x1ecb1, 0x1ecb4}, {0x1ed01, 0x1ed2d}, {0x1ed2f, 0x1ed3d},
|
||||
{0x1f100, 0x1f10c}, {0x1fbf0, 0x1fbf9},
|
||||
};
|
||||
|
||||
static const ds4_qwen4_urange ds4_qwen4_space_ranges[] = {
|
||||
{0x00009, 0x0000d}, {0x00020, 0x00020}, {0x00085, 0x00085}, {0x000a0, 0x000a0},
|
||||
{0x01680, 0x01680}, {0x02000, 0x0200a}, {0x02028, 0x02029}, {0x0202f, 0x0202f},
|
||||
{0x0205f, 0x0205f}, {0x03000, 0x03000},
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef DS4_QWEN4_VISION_H
|
||||
#define DS4_QWEN4_VISION_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define DS4_QWEN4_VISION_LAYERS 27
|
||||
typedef struct {
|
||||
uint64_t ln1_w, ln1_b, qkv_w, qkv_b, out_w, out_b, ln2_w, ln2_b, up_w, up_b, down_w, down_b;
|
||||
uint32_t qkv_type, out_type, up_type, down_type;
|
||||
} ds4_qwen4_vision_layer_weights;
|
||||
typedef struct {
|
||||
uint64_t patch_w0, patch_w1, patch_b, pos_embd, post_ln_w, post_ln_b, mm0_w, mm0_b, mm2_w, mm2_b;
|
||||
uint32_t mm0_type, mm2_type, patch_type;
|
||||
uint32_t n_embd, n_ff, n_head, n_patch, n_merge, n_pos_side, n_out;
|
||||
float eps;
|
||||
ds4_qwen4_vision_layer_weights layer[DS4_QWEN4_VISION_LAYERS];
|
||||
} ds4_qwen4_vision_weights;
|
||||
|
||||
#endif
|
||||
+28
@@ -1,6 +1,7 @@
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
#include "ds4_rocm.h"
|
||||
#include <hipblaslt/hipblaslt.h>
|
||||
#include <rocblas/rocblas.h>
|
||||
|
||||
#define FULL_WARP_MASK 0xFFFFFFFFFFFFFFFFULL
|
||||
#define MASK_T uint64_t
|
||||
@@ -39,7 +40,27 @@
|
||||
#include <vector>
|
||||
|
||||
#include "ds4_gpu.h"
|
||||
#include "ds4_image.h"
|
||||
|
||||
static thread_local bool g_dspark_verify_mode;
|
||||
|
||||
extern "C" void ds4_gpu_set_dspark_verify_mode(bool enabled) {
|
||||
g_dspark_verify_mode = enabled;
|
||||
}
|
||||
|
||||
extern "C" int ds4_mmq_init(int device);
|
||||
extern "C" int ds4_mmq_iq2_xxs_moe_pair(
|
||||
const void *W_a, const void *W_b, const float *X_f32,
|
||||
const int32_t *ids, float *out_a, float *out_b,
|
||||
int M, int K, int n_tokens, int n_experts, int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
extern "C" int ds4_mmq_q8_0_dense_vec(
|
||||
const void *W, const float *X_f32, float *out_f32,
|
||||
int M, int N, int K, cudaStream_t stream);
|
||||
extern "C" int ds4_mmq_q2_K_moe_down_sum6_vec(
|
||||
const void *W, const float *X, const int32_t *ids, float *out,
|
||||
int M, int K, int n_tokens, int n_experts, int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
@@ -107,6 +128,10 @@ __device__ __constant__ static const int8_t cuda_mxfp4_values_x2[16] = {
|
||||
|
||||
#include "rocm/ds4_rocm_common.cuh"
|
||||
|
||||
extern "C" int ds4_gpu_dspark_gfx1151_fast_path(void) {
|
||||
return ds4_rocm_is_gfx1151();
|
||||
}
|
||||
|
||||
#include "rocm/ds4_rocm_q8.cuh"
|
||||
|
||||
#include "rocm/ds4_rocm_norm_rope.cuh"
|
||||
@@ -147,6 +172,9 @@ __device__ __constant__ static const int8_t cuda_mxfp4_values_x2[16] = {
|
||||
#include "rocm/ds4_rocm_current_api_compat.cuh"
|
||||
|
||||
#include "ds4_glm53_vision_gpu.cuh"
|
||||
#include "ds4_deepseek4_vision_gpu.cuh"
|
||||
#include "rocm/ds4_rocm_deepseek4_vision.cuh"
|
||||
#include "ds4_deepseek41_cuda.cuh"
|
||||
|
||||
/* Tensor-parallel gates are Metal-only; stubs keep shared graph code
|
||||
* linkable (TP option validation rejects non-Metal backends). */
|
||||
|
||||
+11
-5
@@ -6,6 +6,7 @@
|
||||
#include <hipcub/hipcub.hpp>
|
||||
#include <rocwmma/rocwmma-version.hpp>
|
||||
#include <rocwmma/rocwmma.hpp>
|
||||
#include "ds4_rocm_memory.h"
|
||||
|
||||
#define cudaError_t hipError_t
|
||||
#define cudaStream_t hipStream_t
|
||||
@@ -30,9 +31,9 @@
|
||||
#define cudaFuncSetAttribute(func, attr, value) hipFuncSetAttribute((const void *)(func), (attr), (value))
|
||||
#define cudaMemLocationTypeDevice hipMemLocationTypeDevice
|
||||
|
||||
#define cudaMalloc hipMalloc
|
||||
#define cudaMallocHost hipHostMalloc
|
||||
#define cudaMallocManaged hipMallocManaged
|
||||
#define cudaMalloc ds4_rocm_malloc
|
||||
#define cudaMallocHost ds4_rocm_malloc_host
|
||||
#define cudaMallocManaged ds4_rocm_malloc_managed
|
||||
#define cudaFree hipFree
|
||||
#define cudaFreeHost hipFreeHost
|
||||
#define cudaMemset hipMemset
|
||||
@@ -41,7 +42,7 @@
|
||||
#define cudaMemcpyHostToDevice hipMemcpyHostToDevice
|
||||
#define cudaMemcpyDeviceToHost hipMemcpyDeviceToHost
|
||||
#define cudaMemcpyDeviceToDevice hipMemcpyDeviceToDevice
|
||||
#define cudaMemGetInfo hipMemGetInfo
|
||||
#define cudaMemGetInfo ds4_rocm_mem_get_info
|
||||
#define cudaMemsetAsync hipMemsetAsync
|
||||
|
||||
#define cudaHostRegister hipHostRegister
|
||||
@@ -138,5 +139,10 @@ extern "C" __device__ __attribute__((pure)) float __ocml_log1p_f32(float);
|
||||
extern "C" __device__ __attribute__((const)) float __ocml_sqrt_f32(float);
|
||||
|
||||
static __device__ __forceinline__ float ds4_precise_expf(float x) { return __ocml_exp_f32(x); }
|
||||
static __device__ __forceinline__ float ds4_precise_log1pf(float x) { return __ocml_log1p_f32(x); }
|
||||
static __device__ __forceinline__ float ds4_precise_log1pf(float x) {
|
||||
/* OCML's unsafe-math mode can reduce log1p(x) to log(1+x), losing
|
||||
* small router probabilities. The cubic's relative error here is < 2^-31. */
|
||||
if (fabsf(x) < 0x1p-10f) return x * (1.0f - x * (0.5f - x / 3.0f));
|
||||
return __ocml_log1p_f32(x);
|
||||
}
|
||||
static __device__ __forceinline__ float ds4_precise_sqrtf(float x) { return __ocml_sqrt_f32(x); }
|
||||
+6
-13
@@ -1,4 +1,5 @@
|
||||
#include <hip/hip_runtime.h>
|
||||
#include "ds4_rocm_memory.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -57,7 +58,7 @@ extern "C" int ds4_gpu_tensor_alloc_on(ds4_gpu_tensor *t, int tier,
|
||||
if (!t) return 1;
|
||||
if (!rocm_tier_valid(tier)) return 2;
|
||||
if (bytes == 0) bytes = 1;
|
||||
if (hipMalloc(&t->ptr, (size_t)bytes) != hipSuccess) return 3;
|
||||
if (ds4_rocm_malloc(&t->ptr, (size_t)bytes) != hipSuccess) return 3;
|
||||
t->bytes = bytes;
|
||||
t->owner = 1;
|
||||
t->device_id = 0;
|
||||
@@ -224,23 +225,15 @@ extern "C" int ds4_gpu_set_decode_score_vec4(int enabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int ds4_gpu_matmul_q8_0_decode_rows_exact_tensor(
|
||||
ds4_gpu_tensor *out, const void *model_map, uint64_t model_size,
|
||||
uint64_t weight_offset, uint64_t in_dim, uint64_t out_dim,
|
||||
const ds4_gpu_tensor *x, uint32_t n_rows) {
|
||||
return ds4_gpu_matmul_q8_0_tensor(out, model_map, model_size,
|
||||
weight_offset, in_dim, out_dim, x,
|
||||
n_rows);
|
||||
}
|
||||
|
||||
extern "C" int ds4_gpu_matmul_q8_0_pair_decode_rows_exact_tensor(
|
||||
ds4_gpu_tensor *out0, ds4_gpu_tensor *out1, const void *model_map,
|
||||
uint64_t model_size, uint64_t weight0_offset,
|
||||
uint64_t weight1_offset, uint64_t in_dim, uint64_t out0_dim,
|
||||
uint64_t out1_dim, const ds4_gpu_tensor *x, uint32_t n_rows) {
|
||||
return ds4_gpu_matmul_q8_0_pair_tensor(
|
||||
out0, out1, model_map, model_size, weight0_offset, weight1_offset,
|
||||
in_dim, out0_dim, out1_dim, x, n_rows);
|
||||
return ds4_gpu_matmul_q8_0_decode_rows_exact_tensor(out0, model_map,
|
||||
model_size, weight0_offset, in_dim, out0_dim, x, n_rows) &&
|
||||
ds4_gpu_matmul_q8_0_decode_rows_exact_tensor(out1, model_map,
|
||||
model_size, weight1_offset, in_dim, out1_dim, x, n_rows);
|
||||
}
|
||||
|
||||
extern "C" int ds4_gpu_matmul_f16_router_rows_exact_tensor(
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
#ifndef DS4_ROCM_MEMORY_H
|
||||
#define DS4_ROCM_MEMORY_H
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#include "ds4_linux_memory.h"
|
||||
|
||||
static inline uint64_t ds4_rocm_model_arena_bytes(uint64_t need) {
|
||||
/* Large tensor spans should not strand hundreds of MiB each. Small
|
||||
* tensors still share an arena to avoid thousands of driver allocations. */
|
||||
return need >= (256ull << 20) ? need : (1792ull << 20);
|
||||
}
|
||||
|
||||
static inline bool ds4_rocm_uses_host_ram(void) {
|
||||
int device;
|
||||
hipDeviceProp_t prop;
|
||||
return hipGetDevice(&device) == hipSuccess &&
|
||||
hipGetDeviceProperties(&prop, device) == hipSuccess && prop.integrated;
|
||||
}
|
||||
|
||||
static inline bool ds4_rocm_allocation_fits(size_t bytes, bool host = false) {
|
||||
if (!host && !ds4_rocm_uses_host_ram()) return true;
|
||||
uint64_t available;
|
||||
const uint64_t reserve = 2ull << 30;
|
||||
if (!ds4_linux_nonmovable_memory(&available)) {
|
||||
fprintf(stderr, "ds4: ROCm cannot read usable host memory; allocation refused\n");
|
||||
return false;
|
||||
}
|
||||
if (available >= reserve && bytes <= available - reserve) return true;
|
||||
fprintf(stderr, "ds4: ROCm allocation refused: %.2f MiB requested, "
|
||||
"%.2f GiB usable after excluding CMA, 2 GiB OS reserve\n",
|
||||
(double)bytes / 1048576.0, (double)available / 1073741824.0);
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline hipError_t ds4_rocm_malloc(T **ptr, size_t bytes) {
|
||||
if (!ds4_rocm_allocation_fits(bytes)) return hipErrorOutOfMemory;
|
||||
return hipMalloc((void **)ptr, bytes);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline hipError_t ds4_rocm_malloc_host(T **ptr, size_t bytes) {
|
||||
if (!ds4_rocm_allocation_fits(bytes, true)) return hipErrorOutOfMemory;
|
||||
return hipHostMalloc((void **)ptr, bytes);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline hipError_t ds4_rocm_malloc_managed(T **ptr, size_t bytes) {
|
||||
if (!ds4_rocm_allocation_fits(bytes, true)) return hipErrorOutOfMemory;
|
||||
return hipMallocManaged((void **)ptr, bytes);
|
||||
}
|
||||
|
||||
static inline hipError_t ds4_rocm_mem_get_info(size_t *free_b, size_t *total_b) {
|
||||
hipError_t err = hipMemGetInfo(free_b, total_b);
|
||||
if (err != hipSuccess || !ds4_rocm_uses_host_ram()) return err;
|
||||
uint64_t available;
|
||||
if (!ds4_linux_nonmovable_memory(&available)) return hipErrorUnknown;
|
||||
if (available < *free_b) *free_b = (size_t)available;
|
||||
return hipSuccess;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -37,3 +37,6 @@ ROCM_UNAVAILABLE_INT(ds4_gpu_shared_mid_swiglu_q8_0_decode_exact_tensor)
|
||||
|
||||
extern "C" uint64_t ds4_gpu_tp_big_gate_kick(...) { return 0; }
|
||||
ROCM_UNAVAILABLE_INT(ds4_gpu_tp_big_gate_wait)
|
||||
ROCM_UNAVAILABLE_INT(ds4_gpu_tp_init)
|
||||
extern "C" void ds4_gpu_tp_shutdown(void) {}
|
||||
extern "C" int ds4_gpu_tp_failed(void) { return 1; }
|
||||
+4436
-549
File diff suppressed because it is too large.
Load diff
@@ -77,7 +77,7 @@ uint32_t ds4_ssd_cache_experts_for_byte_budget(uint64_t bytes,
|
||||
return (uint32_t)experts;
|
||||
}
|
||||
|
||||
static uint64_t ds4_ssd_auto_cache_percent(void) {
|
||||
static uint64_t ds4_ssd_auto_cache_percent(uint32_t default_percent) {
|
||||
const char *env = getenv("DS4_SSD_AUTO_CACHE_PCT");
|
||||
if (env && env[0]) {
|
||||
errno = 0;
|
||||
@@ -102,22 +102,27 @@ static uint64_t ds4_ssd_auto_cache_percent(void) {
|
||||
* the machine out of RAM and trips the Linux OOM killer. 80% was measured
|
||||
* safe here; opt into more only deliberately via DS4_SSD_AUTO_CACHE_PCT.
|
||||
*/
|
||||
return 80;
|
||||
return default_percent;
|
||||
}
|
||||
|
||||
bool ds4_ssd_auto_cache_plan(uint64_t recommended_bytes,
|
||||
uint32_t default_percent,
|
||||
uint64_t model_limit_bytes,
|
||||
uint64_t non_routed_bytes,
|
||||
uint64_t per_expert_bytes,
|
||||
uint64_t max_model_experts,
|
||||
ds4_ssd_cache_plan *out) {
|
||||
if (!out) return false;
|
||||
memset(out, 0, sizeof(*out));
|
||||
if (recommended_bytes == 0 || per_expert_bytes == 0) return false;
|
||||
if (recommended_bytes == 0 || per_expert_bytes == 0 ||
|
||||
default_percent < 50 || default_percent > 95) return false;
|
||||
|
||||
const uint64_t pct = ds4_ssd_auto_cache_percent();
|
||||
const uint64_t pct = ds4_ssd_auto_cache_percent(default_percent);
|
||||
out->model_target_bytes =
|
||||
recommended_bytes > UINT64_MAX / pct ?
|
||||
UINT64_MAX : (recommended_bytes * pct) / 100ull;
|
||||
if (model_limit_bytes != 0 && out->model_target_bytes > model_limit_bytes)
|
||||
out->model_target_bytes = model_limit_bytes;
|
||||
if (out->model_target_bytes > non_routed_bytes) {
|
||||
out->cache_bytes = out->model_target_bytes - non_routed_bytes;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ bool ds4_parse_streaming_cache_experts_arg(const char *s,
|
||||
uint32_t ds4_ssd_cache_experts_for_byte_budget(uint64_t bytes,
|
||||
uint64_t per_expert_bytes);
|
||||
bool ds4_ssd_auto_cache_plan(uint64_t recommended_bytes,
|
||||
uint32_t default_percent,
|
||||
uint64_t model_limit_bytes,
|
||||
uint64_t non_routed_bytes,
|
||||
uint64_t per_expert_bytes,
|
||||
uint64_t max_model_experts,
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#ifndef DS4_TOOL_TEXT_H
|
||||
#define DS4_TOOL_TEXT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Tool bodies are not HTML. Escape only their own closing delimiter and
|
||||
* spellings that would otherwise collide with that escape. */
|
||||
static inline bool ds4_tool_text_escaped_close(const char *s, const char *end) {
|
||||
if (*s != '&') return false;
|
||||
s++;
|
||||
while (!strncmp(s, "amp;", 4)) s += 4;
|
||||
return !strncmp(s, "lt;", 3) && !strncmp(s + 3, end + 1, strlen(end + 1));
|
||||
}
|
||||
|
||||
static inline bool ds4_tool_text_needs_escape(const char *s, const char *end) {
|
||||
return !strncmp(s, end, strlen(end)) || ds4_tool_text_escaped_close(s, end);
|
||||
}
|
||||
|
||||
static inline void ds4_tool_text_unescape(char *s, const char *end) {
|
||||
char *out = s;
|
||||
while (*s) {
|
||||
if (ds4_tool_text_escaped_close(s, end)) {
|
||||
if (!strncmp(s, "&", 5)) {
|
||||
*out++ = '&';
|
||||
s += 5;
|
||||
} else {
|
||||
*out++ = '<';
|
||||
s += 4;
|
||||
}
|
||||
} else {
|
||||
*out++ = *s++;
|
||||
}
|
||||
}
|
||||
*out = '\0';
|
||||
}
|
||||
|
||||
/* A streaming fragment must not split an escape that could become a closing
|
||||
* delimiter. Ordinary entities pass through unchanged. */
|
||||
static inline bool ds4_tool_text_partial_escape(const char *s, size_t n,
|
||||
const char *end) {
|
||||
if (!n || *s != '&') return false;
|
||||
s++;
|
||||
n--;
|
||||
while (n >= 4 && !memcmp(s, "amp;", 4)) {
|
||||
s += 4;
|
||||
n -= 4;
|
||||
}
|
||||
if (n < 4 && !memcmp(s, "amp;", n)) return true;
|
||||
if (n < 3) return !memcmp(s, "lt;", n);
|
||||
if (memcmp(s, "lt;", 3)) return false;
|
||||
s += 3;
|
||||
n -= 3;
|
||||
size_t tail = strlen(end + 1);
|
||||
return n < tail && !memcmp(s, end + 1, n);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -30,8 +30,9 @@ enum {
|
||||
DS4_TP_GATE_ATTN = 0,
|
||||
DS4_TP_GATE_FFN = 1,
|
||||
DS4_TP_GATES_PER_LAYER = 2,
|
||||
/* Max rows in a verify-block batch gate (speculative blocks are <=5). */
|
||||
/* Includes the seed row as well as drafted tokens. */
|
||||
DS4_TP_BATCH_MAX_ROWS = 8,
|
||||
DS4_TP_BATCH_GATES_PER_LAYER = 2,
|
||||
};
|
||||
|
||||
/* Engine identity exchanged in the hello so a mismatched pair aborts before
|
||||
@@ -103,7 +104,20 @@ void ds4_tp_free(ds4_tp *tp);
|
||||
|
||||
int ds4_tp_rank(const ds4_tp *tp);
|
||||
bool ds4_tp_is_rdma(const ds4_tp *tp);
|
||||
/* True once both peers agreed on the RDMA command mailbox (RC queue pairs,
|
||||
* not disabled with DS4_TP_DISABLE_RDMA_MAILBOX on either rank). */
|
||||
bool ds4_tp_command_mailbox_active(const ds4_tp *tp);
|
||||
uint32_t ds4_tp_peer_ctx(const ds4_tp *tp);
|
||||
enum {
|
||||
DS4_TP_MODE_EXPERT_INTERMEDIATE_SPLIT = 1u,
|
||||
DS4_TP_MODE_V41_SHARED_OWNER = 2u,
|
||||
DS4_TP_MODE_V41_SHARED_BALANCE = 4u,
|
||||
DS4_TP_MODE_V41_DSPARK = 8u,
|
||||
};
|
||||
/* Call on both engines before binding the GPU slab. Resident layouts and
|
||||
* shared-expert ownership must match before either rank can reduce results.
|
||||
* full_experts is a local capability on entry, the intersection on return. */
|
||||
int ds4_tp_agree_execution_mode(ds4_tp *tp, uint32_t flags, bool *full_experts, char *err, size_t errlen);
|
||||
bool ds4_tp_failed(const ds4_tp *tp);
|
||||
void ds4_tp_mark_failed(ds4_tp *tp);
|
||||
|
||||
@@ -115,7 +129,8 @@ void ds4_tp_mark_failed(ds4_tp *tp);
|
||||
* in vectors S * vec_bytes RDMA/TCP-written with the peer partials
|
||||
* in seq flags S * 8 written strictly after each in vector
|
||||
* token slot 16 {seq u64, token i32, pad} leader->worker
|
||||
* (gpu flags, then batch out/in: n_layer * BATCH_MAX_ROWS * vec_bytes
|
||||
* (gpu flags, then batch out/in:
|
||||
* n_layer * BATCH_GATES_PER_LAYER * BATCH_MAX_ROWS * vec_bytes
|
||||
* each, row partials for the speculative verify-block gates)
|
||||
*
|
||||
* vec_bytes = n_embd * 4 (f32 partials, never quantized on the wire). */
|
||||
@@ -126,6 +141,9 @@ uint64_t ds4_tp_slab_batch_out_offset(const ds4_tp *tp, uint32_t layer);
|
||||
uint64_t ds4_tp_slab_batch_in_offset(const ds4_tp *tp, uint32_t layer);
|
||||
uint64_t ds4_tp_slab_gpu_flags_offset(const ds4_tp *tp);
|
||||
int ds4_tp_attach_slab(ds4_tp *tp, void *base, char *err, size_t errlen);
|
||||
/* Stop the data plane before freeing its registered buffers. No more gates
|
||||
* may be issued; free the transport after the engine has been unbound. */
|
||||
void ds4_tp_detach_slab(ds4_tp *tp);
|
||||
|
||||
/* Exchange one gate: send out[layer][gate] to the peer's in[layer][gate]
|
||||
* and wait until the peer's partial for `seq` has fully landed locally.
|
||||
@@ -137,6 +155,15 @@ int ds4_tp_gate_exchange(ds4_tp *tp, uint32_t layer, uint32_t gate, uint64_t seq
|
||||
* the GPU gate service thread. */
|
||||
int ds4_tp_batch_gate_exchange(ds4_tp *tp, uint32_t layer, uint32_t rows,
|
||||
uint64_t seq);
|
||||
/* Verify-block RDMA window (speculative decoding): call on both ranks right
|
||||
* before/after a verify block with one batch gate per layer. */
|
||||
int ds4_tp_batch_block_begin(ds4_tp *tp, uint32_t rows, uint32_t n_layers);
|
||||
/* Negotiate one or two batch gates per layer before encoding the block.
|
||||
* Both ranks receive the smaller offered count. Slots are then consecutive
|
||||
* gate indices, not layer indices. Works with RDMA and TCP. */
|
||||
int ds4_tp_batch_block_begin_gates(ds4_tp *tp, uint32_t rows, uint32_t n_layers,
|
||||
uint32_t *gates_per_layer);
|
||||
int ds4_tp_batch_block_end(ds4_tp *tp);
|
||||
|
||||
/* Prefill batch gate: arbitrary-size symmetric payload exchange over bulk
|
||||
* RDMA, with interleaved 2MB TCP rounds as fallback (see ds4_tp.c). */
|
||||
@@ -160,7 +187,13 @@ int ds4_tp_send_sync_multimodal(ds4_tp *tp, uint64_t session_id,
|
||||
uint32_t image_count);
|
||||
int ds4_tp_send_eval(ds4_tp *tp, uint64_t session_id,
|
||||
uint64_t seq, int token);
|
||||
int ds4_tp_send_glm_mtp(ds4_tp *tp, uint64_t session_id,
|
||||
uint64_t seq, int token, int limit);
|
||||
int ds4_tp_send_rewind(ds4_tp *tp, uint64_t session_id, int pos);
|
||||
/* State-only restore: ACK status 0 restored, 1 unavailable, -1 failed (closes).
|
||||
* No TP collectives or logits reply. Await ds4_tp_wait_command_status() before
|
||||
* mirroring replay EVALs; status 1 requires invalidation and full sync. */
|
||||
int ds4_tp_send_spec_restore(ds4_tp *tp, uint64_t session_id, int pos);
|
||||
int ds4_tp_send_invalidate(ds4_tp *tp, uint64_t session_id);
|
||||
int ds4_tp_send_eval_batch(ds4_tp *tp, const ds4_tp_batch_item *items,
|
||||
uint32_t count);
|
||||
@@ -171,6 +204,12 @@ int ds4_tp_send_mixed_batch(ds4_tp *tp, uint64_t prefill_session_id,
|
||||
int ds4_tp_send_command_ack(ds4_tp *tp, uint64_t session_id, int status);
|
||||
int ds4_tp_wait_command_ack(ds4_tp *tp, uint64_t session_id,
|
||||
const char *operation, char *err, size_t errlen);
|
||||
int ds4_tp_wait_command_status(ds4_tp *tp, uint64_t session_id, int *status,
|
||||
const char *operation, char *err, size_t errlen);
|
||||
/* Both ranks call at matching prefill boundaries. Cancellation is agreed
|
||||
* here, never sampled independently while either rank is inside a GPU gate. */
|
||||
int ds4_tp_sync_checkpoint(ds4_tp *tp, uint32_t point, int current, int total,
|
||||
bool requested, bool *cancelled);
|
||||
int ds4_tp_send_stop(ds4_tp *tp);
|
||||
|
||||
/* Worker: blocks for the next mirrored command. Frame types below; for
|
||||
@@ -196,6 +235,12 @@ typedef enum {
|
||||
DS4_TP_FRAME_MIXED_BATCH = 16,
|
||||
DS4_TP_FRAME_COMMAND_ACK = 17,
|
||||
DS4_TP_FRAME_SYNC_MULTIMODAL = 18,
|
||||
DS4_TP_FRAME_RDMA_WARM = 19,
|
||||
DS4_TP_FRAME_RDMA_POSTED = 20,
|
||||
DS4_TP_FRAME_GLM_MTP = 21,
|
||||
DS4_TP_FRAME_SYNC_CHECKPOINT = 22,
|
||||
DS4_TP_FRAME_DSPARK_DRAFT = 23,
|
||||
DS4_TP_FRAME_SPEC_RESTORE = 24,
|
||||
} ds4_tp_frame_type;
|
||||
|
||||
typedef struct {
|
||||
@@ -203,6 +248,7 @@ typedef struct {
|
||||
uint64_t session_id;
|
||||
uint64_t seq;
|
||||
int value;
|
||||
int limit;
|
||||
int *tokens;
|
||||
uint32_t n_tokens;
|
||||
ds4_tp_batch_item *items;
|
||||
@@ -229,13 +275,73 @@ int ds4_tp_recv_logits_half(ds4_tp *tp, float *half, uint32_t count);
|
||||
|
||||
/* Speculative verify mirroring. The leader announces a draft block right
|
||||
* before both ranks run the expert-split batch verify; the worker then blocks
|
||||
* on the commit frame, which carries the leader's decision: full_accept keeps
|
||||
* the pushed rows, otherwise both sides roll back and replay replay_n tokens
|
||||
* through the gated single-token decode in lockstep. */
|
||||
* on the commit frame. A full commit keeps the entire verified block, a prefix
|
||||
* commit restores the matching verifier prefix on both ranks, and rollback
|
||||
* restores the original frontier before replaying token_count tokens. */
|
||||
typedef enum {
|
||||
DS4_TP_VERIFY_ROLLBACK_REPLAY = 0,
|
||||
DS4_TP_VERIFY_COMMIT_FULL = 1,
|
||||
DS4_TP_VERIFY_COMMIT_PREFIX = 2,
|
||||
} ds4_tp_verify_commit_mode;
|
||||
|
||||
int ds4_tp_send_verify(ds4_tp *tp, uint64_t session_id,
|
||||
const int *drafts, uint32_t n);
|
||||
int ds4_tp_send_verify_commit(ds4_tp *tp, int32_t full_accept, int32_t replay_n);
|
||||
int ds4_tp_recv_verify_commit(ds4_tp *tp, int32_t *full_accept, int32_t *replay_n);
|
||||
/* VERIFY flags. DS4_TP_VERIFY_HEAD_SPLIT: greedy block, each rank computes
|
||||
* the verify head on its vocabulary half and the ranks combine row top-1s
|
||||
* (ds4_tp_small_exchange); the worker then sends its half of the one row
|
||||
* the leader commits (ds4_tp_head_row_send/recv). */
|
||||
#define DS4_TP_VERIFY_HEAD_SPLIT 1u
|
||||
/* DS4_TP_VERIFY_DSPARK_TP: the block holds at least one row of the last
|
||||
* lockstep DRAFT's proposal, which the worker also holds and checks (a block
|
||||
* trimmed to its seed is unmarked); DS4_TP_VERIFY_DSPARK_SEED: the block
|
||||
* starts with the seed, so the proposal rows are drafts[1..]. */
|
||||
#define DS4_TP_VERIFY_DSPARK_TP 2u
|
||||
#define DS4_TP_VERIFY_DSPARK_SEED 4u
|
||||
#define DS4_TP_VERIFY_FLAGS \
|
||||
(DS4_TP_VERIFY_HEAD_SPLIT | DS4_TP_VERIFY_DSPARK_TP | DS4_TP_VERIFY_DSPARK_SEED)
|
||||
int ds4_tp_send_verify_flags(ds4_tp *tp, uint64_t session_id,
|
||||
const int *drafts, uint32_t n, uint32_t flags);
|
||||
|
||||
/* Lockstep TP drafter. The leader sends DSPARK_DRAFT where it would prepare
|
||||
* a DSpark proposal. DRAFT: both ranks run the proposal for (token, pos) on
|
||||
* their halves of the drafter. MAINTAIN: both only bring the drafter history
|
||||
* up to pos; the leader may still draft on its own with the full drafter.
|
||||
* The worker receives the token in value, pos in limit and the mode in seq. */
|
||||
enum {
|
||||
DS4_TP_DSPARK_MAINTAIN = 1,
|
||||
DS4_TP_DSPARK_DRAFT = 2,
|
||||
};
|
||||
int ds4_tp_send_dspark_draft(ds4_tp *tp, uint64_t session_id, int token,
|
||||
int pos, uint32_t mode);
|
||||
/* Startup agreement, after the execution mode: enabled only when both ranks
|
||||
* opted in with byte-identical metadata. When only one opted in, both keep
|
||||
* the leader-local drafter (*agreed false, *peer_on reports the peer);
|
||||
* different metadata fails. */
|
||||
#define DS4_TP_DSPARK_META_BYTES 64u
|
||||
int ds4_tp_agree_dspark_split(ds4_tp *tp, bool mine_on, const void *meta,
|
||||
bool *agreed, bool *peer_on,
|
||||
char *err, size_t errlen);
|
||||
|
||||
/* Lockstep symmetric exchange of up to DS4_TP_SMALL_EXCHANGE_MAX bytes over
|
||||
* polled slab records (RDMA command mailbox only; 0 without it). Both
|
||||
* ranks must pass the same byte count, which the record carries and the
|
||||
* receiver checks. Failure marks the context failed. */
|
||||
#define DS4_TP_SMALL_EXCHANGE_MAX 192u
|
||||
int ds4_tp_small_exchange(ds4_tp *tp, const void *mine, void *peer, uint32_t bytes);
|
||||
|
||||
/* Worker -> leader float row of up to DS4_TP_HEAD_ROW_MAX floats (mailbox
|
||||
* only), sent after the current verify block's begin. The worker writes the
|
||||
* row into ds4_tp_head_row_out() (host memory inside the registered slab),
|
||||
* then sends; the leader receives it into dst. The record carries the
|
||||
* verify block, row and float count and the leader requires all three, so
|
||||
* a row sent for a block the leader then rolled back without reading is
|
||||
* never mistaken for a later one. */
|
||||
#define DS4_TP_HEAD_ROW_MAX 131072u
|
||||
float *ds4_tp_head_row_out(ds4_tp *tp);
|
||||
int ds4_tp_head_row_send(ds4_tp *tp, uint32_t row, uint32_t floats);
|
||||
int ds4_tp_head_row_recv(ds4_tp *tp, uint32_t row, float *dst, uint32_t floats);
|
||||
int ds4_tp_send_verify_commit(ds4_tp *tp, int32_t mode, int32_t token_count);
|
||||
int ds4_tp_recv_verify_commit(ds4_tp *tp, int32_t *mode, int32_t *token_count);
|
||||
|
||||
/* Standalone worker mode entry. Loads nothing itself: the engine is already
|
||||
* open. */
|
||||
|
||||
@@ -1144,6 +1144,46 @@ static char *web_browser_ws_url(ds4_web *web, char *err, size_t err_len) {
|
||||
return ws;
|
||||
}
|
||||
|
||||
/* Count page-type CDP targets. Once the last tab is closed a visible Chrome
|
||||
has no window left, and a windowless Chrome rejects Target.createTarget
|
||||
requests that ask for a background tab in the (now absent) window. */
|
||||
static int web_page_target_count(ds4_web *web) {
|
||||
char err[160] = {0};
|
||||
char *body = web_http_request("GET", web->port, "/json/list", err, sizeof(err));
|
||||
if (!body) return -1;
|
||||
int n = 0;
|
||||
const char *p = body;
|
||||
while ((p = strstr(p, "\"type\"")) != NULL) {
|
||||
p += 6;
|
||||
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++;
|
||||
if (*p++ != ':') continue;
|
||||
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++;
|
||||
if (strncmp(p, "\"page\"", 6) == 0) n++;
|
||||
}
|
||||
free(body);
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Fallback target creation over plain HTTP. PUT /json/new works even when
|
||||
Chrome has no window, which is exactly when Target.createTarget fails.
|
||||
The verb must be PUT: modern Chrome answers GET /json/new with 405. */
|
||||
static char *web_create_target_http(ds4_web *web, const char *url,
|
||||
char *err, size_t err_len) {
|
||||
char *enc = web_url_encode(url);
|
||||
web_buf path = {0};
|
||||
web_buf_puts(&path, "/json/new?");
|
||||
web_buf_puts(&path, (enc && enc[0]) ? enc : "about%3Ablank");
|
||||
free(enc);
|
||||
char *path_s = web_buf_take(&path);
|
||||
char *body = web_http_request("PUT", web->port, path_s, err, err_len);
|
||||
free(path_s);
|
||||
if (!body) return NULL;
|
||||
char *id = web_json_get_string(body, "id");
|
||||
free(body);
|
||||
if (!id) web_set_err(err, err_len, "PUT /json/new returned no target id");
|
||||
return id;
|
||||
}
|
||||
|
||||
static bool web_open_tab(ds4_web *web, const char *url, web_tab *tab,
|
||||
char *err, size_t err_len) {
|
||||
memset(tab, 0, sizeof(*tab));
|
||||
@@ -1168,14 +1208,33 @@ static bool web_open_tab(ds4_web *web, const char *url, web_tab *tab,
|
||||
params_s, err, err_len);
|
||||
free(params_s);
|
||||
web_ws_close(&browser);
|
||||
if (!resp) return false;
|
||||
|
||||
tab->id = web_json_get_string(resp, "targetId");
|
||||
free(resp);
|
||||
if (resp) {
|
||||
tab->id = web_json_get_string(resp, "targetId");
|
||||
if (!tab->id) {
|
||||
char *why = web_json_get_string(resp, "message");
|
||||
web_log(web, why ? why : "Target.createTarget returned no target id");
|
||||
free(why);
|
||||
}
|
||||
free(resp);
|
||||
}
|
||||
|
||||
/* A windowless Chrome refuses Target.createTarget with newWindow:false,
|
||||
which is the state we land in the moment a previous tab is closed.
|
||||
Recover through the HTTP endpoint instead of failing the request. */
|
||||
if (!tab->id) {
|
||||
web_tab_free(tab);
|
||||
web_set_err(err, err_len, "Chrome did not return a page target id");
|
||||
return false;
|
||||
char herr[160] = {0};
|
||||
tab->id = web_create_target_http(web, url, herr, sizeof(herr));
|
||||
if (!tab->id) {
|
||||
web_tab_free(tab);
|
||||
if (herr[0])
|
||||
web_set_err(err, err_len,
|
||||
"Chrome did not return a page target id (%s)", herr);
|
||||
else
|
||||
web_set_err(err, err_len, "Chrome did not return a page target id");
|
||||
return false;
|
||||
}
|
||||
web_log(web, "recovered Chrome target creation over HTTP");
|
||||
}
|
||||
|
||||
char ws_url[PATH_MAX + 128];
|
||||
@@ -1187,6 +1246,16 @@ static bool web_open_tab(ds4_web *web, const char *url, web_tab *tab,
|
||||
|
||||
static void web_close_tab(ds4_web *web, const web_tab *tab) {
|
||||
if (!web || !tab || !tab->id || !tab->id[0]) return;
|
||||
|
||||
/* Never leave Chrome with zero page targets: that means no window, and a
|
||||
windowless browser is what breaks the next Target.createTarget. Keeping
|
||||
one tab alive is enough to hold the window open. */
|
||||
int remaining = web_page_target_count(web);
|
||||
if (remaining >= 0 && remaining <= 1) {
|
||||
web_log(web, "keeping last Chrome page target open");
|
||||
return;
|
||||
}
|
||||
|
||||
char *enc = web_url_encode(tab->id);
|
||||
web_buf path = {0};
|
||||
web_buf_puts(&path, "/json/close/");
|
||||
|
||||
@@ -12,3 +12,9 @@ quality-testing/data/*
|
||||
!quality-testing/data/flash/**
|
||||
!quality-testing/data/glm53-flash-openrouter-zai-fp8-100/
|
||||
!quality-testing/data/glm53-flash-openrouter-zai-fp8-100/**
|
||||
!quality-testing/data/flash-vision-exp-openrouter-novita-100/
|
||||
!quality-testing/data/flash-vision-exp-openrouter-novita-100/**
|
||||
!quality-testing/data/qwen38-flash-alibaba-100/
|
||||
!quality-testing/data/qwen38-flash-alibaba-100/**
|
||||
!quality-testing/data/qwen38-flash-alibaba-long/
|
||||
!quality-testing/data/qwen38-flash-alibaba-long/**
|
||||
+8
-6
@@ -12,9 +12,9 @@ NATIVE_CPU_FLAG ?= -mcpu=native
|
||||
QUANTS_SHARED := libds4quants.dylib
|
||||
SHARED_FLAGS := -dynamiclib
|
||||
QUALITY_LDLIBS := -lm -pthread -framework Foundation -framework Metal
|
||||
QUALITY_TARGETS := ds4.o ds4_metal.o ds4_distributed.o ds4_tp.o ds4_ssd.o ds4_layer_pack.o ds4_image.o rax.o ds4_gpu_args.o
|
||||
QUALITY_OBJS := ../ds4.o ../ds4_metal.o ../ds4_distributed.o ../ds4_tp.o ../ds4_ssd.o ../ds4_layer_pack.o ../ds4_image.o ../rax.o ../ds4_gpu_args.o
|
||||
QUALITY_LINK := $(CC) $(CFLAGS) -I.. -o
|
||||
QUALITY_TARGETS := ds4.o ds4_metal.o ds4_distributed.o ds4_tp.o ds4_ssd.o ds4_layer_pack.o ds4_engram.o ds4_image.o rax.o ds4_gpu_args.o
|
||||
QUALITY_OBJS := ../ds4.o ../ds4_metal.o ../ds4_distributed.o ../ds4_tp.o ../ds4_ssd.o ../ds4_layer_pack.o ../ds4_engram.o ../ds4_image.o ../rax.o ../ds4_gpu_args.o
|
||||
QUALITY_LINK = $(CC) $(CFLAGS) -I.. -o
|
||||
LLAMA_CPP_LDLIBS ?= -L$(LLAMA_CPP_BIN) -Wl,-rpath,$(abspath $(LLAMA_CPP_BIN)) \
|
||||
-lllama -lggml -lggml-base -lggml-cpu -lggml-blas -lggml-metal \
|
||||
-framework Foundation -framework Metal -framework Accelerate
|
||||
@@ -31,10 +31,10 @@ NVCC_ARCH_FLAGS := -arch=$(CUDA_ARCH)
|
||||
endif
|
||||
NVCCFLAGS ?= -O3 --use_fast_math $(NVCC_ARCH_FLAGS) -Xcompiler $(NATIVE_CPU_FLAG) -Xcompiler -pthread
|
||||
QUALITY_LDLIBS ?= -lm -Xcompiler -pthread -L$(CUDA_HOME)/targets/sbsa-linux/lib -L$(CUDA_HOME)/lib64 -lcudart -lcublas
|
||||
QUALITY_TARGETS := ds4.o ds4_cuda.o ds4_distributed.o ds4_tp.o ds4_ssd.o ds4_layer_pack.o ds4_image.o rax.o ds4_gpu_args.o \
|
||||
QUALITY_TARGETS := ds4.o ds4_cuda.o ds4_distributed.o ds4_tp.o ds4_ssd.o ds4_layer_pack.o ds4_engram.o ds4_image.o rax.o ds4_gpu_args.o \
|
||||
cuda/mmq/ds4_ggml_stubs.o cuda/mmq/ds4_mmq.o cuda/mmq/ds4_mmq_d2r.o \
|
||||
cuda/mmq/quantize.o cuda/mmq/mmid.o cuda/mmq/mmvq.o cuda/mmq/ds4_repack.o
|
||||
QUALITY_OBJS := ../ds4.o ../ds4_cuda.o ../ds4_distributed.o ../ds4_tp.o ../ds4_ssd.o ../ds4_layer_pack.o ../ds4_image.o ../rax.o ../ds4_gpu_args.o \
|
||||
QUALITY_OBJS := ../ds4.o ../ds4_cuda.o ../ds4_distributed.o ../ds4_tp.o ../ds4_ssd.o ../ds4_layer_pack.o ../ds4_engram.o ../ds4_image.o ../rax.o ../ds4_gpu_args.o \
|
||||
../cuda/mmq/ds4_ggml_stubs.o ../cuda/mmq/ds4_mmq.o ../cuda/mmq/ds4_mmq_d2r.o \
|
||||
../cuda/mmq/quantize.o ../cuda/mmq/mmid.o ../cuda/mmq/mmvq.o ../cuda/mmq/ds4_repack.o
|
||||
QUALITY_LINK := $(NVCC) $(NVCCFLAGS) -I.. -o
|
||||
@@ -45,10 +45,12 @@ endif
|
||||
CFLAGS ?= -O3 -Wall -Wextra -std=c11 $(NATIVE_CPU_FLAG)
|
||||
CPPFLAGS ?= -D_GNU_SOURCE
|
||||
|
||||
.PHONY: all clean quality-score quality-llama-score
|
||||
.PHONY: all clean quants-shared quality-score quality-llama-score
|
||||
|
||||
all: deepseek4-quantize $(QUANTS_SHARED)
|
||||
|
||||
quants-shared: $(QUANTS_SHARED)
|
||||
|
||||
deepseek4-quantize: deepseek4-quantize.c quants.c quants.h
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ deepseek4-quantize.c quants.c -lm -pthread
|
||||
|
||||
|
||||
+190
-1
@@ -14,6 +14,96 @@ The important pieces are:
|
||||
- `quality-testing/`: prompts and scripts used to compare local GGUF variants
|
||||
against official DeepSeek V4 Flash continuations.
|
||||
|
||||
## Qwen3.8 IQ2_XXS experiment
|
||||
|
||||
`qwen4_iq2.py` builds mixed IQ2_XXS/MXFP4 main weights with
|
||||
embedded MTP. It quantizes the 48 trunk layers' gate/up experts directly from
|
||||
the official BF16 checkpoint using the same pinned imatrix as the Q4_K pack.
|
||||
It then copies every other tensor from an existing combined Q4_K `qwen4exp`
|
||||
GGUF, including MXFP4 down projections, dense/control weights and MTP.
|
||||
This isolates the gate/up precision change for a quality comparison.
|
||||
|
||||
The Python environment needs NumPy and `huggingface_hub`. Build the existing
|
||||
native quantizer library with `make -C gguf-tools quants-shared`, then run:
|
||||
|
||||
```sh
|
||||
python3 gguf-tools/qwen4_iq2.py quantize \
|
||||
--source /path/to/official-bf16-checkpoint \
|
||||
--imatrix /path/to/imatrix_unsloth.gguf_file \
|
||||
--library gguf-tools/libds4quants.dylib \
|
||||
--experts-dir /path/to/iq2-experts --threads 8
|
||||
|
||||
python3 gguf-tools/qwen4_iq2.py assemble \
|
||||
--template /path/to/combined-q4k-mtp-pleext.gguf \
|
||||
--experts-dir /path/to/iq2-experts \
|
||||
--out /path/to/Qwen3.8-Flash-Next-IQ2XXSImatrix-MXFP4Down-MTP.gguf
|
||||
```
|
||||
|
||||
The first stage can run beside the source checkpoint on another machine;
|
||||
transfer the complete experts directory, including `experts.json`, for
|
||||
assembly. Repeating the first command verifies completed tensors before
|
||||
resuming. Assembly refuses existing outputs and verifies every written tensor
|
||||
before publishing the completed GGUF and its JSON manifest.
|
||||
|
||||
The intermediate main model is approximately 50.3 GB. Finish it with the
|
||||
[native n-gram packer](#native-qwen-n-grams) before inference. Quantization
|
||||
changes model outputs; fitting weights on disk does not establish a 64 GB
|
||||
runtime fit or acceptable reasoning quality. Compare with the same evaluator
|
||||
settings using `ds4-eval --suite hard`.
|
||||
### Experimental padded Q2_K down projections
|
||||
|
||||
Use `--projection down` on both stages to quantize the 48 trunk down
|
||||
projections directly from BF16, with the pinned imatrix. Their 640 logical
|
||||
inputs are zero-padded to 768 for three Q2_K blocks per row. Assemble against
|
||||
the existing IQ2_XXS/MXFP4 combined model, using a new experts directory and
|
||||
output path. All other tensors, including MTP, are copied unchanged:
|
||||
|
||||
```sh
|
||||
uv run --with numpy --with huggingface_hub python gguf-tools/qwen4_iq2.py quantize \
|
||||
--projection down --source /path/to/official-bf16-checkpoint \
|
||||
--imatrix /path/to/imatrix_unsloth.gguf_file \
|
||||
--library gguf-tools/libds4quants.dylib \
|
||||
--experts-dir /path/to/q2-down-experts --threads 8
|
||||
uv run --with numpy --with huggingface_hub python gguf-tools/qwen4_iq2.py assemble \
|
||||
--projection down --template /path/to/IQ2XXSImatrix-MXFP4Down-MTP.gguf \
|
||||
--experts-dir /path/to/q2-down-experts \
|
||||
--out /path/to/IQ2XXSImatrix-Q2KDownPad768-MTP.gguf
|
||||
```
|
||||
|
||||
This layout requires the padded-down Qwen runtime support. The GGUF stores
|
||||
physical dimensions `[768, 2560, 512]`; the architecture's expert width remains
|
||||
640. Metal decode and prefill use the padded weight stride while reading only
|
||||
640 activation columns. The CPU reference supplies zero activation padding.
|
||||
The shared expert and MTP retain their original dimensions and formats.
|
||||
Saving 88 bytes per trunk down row reduces the main model by 5.15625 GiB;
|
||||
quality and speed must be evaluated for this new recipe.
|
||||
|
||||
### Native Qwen n-grams
|
||||
|
||||
Finish main-only packs with the original BF16 n-gram shards, not the old
|
||||
quantized sidecar:
|
||||
|
||||
```sh
|
||||
python3 gguf-tools/qwen4_native_ngrams.py \
|
||||
--model /path/to/IQ2XXSImatrix-Q2KDownPad768-MTP.gguf \
|
||||
--source /path/to/Qwen3.8-Flash-Next \
|
||||
--source-revision de4b8e4d43b917e7706784d8bb445c9af86a3540 \
|
||||
--output gguf/Qwen3.8-Flash-Next-Q2.gguf
|
||||
```
|
||||
|
||||
The source directory needs its index and the safetensors shards containing
|
||||
`ple.ple_embedding` tensors. The packer checks the hash constants, copies all
|
||||
main/MTP tensor bytes unchanged, and appends the original BF16 table at a
|
||||
page-aligned offset. It verifies every copied payload before publishing the
|
||||
file and its checksum report. Allow space for the complete new file plus
|
||||
16 GiB spare. An incomplete output is never a runnable release artifact.
|
||||
|
||||
For the Q4 pack, use its main GGUF as `--model`. `--source` can also be a
|
||||
previously verified native n-gram GGUF from the same pinned checkpoint.
|
||||
This avoids keeping a second copy of the original safetensors. The final
|
||||
Q2 and Q4 files include 95.37 GiB of disk-only n-grams; no table precision
|
||||
is lost, and the main-model quantization is unchanged.
|
||||
|
||||
## Build
|
||||
|
||||
```sh
|
||||
@@ -119,6 +209,67 @@ gguf-tools/deepseek4-quantize \
|
||||
`--compare-tensor` regenerates a single tensor and byte-compares it against the
|
||||
template or `--compare-gguf`. `--threads N` controls routed-expert workers.
|
||||
|
||||
## Convert DeepSeek V4.1 Flash
|
||||
|
||||
V4.1 uses its own converter; a V4 template is not compatible. Install NumPy,
|
||||
tokenizers and SymPy, then build the quantizer library:
|
||||
|
||||
```sh
|
||||
make -C gguf-tools libds4quants.dylib
|
||||
python3 gguf-tools/deepseek41_quantize.py \
|
||||
--hf models/DeepSeek-V4.1-Flash \
|
||||
--source-revision df42c109f1defefcbfcedbe7d905718a12266e40 \
|
||||
--out gguf/DeepSeek-V4.1-Flash-IQ2_XXS-Q2_K-bootstrap.gguf --dry-run
|
||||
```
|
||||
|
||||
Omit `--dry-run` to write the file. Add `--resume` after an interrupted conversion.
|
||||
Use `libds4quants.so` on Linux. Gate/up experts use IQ2_XXS and down experts use
|
||||
Q2_K; attention, shared experts and the output head use Q8_0. `--quant q4` uses
|
||||
Q4_K experts instead and `--quant mxfp4` keeps the released MXFP4 experts
|
||||
unchanged, repacked into GGUF blocks without calibration. Engram rows retain
|
||||
their original FP8 values and scales, packed together at the end of the GGUF for
|
||||
disk lookups. Vision and DSpark weights are not included.
|
||||
|
||||
The first conversion uses weight-energy importance for IQ2_XXS. After runtime
|
||||
calibration, add `--imatrix FILE` and choose a new output filename to regenerate
|
||||
from the original safetensors. Do not requantize the first GGUF.
|
||||
|
||||
For Q4, add `--quant q4` and use `DeepSeek-V4.1-Flash-Q4.gguf` as the output.
|
||||
This changes only the routed experts to Q4_K; the other tensor formats and
|
||||
disk-only Engram layout stay the same. The same imatrix works for both recipes.
|
||||
|
||||
Check the finished artifact against the pinned source before running it:
|
||||
|
||||
```sh
|
||||
python3 gguf-tools/deepseek41_validate_gguf.py \
|
||||
--hf models/DeepSeek-V4.1-Flash \
|
||||
--source-revision df42c109f1defefcbfcedbe7d905718a12266e40 \
|
||||
--gguf gguf/DeepSeek-V4.1-Flash-IQ2_XXS-Q2_K-bootstrap.gguf --payload
|
||||
```
|
||||
|
||||
For a calibrated file, pass the same `--imatrix FILE` used during conversion.
|
||||
Pass `--quant q4` to the audit as well when checking a Q4 file.
|
||||
The audit checks the complete layout, all non-expert tensors, sampled experts
|
||||
and native Engram rows. It does not replace [inference quality tests](quality-testing/deepseek-v4.1-flash-20260910/README.md).
|
||||
|
||||
### Convert V4.1 DSpark
|
||||
|
||||
The V4.1 support converter needs NumPy, the official config and tensor index,
|
||||
and only the shards containing `mtp.*` tensors (44, 45 and 46 of the pinned
|
||||
checkpoint). No imatrix or main-model GGUF is needed:
|
||||
|
||||
```sh
|
||||
python3 gguf-tools/deepseek41_dspark.py \
|
||||
--hf models/DeepSeek-V4.1-Flash \
|
||||
--source-revision df42c109f1defefcbfcedbe7d905718a12266e40 \
|
||||
--out gguf/DeepSeek-V4.1-Flash-DSpark.gguf
|
||||
```
|
||||
|
||||
The result is 8.04 GiB. Routed experts keep their native MXFP4 payloads;
|
||||
dense weights expand losslessly to F16 or F32. Add `--dry-run` to inspect the
|
||||
layout, or `--verify` to compare every tensor in an existing output against
|
||||
the source. This file works only with the V4.1 Flash target.
|
||||
|
||||
## Convert A DSpark Support Checkpoint
|
||||
|
||||
The DSpark Flash checkpoint is published as Hugging Face safetensors and stores
|
||||
@@ -136,7 +287,7 @@ The manifest reads only `model.safetensors.index.json`; it does not require the
|
||||
large shard files to be present. The final summary should report three DSpark
|
||||
stages and zero unknown DSpark tensors before attempting a full conversion.
|
||||
|
||||
To build the support GGUF used by `ds4 --mtp`, run the DSpark support mode. This
|
||||
To build the support GGUF used by `ds4 --dspark`, run the DSpark support mode. This
|
||||
mode writes standalone DSpark metadata plus the packed `mtp.*` tensor payloads;
|
||||
it does not require a base-model GGUF template:
|
||||
|
||||
@@ -147,6 +298,12 @@ gguf-tools/deepseek4-quantize \
|
||||
--out DeepSeek-V4-Flash-DSpark-support-0731.gguf
|
||||
```
|
||||
|
||||
Add `--experts mxfp4` to preserve the checkpoint's routed expert weights
|
||||
without requantization. Use a different output filename, such as
|
||||
`DeepSeek-V4-Flash-DSpark-MXFP4-support-0731.gguf`. This takes about 10.1 GiB
|
||||
instead of 5.6 GiB; the other tensors keep the same formats. A more accurate
|
||||
drafter can improve acceptance, but does not guarantee faster generation.
|
||||
|
||||
`--dspark-support --dry-run` reads safetensors shard headers to derive exact
|
||||
GGUF shapes and types, but it does not read tensor payloads. The DSpark metadata
|
||||
defaults match the published Flash DSpark config: block size 5, target layers
|
||||
@@ -191,3 +348,35 @@ make -C gguf-tools quality-score
|
||||
gguf-tools/quality-testing/score_official MODEL.gguf gguf-tools/quality-testing/data/manifest.tsv /tmp/model.tsv 4096
|
||||
python3 gguf-tools/quality-testing/compare_scores.py /tmp/old.tsv /tmp/new.tsv
|
||||
```
|
||||
|
||||
## Qwen3.8 Flash Next
|
||||
|
||||
`qwen4_exp_convert.py` wraps llama.cpp's `convert_hf_to_gguf.py` and writes
|
||||
the `qwen4exp` schema DS4 loads, including the MTP block as `blk.<n>.nextn.*`.
|
||||
It needs a llama.cpp master checkout (b96806d96 or newer, found through
|
||||
`--llama-cpp` or `$LLAMA_CPP`) and a Python with torch, safetensors and
|
||||
transformers, such as llama.cpp's own venv:
|
||||
|
||||
```sh
|
||||
python gguf-tools/qwen4_exp_convert.py --src /path/to/Qwen3.8-Flash-Next \
|
||||
--source-revision de4b8e4d43b917e7706784d8bb445c9af86a3540 \
|
||||
--out Qwen3.8-Flash-Next-Q8.gguf --outtype q8_0
|
||||
python gguf-tools/qwen4_exp_convert.py --src /path/to/Qwen3.8-Flash-Next \
|
||||
--source-revision de4b8e4d43b917e7706784d8bb445c9af86a3540 \
|
||||
--out Qwen3.8-Flash-Next-MXFP4.gguf --outtype q8_0 --experts mxfp4
|
||||
```
|
||||
|
||||
Options: `--outtype q8_0|f32` (dense projections), `--experts
|
||||
q8_0|mxfp4|q4_k|f32` (routed experts; `--experts-down` picks the 640-wide down
|
||||
projection type when the gate/up type needs 256-wide rows), `--hc-type f16|f32|q8_0`
|
||||
(hyper-connection mixers), `--indexer bf16|f16|q8_0|f32` (the QSA indexer
|
||||
projections, kept at the released BF16 by default), `--no-mtp` and
|
||||
`--dry-run`. Norms, conv kernels, `ssm_a`, dt biases and the routers stay F32.
|
||||
N-grams always retain the original BF16 bytes. The converter finishes through
|
||||
the native packer above; its temporary main-weight file is removed on success.
|
||||
`gen_qwen4_unicode.py` regenerates `ds4_qwen4_unicode.inc` for the `qwen35`
|
||||
pre-tokenizer from a current `regex` release.
|
||||
|
||||
`make test-qwen4-kernels` runs the Metal kernel tests and
|
||||
`make test-qwen4-vision` checks the vision tower against the HF implementation
|
||||
(`tests/qwen4_vision_ref.py`).
|
||||
+177
-15
@@ -46,8 +46,19 @@
|
||||
#define DS4_KV_QUANTIZE_IMATRIX_N_ENTRIES "quantize.imatrix.entries_count"
|
||||
#define DS4_KV_QUANTIZE_IMATRIX_N_CHUNKS "quantize.imatrix.chunks_count"
|
||||
#define DS4_KV_COMPRESS_RATIOS "deepseek4.attention.compress_ratios"
|
||||
#define DS4_KV_RMS_EPS "deepseek4.attention.layer_norm_rms_epsilon"
|
||||
#define DS4_KV_CHECKPOINT_VARIANT "deepseek4.checkpoint_variant"
|
||||
#define DS4_KV_VISION_SIDECAR_REQUIRED "deepseek4.vision.sidecar_required"
|
||||
#define DS4_KV_SOURCE_URL "general.source.url"
|
||||
#define DS4_KV_SOURCE_REVISION "general.source.revision"
|
||||
#define DS4_GGUF_DEFAULT_ALIGNMENT 32
|
||||
|
||||
#define DS4_VISION_EXP_SOURCE_URL \
|
||||
"https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash-Vision-Exp"
|
||||
#define DS4_VISION_EXP_SOURCE_REVISION \
|
||||
"e46e16bf6035c6f317eb2ac7458eb0362926d402"
|
||||
#define DS4_VISION_EXP_NAME "DeepSeek V4 Flash Vision Experimental"
|
||||
|
||||
typedef enum {
|
||||
GGUF_TYPE_UINT8 = 0,
|
||||
GGUF_TYPE_INT8 = 1,
|
||||
@@ -351,12 +362,31 @@ static int64_t json_i64(const json_doc *d, int tok) {
|
||||
return strtoll(tmp, NULL, 10);
|
||||
}
|
||||
|
||||
static double json_f64(const json_doc *d, int tok) {
|
||||
char tmp[128];
|
||||
const int n = d->v[tok].end - d->v[tok].start;
|
||||
if (n <= 0 || n >= (int)sizeof(tmp)) die("bad JSON number");
|
||||
memcpy(tmp, d->js + d->v[tok].start, (size_t)n);
|
||||
tmp[n] = '\0';
|
||||
errno = 0;
|
||||
char *end = NULL;
|
||||
double value = strtod(tmp, &end);
|
||||
if (errno != 0 || end == tmp || *end != '\0' || !isfinite(value)) {
|
||||
die("bad JSON number");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint32_t *compress_ratios;
|
||||
uint64_t n_compress_ratios;
|
||||
float rms_eps;
|
||||
bool vision_exp;
|
||||
char *source_revision;
|
||||
} hf_model_metadata;
|
||||
|
||||
static hf_model_metadata load_hf_model_metadata(const char *hf_dir) {
|
||||
static hf_model_metadata load_hf_model_metadata(const char *hf_dir,
|
||||
const char *source_revision) {
|
||||
hf_model_metadata m = {0};
|
||||
char *path = path_join(hf_dir, "config.json");
|
||||
size_t len = 0;
|
||||
@@ -386,6 +416,36 @@ static hf_model_metadata load_hf_model_metadata(const char *hf_dir) {
|
||||
m.compress_ratios[j++] = (uint32_t)value;
|
||||
}
|
||||
|
||||
int rms_eps = json_obj_get(&d, 0, "rms_norm_eps");
|
||||
if (rms_eps < 0) {
|
||||
fprintf(stderr, "error: missing rms_norm_eps in %s\n", path);
|
||||
exit(1);
|
||||
}
|
||||
double rms = json_f64(&d, rms_eps);
|
||||
if (rms <= 0.0 || rms > 1.0) {
|
||||
fprintf(stderr, "error: invalid rms_norm_eps in %s: %.9g\n", path, rms);
|
||||
exit(1);
|
||||
}
|
||||
m.rms_eps = (float)rms;
|
||||
|
||||
int vision_layers = json_obj_get(&d, 0, "vision_n_layers");
|
||||
if (vision_layers >= 0) {
|
||||
int64_t count_layers = json_i64(&d, vision_layers);
|
||||
if (count_layers != 32) {
|
||||
fprintf(stderr, "error: unsupported vision_n_layers in %s: %" PRId64 "\n",
|
||||
path, count_layers);
|
||||
exit(1);
|
||||
}
|
||||
m.vision_exp = true;
|
||||
if (!source_revision || strcmp(source_revision, DS4_VISION_EXP_SOURCE_REVISION) != 0) {
|
||||
fprintf(stderr,
|
||||
"error: Vision-Exp conversion requires --source-revision %s\n",
|
||||
DS4_VISION_EXP_SOURCE_REVISION);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
if (source_revision) m.source_revision = xstrdup(source_revision);
|
||||
|
||||
json_free(&d);
|
||||
free(text);
|
||||
free(path);
|
||||
@@ -394,6 +454,7 @@ static hf_model_metadata load_hf_model_metadata(const char *hf_dir) {
|
||||
|
||||
static void free_hf_model_metadata(hf_model_metadata *m) {
|
||||
free(m->compress_ratios);
|
||||
free(m->source_revision);
|
||||
memset(m, 0, sizeof(*m));
|
||||
}
|
||||
|
||||
@@ -1669,6 +1730,15 @@ static void write_u64(FILE *fp, uint64_t v) {
|
||||
if (fwrite(&v, sizeof(v), 1, fp) != 1) die("write u64 failed");
|
||||
}
|
||||
|
||||
static void write_f32(FILE *fp, float v) {
|
||||
if (fwrite(&v, sizeof(v), 1, fp) != 1) die("write f32 failed");
|
||||
}
|
||||
|
||||
static void write_bool(FILE *fp, bool v) {
|
||||
const uint8_t value = v ? 1u : 0u;
|
||||
if (fwrite(&value, sizeof(value), 1, fp) != 1) die("write bool failed");
|
||||
}
|
||||
|
||||
static void write_gguf_string(FILE *fp, const char *s) {
|
||||
uint64_t n = strlen(s);
|
||||
write_u64(fp, n);
|
||||
@@ -1733,6 +1803,7 @@ static gguf_file load_gguf_metadata_with_override(const char *path,
|
||||
byte_span *kv_keep = xcalloc((size_t)g.n_kv, sizeof(kv_keep[0]));
|
||||
uint64_t n_kv_keep = 0;
|
||||
bool found_compress_ratios = false;
|
||||
bool found_rms_eps = false;
|
||||
|
||||
off_t kv_start = ftello(fp);
|
||||
if (kv_start < 0) die("GGUF ftell failed");
|
||||
@@ -1742,6 +1813,7 @@ static gguf_file load_gguf_metadata_with_override(const char *path,
|
||||
char *key = read_gguf_string_fp(fp);
|
||||
uint32_t type = read_u32_le_fp(fp, "GGUF KV type");
|
||||
if (strcmp(key, DS4_KV_COMPRESS_RATIOS) == 0) found_compress_ratios = true;
|
||||
if (strcmp(key, DS4_KV_RMS_EPS) == 0) found_rms_eps = true;
|
||||
if (strcmp(key, "general.alignment") == 0 && type == GGUF_TYPE_UINT32) {
|
||||
uint32_t a = read_u32_le_fp(fp, "GGUF alignment");
|
||||
if (a) g.alignment = a;
|
||||
@@ -1765,7 +1837,15 @@ static gguf_file load_gguf_metadata_with_override(const char *path,
|
||||
* otherwise the output can contain duplicate GGUF metadata with stale
|
||||
* and new values.
|
||||
*/
|
||||
const bool replace_from_config = metadata && strcmp(key, DS4_KV_COMPRESS_RATIOS) == 0;
|
||||
const bool replace_from_config = metadata &&
|
||||
(strcmp(key, DS4_KV_COMPRESS_RATIOS) == 0 ||
|
||||
strcmp(key, DS4_KV_RMS_EPS) == 0 ||
|
||||
(metadata->vision_exp &&
|
||||
(strcmp(key, "general.name") == 0 ||
|
||||
strcmp(key, DS4_KV_SOURCE_URL) == 0 ||
|
||||
strcmp(key, DS4_KV_SOURCE_REVISION) == 0 ||
|
||||
strcmp(key, DS4_KV_CHECKPOINT_VARIANT) == 0 ||
|
||||
strcmp(key, DS4_KV_VISION_SIDECAR_REQUIRED) == 0)));
|
||||
if (!is_imatrix_kv_key(key) && !replace_from_config) {
|
||||
kv_keep[n_kv_keep++] = (byte_span){
|
||||
.start = (size_t)(rec_start - kv_start),
|
||||
@@ -1777,6 +1857,9 @@ static gguf_file load_gguf_metadata_with_override(const char *path,
|
||||
if (metadata && !found_compress_ratios) {
|
||||
die("template has no deepseek4.attention.compress_ratios metadata");
|
||||
}
|
||||
if (metadata && !found_rms_eps) {
|
||||
die("template has no deepseek4.attention.layer_norm_rms_epsilon metadata");
|
||||
}
|
||||
if (metadata && g.n_layers > metadata->n_compress_ratios) {
|
||||
die("config.json compress_ratios is shorter than the template layer count");
|
||||
}
|
||||
@@ -1852,8 +1935,25 @@ static uint64_t fnv1a64_bytes(const uint8_t *data, size_t n) {
|
||||
}
|
||||
|
||||
static size_t extra_hf_metadata_kv_size(const hf_model_metadata *metadata) {
|
||||
return gguf_string_size(DS4_KV_COMPRESS_RATIOS) + 4 + 4 + 8 +
|
||||
(size_t)metadata->n_compress_ratios * 4;
|
||||
size_t n = gguf_string_size(DS4_KV_COMPRESS_RATIOS) + 4 + 4 + 8 +
|
||||
(size_t)metadata->n_compress_ratios * 4;
|
||||
n += gguf_string_size(DS4_KV_RMS_EPS) + 4 + sizeof(float);
|
||||
if (metadata->vision_exp) {
|
||||
n += gguf_string_size("general.name") + 4 +
|
||||
gguf_string_size(DS4_VISION_EXP_NAME);
|
||||
n += gguf_string_size(DS4_KV_SOURCE_URL) + 4 +
|
||||
gguf_string_size(DS4_VISION_EXP_SOURCE_URL);
|
||||
n += gguf_string_size(DS4_KV_SOURCE_REVISION) + 4 +
|
||||
gguf_string_size(metadata->source_revision);
|
||||
n += gguf_string_size(DS4_KV_CHECKPOINT_VARIANT) + 4 +
|
||||
gguf_string_size("vision-exp");
|
||||
n += gguf_string_size(DS4_KV_VISION_SIDECAR_REQUIRED) + 4 + 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static uint64_t extra_hf_metadata_kv_count(const hf_model_metadata *metadata) {
|
||||
return 2u + (metadata->vision_exp ? 5u : 0u);
|
||||
}
|
||||
|
||||
static void write_hf_metadata_kvs(FILE *fp, const hf_model_metadata *metadata) {
|
||||
@@ -1864,6 +1964,26 @@ static void write_hf_metadata_kvs(FILE *fp, const hf_model_metadata *metadata) {
|
||||
for (uint64_t i = 0; i < metadata->n_compress_ratios; i++) {
|
||||
write_u32(fp, metadata->compress_ratios[i]);
|
||||
}
|
||||
write_gguf_string(fp, DS4_KV_RMS_EPS);
|
||||
write_u32(fp, GGUF_TYPE_FLOAT32);
|
||||
write_f32(fp, metadata->rms_eps);
|
||||
if (metadata->vision_exp) {
|
||||
write_gguf_string(fp, "general.name");
|
||||
write_u32(fp, GGUF_TYPE_STRING);
|
||||
write_gguf_string(fp, DS4_VISION_EXP_NAME);
|
||||
write_gguf_string(fp, DS4_KV_SOURCE_URL);
|
||||
write_u32(fp, GGUF_TYPE_STRING);
|
||||
write_gguf_string(fp, DS4_VISION_EXP_SOURCE_URL);
|
||||
write_gguf_string(fp, DS4_KV_SOURCE_REVISION);
|
||||
write_u32(fp, GGUF_TYPE_STRING);
|
||||
write_gguf_string(fp, metadata->source_revision);
|
||||
write_gguf_string(fp, DS4_KV_CHECKPOINT_VARIANT);
|
||||
write_u32(fp, GGUF_TYPE_STRING);
|
||||
write_gguf_string(fp, "vision-exp");
|
||||
write_gguf_string(fp, DS4_KV_VISION_SIDECAR_REQUIRED);
|
||||
write_u32(fp, GGUF_TYPE_BOOL);
|
||||
write_bool(fp, true);
|
||||
}
|
||||
}
|
||||
|
||||
static output_context build_output_context(const gguf_file *tmpl, const quant_policy *policy,
|
||||
@@ -1871,7 +1991,8 @@ static output_context build_output_context(const gguf_file *tmpl, const quant_po
|
||||
const hf_model_metadata *metadata) {
|
||||
output_context out = {0};
|
||||
out.n_tensors = tmpl->n_tensors;
|
||||
out.n_kv_extra = 1 + extra_imatrix_kv_count(im);
|
||||
out.n_kv_extra = extra_hf_metadata_kv_count(metadata) +
|
||||
extra_imatrix_kv_count(im);
|
||||
out.alignment = tmpl->alignment;
|
||||
out.tensors = xcalloc((size_t)out.n_tensors, sizeof(out.tensors[0]));
|
||||
size_t tensor_info = 0;
|
||||
@@ -2013,6 +2134,7 @@ typedef struct {
|
||||
char *compare_gguf;
|
||||
char *compare_tensor;
|
||||
char *imatrix_file;
|
||||
char *source_revision;
|
||||
quant_policy policy;
|
||||
int n_experts;
|
||||
int n_threads;
|
||||
@@ -2118,6 +2240,7 @@ static const dspark_name_rule dspark_stage_rules[] = {
|
||||
|
||||
{"ffn.gate.weight", "ffn_gate_inp.weight", "emit"},
|
||||
{"ffn.gate.bias", "exp_probs_b.bias", "emit"},
|
||||
{"ffn.gate.bias_vl", "visual_router_bias", "consume_visual_sidecar"},
|
||||
{"ffn_norm.weight", "ffn_norm.weight", "emit"},
|
||||
{"ffn.shared_experts.w1.weight", "ffn_gate_shexp.weight", "emit"},
|
||||
{"ffn.shared_experts.w1.scale", "ffn_gate_shexp.weight", "consume_scale"},
|
||||
@@ -2385,10 +2508,13 @@ static void dspark_shape_reversed_from_info(tensor_meta *m, const st_info *info)
|
||||
}
|
||||
|
||||
static void dspark_plan_set_size(dspark_tensor_plan *tp) {
|
||||
if (tp->meta.type != DS4Q_TYPE_I32 && !is_quantizable_target(tp->meta.type)) {
|
||||
const bool preserved_mxfp4 =
|
||||
tp->kind == DSPARK_PLAN_EXPERT && tp->meta.type == DS4Q_TYPE_MXFP4;
|
||||
if (tp->meta.type != DS4Q_TYPE_I32 &&
|
||||
!is_quantizable_target(tp->meta.type) && !preserved_mxfp4) {
|
||||
die("unsupported DSpark planned tensor type");
|
||||
}
|
||||
if (ds4q_can_quantize(tp->meta.type) &&
|
||||
if ((ds4q_can_quantize(tp->meta.type) || preserved_mxfp4) &&
|
||||
tp->meta.ne[0] % ds4q_block_size(tp->meta.type) != 0) {
|
||||
fprintf(stderr,
|
||||
"error: DSpark tensor %s ne[0]=%" PRId64
|
||||
@@ -2502,13 +2628,17 @@ static void write_gguf_kv_u32_array(FILE *fp, const char *key, const uint32_t *v
|
||||
}
|
||||
|
||||
static void dspark_plan_finalize(dspark_support_plan *plan,
|
||||
const dspark_support_options *opt) {
|
||||
const dspark_support_options *opt,
|
||||
const hf_model_metadata *metadata) {
|
||||
qsort(plan->tensors, (size_t)plan->len, sizeof(plan->tensors[0]), dspark_plan_cmp);
|
||||
plan->alignment = DS4_GGUF_DEFAULT_ALIGNMENT;
|
||||
plan->n_kv = 9;
|
||||
const char *name = metadata->vision_exp ?
|
||||
"DeepSeek V4 Flash Vision Experimental DSpark support" :
|
||||
"DeepSeek V4 Flash DSpark support";
|
||||
plan->n_kv = 9 + (metadata->vision_exp ? 3 : 0);
|
||||
plan->kv_bytes =
|
||||
gguf_kv_size_string("general.architecture", "deepseek4-dspark") +
|
||||
gguf_kv_size_string("general.name", "DeepSeek V4 Flash DSpark support") +
|
||||
gguf_kv_size_string("general.name", name) +
|
||||
gguf_kv_size_u32("general.alignment") +
|
||||
gguf_kv_size_u32("dspark.block_size") +
|
||||
gguf_kv_size_u32("dspark.markov_rank") +
|
||||
@@ -2516,6 +2646,15 @@ static void dspark_plan_finalize(dspark_support_plan *plan,
|
||||
gguf_kv_size_u32_array("dspark.target_layer_ids", opt->target_layer_count) +
|
||||
gguf_kv_size_u32("dspark.stage_count") +
|
||||
gguf_kv_size_u32("dspark.n_layers");
|
||||
if (metadata->vision_exp) {
|
||||
plan->kv_bytes +=
|
||||
gguf_kv_size_string(DS4_KV_SOURCE_URL,
|
||||
DS4_VISION_EXP_SOURCE_URL) +
|
||||
gguf_kv_size_string(DS4_KV_SOURCE_REVISION,
|
||||
metadata->source_revision) +
|
||||
gguf_kv_size_string(DS4_KV_CHECKPOINT_VARIANT,
|
||||
"vision-exp");
|
||||
}
|
||||
|
||||
size_t tensor_info = 0;
|
||||
size_t off = 0;
|
||||
@@ -2538,7 +2677,8 @@ static void dspark_plan_finalize(dspark_support_plan *plan,
|
||||
static dspark_support_plan build_dspark_support_plan(st_db *db,
|
||||
const quant_policy *policy,
|
||||
const dspark_support_options *opt,
|
||||
int requested_n_experts) {
|
||||
int requested_n_experts,
|
||||
const hf_model_metadata *metadata) {
|
||||
(void)opt;
|
||||
dspark_support_plan plan = {0};
|
||||
str_list names = load_index_weight_names(db->hf_dir);
|
||||
@@ -2589,7 +2729,7 @@ static dspark_support_plan build_dspark_support_plan(st_db *db,
|
||||
}
|
||||
}
|
||||
}
|
||||
dspark_plan_finalize(&plan, opt);
|
||||
dspark_plan_finalize(&plan, opt, metadata);
|
||||
return plan;
|
||||
}
|
||||
|
||||
@@ -2646,6 +2786,7 @@ static byte_buf generate_dspark_tensor(st_db *db, const dspark_tensor_plan *tp,
|
||||
static void write_dspark_support_gguf(st_db *db,
|
||||
const dspark_support_plan *plan,
|
||||
const dspark_support_options *opt,
|
||||
const hf_model_metadata *metadata,
|
||||
const char *out_path,
|
||||
int n_threads,
|
||||
const imatrix_store *imatrix) {
|
||||
@@ -2656,7 +2797,11 @@ static void write_dspark_support_gguf(st_db *db,
|
||||
write_u64(fp, (uint64_t)plan->len);
|
||||
write_u64(fp, plan->n_kv);
|
||||
write_gguf_kv_string(fp, "general.architecture", "deepseek4-dspark");
|
||||
write_gguf_kv_string(fp, "general.name", "DeepSeek V4 Flash DSpark support");
|
||||
write_gguf_kv_string(
|
||||
fp, "general.name",
|
||||
metadata->vision_exp ?
|
||||
"DeepSeek V4 Flash Vision Experimental DSpark support" :
|
||||
"DeepSeek V4 Flash DSpark support");
|
||||
write_gguf_kv_u32(fp, "general.alignment", (uint32_t)plan->alignment);
|
||||
write_gguf_kv_u32(fp, "dspark.block_size", opt->block_size);
|
||||
write_gguf_kv_u32(fp, "dspark.markov_rank", opt->markov_rank);
|
||||
@@ -2664,6 +2809,14 @@ static void write_dspark_support_gguf(st_db *db,
|
||||
write_gguf_kv_u32_array(fp, "dspark.target_layer_ids", opt->target_layers, opt->target_layer_count);
|
||||
write_gguf_kv_u32(fp, "dspark.stage_count", (uint32_t)plan->stages);
|
||||
write_gguf_kv_u32(fp, "dspark.n_layers", (uint32_t)plan->stages);
|
||||
if (metadata->vision_exp) {
|
||||
write_gguf_kv_string(fp, DS4_KV_SOURCE_URL,
|
||||
DS4_VISION_EXP_SOURCE_URL);
|
||||
write_gguf_kv_string(fp, DS4_KV_SOURCE_REVISION,
|
||||
metadata->source_revision);
|
||||
write_gguf_kv_string(fp, DS4_KV_CHECKPOINT_VARIANT,
|
||||
"vision-exp");
|
||||
}
|
||||
|
||||
for (int i = 0; i < plan->len; i++) {
|
||||
const tensor_meta *t = &plan->tensors[i].meta;
|
||||
@@ -2731,6 +2884,7 @@ static void usage(const char *argv0) {
|
||||
printf(" --dspark-target-layers CSV DSpark target layer ids metadata, default 40,41,42\n");
|
||||
printf(" --imatrix FILE legacy .dat imatrix from ds4 --imatrix-out\n");
|
||||
printf(" --imatrix-strict fail if a quantized tensor has no matching imatrix vector\n");
|
||||
printf(" --source-revision SHA pin the Hugging Face source revision in output metadata\n");
|
||||
printf(" --experts TYPE set routed w1/w2/w3 expert tensors to TYPE\n");
|
||||
printf(" --routed-w1 TYPE routed gate expert tensor type\n");
|
||||
printf(" --routed-w2 TYPE routed down expert tensor type\n");
|
||||
@@ -2843,6 +2997,8 @@ static params parse_args(int argc, char **argv) {
|
||||
p.imatrix_file = need_value(argc, argv, &i, arg);
|
||||
} else if (strcmp(arg, "--imatrix-strict") == 0) {
|
||||
p.imatrix_strict = true;
|
||||
} else if (strcmp(arg, "--source-revision") == 0) {
|
||||
p.source_revision = need_value(argc, argv, &i, arg);
|
||||
} else if (strcmp(arg, "--experts") == 0 || strcmp(arg, "--routed") == 0) {
|
||||
ds4q_type t = parse_type(need_value(argc, argv, &i, arg));
|
||||
p.policy.routed_w1 = p.policy.routed_w2 = p.policy.routed_w3 = t;
|
||||
@@ -3013,10 +3169,13 @@ int main(int argc, char **argv) {
|
||||
if (p.imatrix_file) imatrix_load(&imatrix, p.imatrix_file, p.imatrix_strict);
|
||||
|
||||
if (p.dspark_support) {
|
||||
hf_model_metadata metadata = load_hf_model_metadata(p.hf_dir,
|
||||
p.source_revision);
|
||||
st_db db;
|
||||
db_open(&db, p.hf_dir);
|
||||
dspark_support_plan plan =
|
||||
build_dspark_support_plan(&db, &p.policy, &p.dspark, p.n_experts);
|
||||
build_dspark_support_plan(&db, &p.policy, &p.dspark,
|
||||
p.n_experts, &metadata);
|
||||
print_dspark_support_plan(&plan, &p.dspark);
|
||||
if (p.dry_run) {
|
||||
/* Plan only: build_dspark_support_plan reads shard headers, not tensor payloads. */
|
||||
@@ -3026,6 +3185,7 @@ int main(int argc, char **argv) {
|
||||
write_dspark_support_gguf(&db,
|
||||
&plan,
|
||||
&p.dspark,
|
||||
&metadata,
|
||||
p.out_gguf,
|
||||
p.n_threads,
|
||||
&imatrix);
|
||||
@@ -3033,13 +3193,15 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
free_dspark_support_plan(&plan);
|
||||
db_close(&db);
|
||||
free_hf_model_metadata(&metadata);
|
||||
imatrix_free(&imatrix);
|
||||
for (int i = 0; i < p.policy.n_overrides; i++) free(p.policy.overrides[i].prefix);
|
||||
free(p.policy.overrides);
|
||||
return 0;
|
||||
}
|
||||
|
||||
hf_model_metadata metadata = load_hf_model_metadata(p.hf_dir);
|
||||
hf_model_metadata metadata = load_hf_model_metadata(p.hf_dir,
|
||||
p.source_revision);
|
||||
gguf_file tmpl = load_gguf_metadata_with_override(p.template_gguf, &metadata);
|
||||
if (p.n_experts <= 0) {
|
||||
if (tmpl.n_experts > 0) {
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extract DeepSeek V4.1 Flash DSpark without further weight quantization.
|
||||
|
||||
Routed experts retain their native MXFP4 codes and scales. Dense weights are
|
||||
expanded to F16 only when every value is exactly representable, otherwise F32.
|
||||
The target model supplies the token embedding and output projection at runtime.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
|
||||
from deepseek41_quantize import NativeQuantizer, scale_name
|
||||
from glm53_quantize import (
|
||||
SourceDB, TensorPlan, QTYPE_F16, QTYPE_F32, QTYPE_MXFP4, align,
|
||||
conversion_signature, kv_string, kv_u32, kv_u32_array, load_resume_state,
|
||||
print_plan, qtype_nbytes, save_resume_state, tensor_header,
|
||||
read_exact,
|
||||
)
|
||||
|
||||
ALIGNMENT = 16384
|
||||
|
||||
|
||||
class LosslessWeights(NativeQuantizer):
|
||||
def __init__(self):
|
||||
# No C quantizer or imatrix is needed: this converter changes layouts,
|
||||
# never weight values.
|
||||
import numpy as np
|
||||
self.np = np
|
||||
self.fp8_lut = self._build_fp8_lut()
|
||||
|
||||
def dense_type(self, db, name):
|
||||
values = self.to_f32(db, name)
|
||||
with self.np.errstate(over="ignore", under="ignore"):
|
||||
restored = values.astype(self.np.float16).astype(self.np.float32)
|
||||
return QTYPE_F16 if self.np.array_equal(values, restored) else QTYPE_F32
|
||||
|
||||
def dense_bytes(self, db, item):
|
||||
values = self.to_f32(db, item.source)
|
||||
if item.qtype == QTYPE_F16:
|
||||
with self.np.errstate(over="ignore", under="ignore"):
|
||||
restored = values.astype(self.np.float16).astype(self.np.float32)
|
||||
if not self.np.array_equal(values, restored):
|
||||
raise ValueError(f"{item.name}: F16 would change source values")
|
||||
return self.encode(values, item.qtype)
|
||||
|
||||
def expert_bytes(self, db, name):
|
||||
np = self.np
|
||||
info, sinfo = db.info(name), db.info(scale_name(name))
|
||||
rows, packed_cols = info["shape"]
|
||||
if (info["dtype"] != "I8" or packed_cols % 16 or
|
||||
sinfo["dtype"] != "F8_E8M0" or
|
||||
sinfo["shape"] != [rows, packed_cols // 16]):
|
||||
raise ValueError(f"{name}: invalid native FP4 tensor or scales")
|
||||
codes = np.frombuffer(db.read(name), dtype=np.uint8).reshape(-1, 16)
|
||||
scales = np.frombuffer(db.read(scale_name(name)), dtype=np.uint8)
|
||||
if np.any(scales == 255):
|
||||
raise ValueError(f"{name}: nonfinite MXFP4 scale")
|
||||
# Source nibbles are consecutive. GGUF puts values 0..15 in the low
|
||||
# nibbles, 16..31 in the high nibbles, after the unchanged E8M0 byte.
|
||||
unpacked = np.empty((codes.shape[0], 32), dtype=np.uint8)
|
||||
unpacked[:, 0::2], unpacked[:, 1::2] = codes & 15, codes >> 4
|
||||
packed = np.empty((codes.shape[0], 17), dtype=np.uint8)
|
||||
packed[:, 0] = scales
|
||||
packed[:, 1:] = unpacked[:, :16] | (unpacked[:, 16:] << 4)
|
||||
if not (np.array_equal(packed[:, 1:] & 15, unpacked[:, :16]) and
|
||||
np.array_equal(packed[:, 1:] >> 4, unpacked[:, 16:])):
|
||||
raise ValueError(f"{name}: MXFP4 repacking changed a code")
|
||||
return packed.tobytes()
|
||||
|
||||
|
||||
def build_plan(db, config, weights):
|
||||
if config.get("model_type") != "deepseek_v41":
|
||||
raise ValueError("expected a DeepSeek V4.1 checkpoint")
|
||||
c = config["text_config"]
|
||||
if config["quantization_config"]["weight_block_size"] != [32, 32]:
|
||||
raise ValueError("expected native 32x32 FP8 blocks")
|
||||
dim, inter, hc = c["hidden_size"], c["moe_intermediate_size"], c["hc_mult"]
|
||||
heads, hd = c["num_attention_heads"], c["head_dim"]
|
||||
qrank, orank, groups = c["q_lora_rank"], c["o_lora_rank"], c["o_groups"]
|
||||
experts = c["dspark_n_routed_experts"]
|
||||
stages, vocab, rank = c["num_nextn_predict_layers"], c["vocab_size"], c["dspark_markov_rank"]
|
||||
targets = c["dspark_target_layer_ids"]
|
||||
if (stages != 3 or len(targets) != 3 or len(set(targets)) != 3 or
|
||||
any(not 0 <= layer < c["num_hidden_layers"] for layer in targets) or
|
||||
not 0 < c["dspark_num_experts_per_tok"] <= experts or
|
||||
not 0 <= c["dspark_noise_token_id"] < vocab or
|
||||
not 0 < c["dspark_block_size"] <= c["sliding_window"]):
|
||||
raise ValueError("invalid DSpark dimensions or target layers")
|
||||
plan, consumed = [], set()
|
||||
|
||||
def claim(name, shape, dtype=None):
|
||||
info = db.info(name)
|
||||
if info["shape"] != list(shape) or (dtype and info["dtype"] != dtype):
|
||||
raise ValueError(f"{name}: unexpected {info['dtype']} {info['shape']}")
|
||||
consumed.add(name)
|
||||
if info["dtype"] in ("I8", "F8_E4M3"):
|
||||
expected = ([shape[0], shape[1] // 16] if info["dtype"] == "I8"
|
||||
else [(n + 31) // 32 for n in shape])
|
||||
scale = db.info(scale_name(name))
|
||||
if scale["dtype"] != "F8_E8M0" or scale["shape"] != expected:
|
||||
raise ValueError(f"{name}: invalid native scale shape")
|
||||
consumed.add(scale_name(name))
|
||||
|
||||
def regular(dst, src, shape, role):
|
||||
claim(src, shape)
|
||||
# Keep norms, routers, HC and confidence in F32 for the runtime's
|
||||
# FP32 reductions. Matrix F16 is selected only after a lossless scan.
|
||||
qt = weights.dense_type(db, src) if role in ("projection", "markov") else QTYPE_F32
|
||||
plan.append(TensorPlan(dst, tuple(reversed(shape)), qt, role, source=src))
|
||||
|
||||
for stage in range(stages):
|
||||
prefix = f"mtp.{stage}"
|
||||
for site in ("attn", "ffn"):
|
||||
for part, shape in (("fn", (hc * (hc + 2), hc * dim)),
|
||||
("base", (hc * (hc + 2),)), ("scale", (3,))):
|
||||
regular(f"{prefix}.hc_{site}_{part}.weight", f"{prefix}.hc_{site}_{part}", shape, "hc")
|
||||
regular(f"{prefix}.{site}_norm.weight", f"{prefix}.{site}_norm.weight", (dim,), "norm")
|
||||
for dst, src, shape, role in (
|
||||
("attn_sinks.weight", "attn_sink", (heads,), "norm"),
|
||||
("attn_q_a.weight", "wq_a.weight", (qrank, dim), "projection"),
|
||||
("attn_q_b.weight", "wq_b.weight", (heads * hd, qrank), "projection"),
|
||||
("attn_q_a_norm.weight", "q_norm.weight", (qrank,), "norm"),
|
||||
("attn_kv.weight", "wkv.weight", (hd, dim), "projection"),
|
||||
("attn_kv_a_norm.weight", "kv_norm.weight", (hd,), "norm"),
|
||||
("attn_output_a.weight", "wo_a.weight", (groups * orank, heads * hd // groups), "projection"),
|
||||
("attn_output_b.weight", "wo_b.weight", (dim, groups * orank), "projection"),
|
||||
):
|
||||
regular(f"{prefix}.{dst}", f"{prefix}.attn.{src}", shape, role)
|
||||
regular(f"{prefix}.ffn_gate_inp.weight", f"{prefix}.ffn.gate.weight", (experts, dim), "router")
|
||||
for suffix, dst in (("bias", "exp_probs_b.bias"), ("bias_vl", "exp_probs_b_vl.bias")):
|
||||
regular(f"{prefix}.{dst}", f"{prefix}.ffn.gate.{suffix}", (experts,), "router")
|
||||
for part, src, shape in (("gate", "w1", (inter, dim)),
|
||||
("up", "w3", (inter, dim)), ("down", "w2", (dim, inter))):
|
||||
regular(f"{prefix}.ffn_{part}_shexp.weight", f"{prefix}.ffn.shared_experts.{src}.weight", shape, "projection")
|
||||
pattern = f"{prefix}.ffn.experts.{{expert}}.{src}.weight"
|
||||
for expert in range(experts):
|
||||
claim(pattern.format(expert=expert), (shape[0], shape[1] // 2), "I8")
|
||||
plan.append(TensorPlan(f"{prefix}.ffn_{part}_exps.weight", (*reversed(shape), experts),
|
||||
QTYPE_MXFP4, "experts", source=pattern, expert_layer=stage,
|
||||
expert_part=part, expert_count=experts))
|
||||
if stage == 0:
|
||||
regular(f"{prefix}.main_proj.weight", f"{prefix}.main_proj.weight", (dim, dim * len(targets)), "projection")
|
||||
regular(f"{prefix}.main_norm.weight", f"{prefix}.main_norm.weight", (dim,), "norm")
|
||||
if stage == stages - 1:
|
||||
regular(f"{prefix}.norm.weight", f"{prefix}.norm.weight", (dim,), "norm")
|
||||
for src, dst in (("embed", "markov_w1"), ("head", "markov_w2")):
|
||||
regular(f"{prefix}.markov_head.{dst}.weight", f"{prefix}.markov_head.{src}.weight", (vocab, rank), "markov")
|
||||
regular(f"{prefix}.confidence_head.proj.weight", f"{prefix}.confidence_head.proj.weight", (1, dim + rank), "confidence")
|
||||
unknown = {name for name in db.tensors if name.startswith("mtp.")} - consumed
|
||||
if unknown:
|
||||
raise ValueError(f"unclaimed DSpark tensors: {sorted(unknown)[:10]}")
|
||||
offset = 0
|
||||
for item in plan:
|
||||
item.offset, item.nbytes = offset, qtype_nbytes(item.qtype, item.shape)
|
||||
offset += align(item.nbytes, ALIGNMENT)
|
||||
return plan
|
||||
|
||||
|
||||
def metadata(config, revision):
|
||||
c = config["text_config"]
|
||||
records = [
|
||||
kv_string("general.architecture", "deepseek41-dspark"),
|
||||
kv_string("general.name", "DeepSeek V4.1 Flash DSpark"),
|
||||
kv_string("general.source.url", "https://huggingface.co/deepseek-ai/DeepSeek-V4.1-Flash"),
|
||||
kv_string("general.source.revision", revision),
|
||||
kv_u32("general.alignment", ALIGNMENT),
|
||||
kv_string("dspark.target_architecture", "deepseek41"),
|
||||
kv_string("dspark.source_config", json.dumps(config, sort_keys=True, separators=(",", ":"))),
|
||||
kv_string("dspark.weight_encoding", "native MXFP4; lossless F16/F32 expansion"),
|
||||
kv_u32_array("dspark.target_layer_ids", c["dspark_target_layer_ids"]),
|
||||
]
|
||||
for key in ("block_size", "markov_rank", "noise_token_id", "n_routed_experts", "num_experts_per_tok"):
|
||||
records.append(kv_u32(f"dspark.{key}", c[f"dspark_{key}"]))
|
||||
return records
|
||||
|
||||
|
||||
def source_fingerprint(db):
|
||||
shards = sorted({info["shard"] for name, info in db.tensors.items() if name.startswith("mtp.")})
|
||||
result = []
|
||||
for shard in shards:
|
||||
h = hashlib.sha256()
|
||||
with open(os.path.join(db.hf_dir, shard), "rb") as fp:
|
||||
for chunk in iter(lambda: fp.read(8 << 20), b""):
|
||||
h.update(chunk)
|
||||
result.append((shard, h.hexdigest()))
|
||||
return result
|
||||
|
||||
|
||||
def write_gguf(args, db, weights, plan, records):
|
||||
data_start, data_bytes = print_plan(plan, records, [], ALIGNMENT)
|
||||
if os.path.exists(args.out):
|
||||
raise ValueError(f"refusing to overwrite {args.out}")
|
||||
partial, journal = args.out + ".partial", args.out + ".partial.json"
|
||||
fingerprint = source_fingerprint(db)
|
||||
signature = hashlib.sha256((conversion_signature(plan, records, [], None) +
|
||||
json.dumps(fingerprint)).encode()).hexdigest()
|
||||
completed = 0
|
||||
if os.path.exists(partial) or os.path.exists(journal):
|
||||
if not args.resume or not (os.path.exists(partial) and os.path.exists(journal)):
|
||||
raise ValueError("partial file and journal require --resume")
|
||||
completed = load_resume_state(journal, signature, plan)
|
||||
end = data_start + (plan[completed - 1].offset + align(plan[completed - 1].nbytes, ALIGNMENT) if completed else 0)
|
||||
if shutil.disk_usage(os.path.dirname(os.path.abspath(args.out))).free < data_start + data_bytes - end + (32 << 30):
|
||||
raise ValueError("insufficient disk space for output plus 32 GiB reserve")
|
||||
header = b"GGUF" + struct.pack("<IQQ", 3, len(plan), len(records))
|
||||
header += b"".join(records) + b"".join(tensor_header(item) for item in plan)
|
||||
header += bytes(data_start - len(header))
|
||||
if os.path.exists(partial):
|
||||
with open(partial, "rb") as fp:
|
||||
if fp.read(data_start) != header or os.fstat(fp.fileno()).st_size < end:
|
||||
raise ValueError("partial GGUF is truncated or has a different header")
|
||||
else:
|
||||
with open(partial, "xb") as fp:
|
||||
fp.write(header)
|
||||
fp.flush()
|
||||
os.fsync(fp.fileno())
|
||||
save_resume_state(journal, signature, 0)
|
||||
with open(partial, "r+b") as fp:
|
||||
fp.truncate(end)
|
||||
fp.seek(end)
|
||||
for i in range(completed, len(plan)):
|
||||
item, start = plan[i], time.monotonic()
|
||||
if fp.tell() != data_start + item.offset:
|
||||
raise ValueError(f"incorrect offset for {item.name}")
|
||||
if item.is_expert:
|
||||
for expert in range(item.expert_count):
|
||||
fp.write(weights.expert_bytes(db, item.source.format(expert=expert)))
|
||||
else:
|
||||
fp.write(weights.dense_bytes(db, item))
|
||||
if fp.tell() != data_start + item.offset + item.nbytes:
|
||||
raise ValueError(f"incorrect payload size for {item.name}")
|
||||
fp.write(bytes(align(item.nbytes, ALIGNMENT) - item.nbytes))
|
||||
fp.flush()
|
||||
os.fsync(fp.fileno())
|
||||
save_resume_state(journal, signature, i + 1)
|
||||
print(f"[{i + 1}/{len(plan)}] {item.name}: {time.monotonic() - start:.1f}s", flush=True)
|
||||
# Refuse publication if an in-place source edit occurred during conversion.
|
||||
if source_fingerprint(db) != fingerprint:
|
||||
raise ValueError("source shards changed during conversion")
|
||||
os.rename(partial, args.out)
|
||||
os.unlink(journal)
|
||||
|
||||
|
||||
def verify_gguf(path, db, weights, plan, records):
|
||||
"""Check the complete layout and every payload, not a sample of experts."""
|
||||
with open(path, "rb") as fp:
|
||||
if read_exact(fp, 24, "header") != b"GGUF" + struct.pack("<IQQ", 3, len(plan), len(records)):
|
||||
raise ValueError("GGUF header differs from source plan")
|
||||
encoded = b"".join(records) + b"".join(tensor_header(t) for t in plan)
|
||||
if read_exact(fp, len(encoded), "metadata and tensors") != encoded:
|
||||
raise ValueError("GGUF metadata or tensor layout differs from source plan")
|
||||
start = align(fp.tell(), ALIGNMENT)
|
||||
size = start + plan[-1].offset + align(plan[-1].nbytes, ALIGNMENT)
|
||||
if os.fstat(fp.fileno()).st_size != size:
|
||||
raise ValueError("incorrect GGUF file size")
|
||||
for item in plan:
|
||||
fp.seek(start + item.offset)
|
||||
if item.is_expert:
|
||||
for expert in range(item.expert_count):
|
||||
expected = weights.expert_bytes(db, item.source.format(expert=expert))
|
||||
if read_exact(fp, len(expected), item.name) != expected:
|
||||
raise ValueError(f"{item.name}: expert {expert} payload differs from source")
|
||||
else:
|
||||
expected = weights.dense_bytes(db, item)
|
||||
if read_exact(fp, len(expected), item.name) != expected:
|
||||
raise ValueError(f"{item.name}: dense payload differs from source")
|
||||
print(f"PASS: all {len(plan)} tensor layouts and payloads match the source", flush=True)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--hf", required=True)
|
||||
parser.add_argument("--out", required=True)
|
||||
parser.add_argument("--source-revision", required=True)
|
||||
parser.add_argument("--resume", action="store_true")
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
parser.add_argument("--verify", action="store_true", help="audit an existing --out file without writing it")
|
||||
args = parser.parse_args()
|
||||
if not re.fullmatch("[0-9a-f]{40}", args.source_revision):
|
||||
parser.error("source revision must be a full commit hash")
|
||||
with open(os.path.join(args.hf, "config.json"), "rb") as fp:
|
||||
config = json.load(fp)
|
||||
db = SourceDB(args.hf, index_validator=lambda _: None, scale_validator=lambda _: None,
|
||||
tensor_prefix="mtp.")
|
||||
try:
|
||||
weights = LosslessWeights()
|
||||
plan = build_plan(db, config, weights)
|
||||
records = metadata(config, args.source_revision)
|
||||
if args.verify:
|
||||
verify_gguf(args.out, db, weights, plan, records)
|
||||
elif args.dry_run:
|
||||
print_plan(plan, records, [], ALIGNMENT)
|
||||
else:
|
||||
write_gguf(args, db, weights, plan, records)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except (ValueError, OSError) as error:
|
||||
sys.exit(f"deepseek41-dspark: {error}")
|
||||
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python3
|
||||
"""DeepSeek V4.1 tokenizer and Engram metadata, derived from the released config.
|
||||
|
||||
The normalization, prime allocation and seeded hash multipliers follow DeepSeek's
|
||||
MIT-licensed inference/engram.py. The runtime consumes the resulting arrays, not
|
||||
a second implementation of Unicode normalization or NumPy's random generator.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import struct
|
||||
|
||||
from glm53_quantize import (
|
||||
GGUF_ARRAY, GGUF_STRING, GGUF_UINT32, GGUF_UINT64, kv_bool, kv_string,
|
||||
kv_f32, kv_u32, kv_u32_array, pack_string,
|
||||
)
|
||||
|
||||
# Apple Silicon VM pages are 16 KiB. The Engram extent must be removable
|
||||
# without also unmapping bytes of the preceding resident tensor.
|
||||
GGUF_ALIGNMENT = 16384
|
||||
|
||||
|
||||
def array_record(key, kind, values):
|
||||
header = pack_string(key) + struct.pack("<IIQ", GGUF_ARRAY, kind, len(values))
|
||||
if kind == GGUF_STRING:
|
||||
return header + b"".join(pack_string(value) for value in values)
|
||||
fmt = {GGUF_UINT32: "I", GGUF_UINT64: "Q", 5: "i", 6: "f"}[kind]
|
||||
return header + struct.pack(f"<{len(values)}{fmt}", *values)
|
||||
|
||||
|
||||
def compressed_token_map(tokenizer):
|
||||
from tokenizers import Regex, normalizers
|
||||
|
||||
sentinel = "\ue000"
|
||||
normalizer = normalizers.Sequence([
|
||||
normalizers.NFKC(), normalizers.NFD(), normalizers.StripAccents(),
|
||||
normalizers.Lowercase(), normalizers.Replace(Regex(r"[ \t\r\n]+"), " "),
|
||||
normalizers.Replace(Regex(r"^ $"), sentinel), normalizers.Strip(),
|
||||
normalizers.Replace(sentinel, " "),
|
||||
])
|
||||
unique, result = {}, []
|
||||
for token_id in range(tokenizer.get_vocab_size(with_added_tokens=True)):
|
||||
text = tokenizer.decode([token_id], skip_special_tokens=False)
|
||||
if "\ufffd" in text:
|
||||
key = tokenizer.id_to_token(token_id)
|
||||
else:
|
||||
key = normalizer.normalize_str(text) or text
|
||||
result.append(unique.setdefault(key, len(unique)))
|
||||
return result, len(unique)
|
||||
|
||||
|
||||
def engram_layout(config, tokenizer):
|
||||
import numpy as np
|
||||
from sympy import nextprime
|
||||
|
||||
token_map, vocab_size = compressed_token_map(tokenizer)
|
||||
if vocab_size != config["engram_compressed_vocab_size"]:
|
||||
raise ValueError(f"Engram vocabulary mismatch: {vocab_size}")
|
||||
if len(token_map) != config["vocab_size"]:
|
||||
raise ValueError("Engram requires every released tokenizer ID")
|
||||
layers = config["engram_layer_ids"]
|
||||
if len(layers) != 2 or config["engram_max_ngram_size"] != 4 or \
|
||||
config["engram_n_heads"] != 8 or config["engram_head_dim"] != 256:
|
||||
raise ValueError("unsupported Engram shape")
|
||||
seen, primes, multipliers = set(), [], []
|
||||
bound = (np.iinfo(np.int64).max // vocab_size) // 2
|
||||
for layer in layers:
|
||||
row = []
|
||||
for _ in range(config["engram_max_ngram_size"] - 1):
|
||||
current = config["engram_vocab_size"] - 1
|
||||
for _ in range(config["engram_n_heads"]):
|
||||
current = int(nextprime(current))
|
||||
while current in seen:
|
||||
current = int(nextprime(current))
|
||||
seen.add(current)
|
||||
row.append(current)
|
||||
primes.append(row)
|
||||
rng = np.random.default_rng(10007 * layer)
|
||||
multipliers.append((rng.integers(0, bound, size=4, dtype=np.int64) * 2 + 1).tolist())
|
||||
if [sum(row) for row in primes] != config["engram_num_embeddings"]:
|
||||
raise ValueError("Engram table size differs from hash bucket layout")
|
||||
return dict(token_map=token_map, compressed_vocab_size=vocab_size,
|
||||
pad_id=token_map[config["engram_pad_token_id"]],
|
||||
rows=config["engram_num_embeddings"], layers=layers,
|
||||
primes=primes, multipliers=multipliers)
|
||||
|
||||
|
||||
def metadata(hf_dir, revision):
|
||||
from tokenizers import Tokenizer
|
||||
|
||||
with open(os.path.join(hf_dir, "config.json"), "rb") as fp:
|
||||
config = json.load(fp)
|
||||
if config["model_type"] != "deepseek_v41":
|
||||
raise ValueError("not a DeepSeek V4.1 source checkpoint")
|
||||
text = config["text_config"]
|
||||
tokenizer = Tokenizer.from_file(os.path.join(hf_dir, "tokenizer.json"))
|
||||
layout = engram_layout(text, tokenizer)
|
||||
with open(os.path.join(hf_dir, "tokenizer.json"), "rb") as fp:
|
||||
tok = json.load(fp)
|
||||
if tok["model"]["type"] != "BPE":
|
||||
raise ValueError("unsupported tokenizer")
|
||||
# This tokenizer lists special tokens in both the BPE vocabulary and
|
||||
# added_tokens. Its parsed ID table resolves that intentional overlap.
|
||||
tokens = [tokenizer.id_to_token(i) for i in range(text["vocab_size"])]
|
||||
if any(token is None for token in tokens):
|
||||
raise ValueError("tokenizer has missing IDs")
|
||||
special = {item["id"] for item in tok["added_tokens"] if item["special"]}
|
||||
merges = [" ".join(pair) if isinstance(pair, list) else pair for pair in tok["model"]["merges"]]
|
||||
records = [
|
||||
kv_string("general.architecture", "deepseek41"),
|
||||
kv_string("general.name", "DeepSeek V4.1 Flash"),
|
||||
kv_string("general.source.url", "https://huggingface.co/deepseek-ai/DeepSeek-V4.1-Flash"),
|
||||
kv_string("general.source.revision", revision),
|
||||
kv_u32("general.alignment", GGUF_ALIGNMENT),
|
||||
kv_string("deepseek41.config", json.dumps(config, sort_keys=True, separators=(",", ":"))),
|
||||
kv_string("deepseek41.engram.encoding", "e4m3_e8m0_32_row264"),
|
||||
kv_u32_array("deepseek41.engram.layer_ids", layout["layers"]),
|
||||
kv_u32_array("deepseek41.engram.rows", layout["rows"]),
|
||||
kv_u32("deepseek41.engram.compressed_vocab_size", layout["compressed_vocab_size"]),
|
||||
kv_u32("deepseek41.engram.pad_id", layout["pad_id"]),
|
||||
kv_u32_array("deepseek41.engram.token_map", layout["token_map"]),
|
||||
kv_u32_array("deepseek41.engram.primes", sum(layout["primes"], [])),
|
||||
array_record("deepseek41.engram.multipliers", GGUF_UINT64, sum(layout["multipliers"], [])),
|
||||
kv_string("tokenizer.ggml.model", "gpt2"),
|
||||
kv_string("tokenizer.ggml.pre", "joyai-llm"),
|
||||
array_record("tokenizer.ggml.tokens", GGUF_STRING, tokens),
|
||||
array_record("tokenizer.ggml.token_type", 5, [3 if i in special else 1 for i in range(len(tokens))]),
|
||||
array_record("tokenizer.ggml.merges", GGUF_STRING, merges),
|
||||
kv_u32("tokenizer.ggml.bos_token_id", config["bos_token_id"]),
|
||||
kv_u32("tokenizer.ggml.eos_token_id", config["eos_token_id"]),
|
||||
kv_bool("tokenizer.ggml.add_bos_token", True),
|
||||
kv_bool("tokenizer.ggml.add_eos_token", False),
|
||||
]
|
||||
for key in (
|
||||
"vocab_size", "hidden_size", "moe_intermediate_size", "num_hidden_layers",
|
||||
"num_attention_heads", "num_key_value_heads", "head_dim", "qk_rope_head_dim",
|
||||
"q_lora_rank", "o_lora_rank", "o_groups", "n_routed_experts", "n_shared_experts",
|
||||
"num_experts_per_tok", "max_position_embeddings", "sliding_window",
|
||||
"index_n_heads", "index_head_dim", "index_topk", "candidate_source_layer_id",
|
||||
"candidate_topk_blocks", "candidate_block_size", "hc_mult", "hc_sinkhorn_iters",
|
||||
"rope_theta", "compress_rope_theta",
|
||||
):
|
||||
records.append(kv_u32(f"deepseek41.{key}", text[key]))
|
||||
for key in ("rms_norm_eps", "hc_eps", "swiglu_limit", "routed_scaling_factor"):
|
||||
records.append(kv_f32(f"deepseek41.{key}", text[key]))
|
||||
for key in ("scoring_func", "hidden_act", "topk_method"):
|
||||
records.append(kv_string(f"deepseek41.{key}", text[key]))
|
||||
records.append(kv_bool("deepseek41.norm_topk_prob", text["norm_topk_prob"]))
|
||||
records.append(kv_u32_array("deepseek41.compress_ratios", text["compress_ratios"][:text["num_hidden_layers"]]))
|
||||
for key in ("kv_source_layer_ids", "index_source_layer_ids"):
|
||||
records.append(kv_u32_array(f"deepseek41.{key}", text[key]))
|
||||
for key in ("factor", "beta_fast", "beta_slow", "original_max_position_embeddings"):
|
||||
records.append(kv_f32(f"deepseek41.rope_scaling.{key}", text["rope_scaling"][key]))
|
||||
return config, records
|
||||
@@ -0,0 +1,357 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Convert DeepSeek V4.1 Flash to DwarfStar's Q2 or Q4_K GGUF.
|
||||
|
||||
Uses the project's C quantizers. Engram FP8 rows and their scales are packed
|
||||
losslessly at the end of the file, outside the main model's resident extent.
|
||||
Vision and DSpark are separate models and are not part of this text GGUF.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import concurrent.futures
|
||||
import dataclasses
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
|
||||
from deepseek41_metadata import GGUF_ALIGNMENT, metadata
|
||||
from glm53_quantize import (
|
||||
SourceDB, TensorPlan, Quantizer, Imatrix, QTYPE_F32, QTYPE_F16,
|
||||
QTYPE_Q8_0, QTYPE_Q2_K, QTYPE_Q4_K, QTYPE_IQ2_XXS, QTYPE_I8, QTYPE_MXFP4, align,
|
||||
conversion_signature, kv_string, load_resume_state, print_plan,
|
||||
qtype_nbytes, save_resume_state, tensor_header,
|
||||
)
|
||||
|
||||
QUANTIZATION = {
|
||||
"q2": "IQ2_XXS gate/up; Q2_K down; Q8_0 attention/shared/head",
|
||||
"q4": "Q4_K gate/up/down; Q8_0 attention/shared/head",
|
||||
"mxfp4": "released MXFP4 gate/up/down; Q8_0 attention/shared/head",
|
||||
}
|
||||
|
||||
|
||||
def scale_name(name):
|
||||
return name.removesuffix(".weight") + ".scale"
|
||||
|
||||
|
||||
def validate_scales(tensors):
|
||||
for name, info in tensors.items():
|
||||
if not name.startswith(("layers.", "embed.", "head.", "norm.")):
|
||||
continue
|
||||
dtype, shape = info["dtype"], info["shape"]
|
||||
if dtype not in ("F8_E4M3", "I8") or not name.endswith(".weight"):
|
||||
continue
|
||||
if len(shape) != 2:
|
||||
raise ValueError(f"{name}: expected a matrix")
|
||||
if dtype == "I8":
|
||||
expected = [shape[0], shape[1] // 16]
|
||||
elif ".engram.embed." in name:
|
||||
expected = [shape[0], shape[1] // 32]
|
||||
else:
|
||||
expected = [(dim + 31) // 32 for dim in shape]
|
||||
scale = tensors.get(scale_name(name))
|
||||
if not scale or scale["dtype"] != "F8_E8M0" or scale["shape"] != expected:
|
||||
raise ValueError(f"{name}: expected E8M0 scales {expected}")
|
||||
|
||||
|
||||
def build_plan(db, config, quant="q2"):
|
||||
if quant not in QUANTIZATION:
|
||||
raise ValueError(f"unknown quantization recipe: {quant}")
|
||||
c = config["text_config"]
|
||||
if config["quantization_config"]["weight_block_size"] != [32, 32]:
|
||||
raise ValueError("expected native 32x32 FP8 blocks")
|
||||
dim, inter = c["hidden_size"], c["moe_intermediate_size"]
|
||||
heads, hd = c["num_attention_heads"], c["head_dim"]
|
||||
qrank, orank, groups = c["q_lora_rank"], c["o_lora_rank"], c["o_groups"]
|
||||
hc, experts, vocab = c["hc_mult"], c["n_routed_experts"], c["vocab_size"]
|
||||
ih, idim = c["index_n_heads"], c["index_head_dim"]
|
||||
plan, disk, consumed = [], [], set()
|
||||
|
||||
def claim(name, expected, dtype=None):
|
||||
info = db.info(name)
|
||||
if info["shape"] != list(expected) or (dtype and info["dtype"] != dtype):
|
||||
raise ValueError(f"{name}: unexpected {info['dtype']} {info['shape']}, expected {dtype} {expected}")
|
||||
consumed.add(name)
|
||||
if info["dtype"] in ("I8", "F8_E4M3"):
|
||||
consumed.add(scale_name(name))
|
||||
return info
|
||||
|
||||
def regular(name, source, shape, qtype, role):
|
||||
claim(source, shape)
|
||||
plan.append(TensorPlan(name, tuple(reversed(shape)), qtype, role, source=source))
|
||||
|
||||
regular("token_embd.weight", "embed.weight", (vocab, dim), QTYPE_F16, "embedding")
|
||||
regular("output_norm.weight", "norm.weight", (dim,), QTYPE_F32, "norm")
|
||||
regular("output.weight", "head.weight", (vocab, dim), QTYPE_Q8_0, "output")
|
||||
for layer in range(c["num_hidden_layers"]):
|
||||
src, dst = f"layers.{layer}", f"blk.{layer}"
|
||||
for site in ("attn", "ffn"):
|
||||
for part, shape, qt in (("fn", (hc * (hc + 2), hc * dim), QTYPE_F16),
|
||||
("base", (hc * (hc + 2),), QTYPE_F32),
|
||||
("scale", (3,), QTYPE_F32)):
|
||||
regular(f"{dst}.hc_{site}_{part}.weight", f"{src}.hc_{site}_{part}", shape, qt, "mhc")
|
||||
regular(f"{dst}.{site}_norm.weight", f"{src}.{site}_norm.weight", (dim,), QTYPE_F32, "norm")
|
||||
for target, source, shape, qt in (
|
||||
("attn_sinks.weight", "attn_sink", (heads,), QTYPE_F32),
|
||||
("attn_q_a.weight", "wq_a.weight", (qrank, dim), QTYPE_Q8_0),
|
||||
("attn_q_b.weight", "wq_b.weight", (heads * hd, qrank), QTYPE_Q8_0),
|
||||
("attn_q_a_norm.weight", "q_norm.weight", (qrank,), QTYPE_F32),
|
||||
("attn_kv.weight", "wkv.weight", (hd, dim), QTYPE_Q8_0),
|
||||
("attn_kv_a_norm.weight", "kv_norm.weight", (hd,), QTYPE_F32),
|
||||
("attn_output_a.weight", "wo_a.weight", (groups * orank, heads * hd // groups), QTYPE_Q8_0),
|
||||
("attn_output_b.weight", "wo_b.weight", (dim, groups * orank), QTYPE_Q8_0),
|
||||
):
|
||||
regular(f"{dst}.{target}", f"{src}.attn.{source}", shape, qt, "attention")
|
||||
if layer in c["kv_source_layer_ids"]:
|
||||
for target, source, shape, qt in (
|
||||
("attn_compressor_kv.weight", "compressor.wkv.weight", (hd, dim), QTYPE_F16),
|
||||
("attn_compressor_norm.weight", "compressor.norm.weight", (hd,), QTYPE_F32),
|
||||
("indexer.attn_k.weight", "indexer.wk.weight", (idim, hd), QTYPE_F16),
|
||||
("indexer.k_norm.weight", "indexer.k_norm.weight", (idim,), QTYPE_F32),
|
||||
):
|
||||
regular(f"{dst}.{target}", f"{src}.attn.{source}", shape, qt, "compressor")
|
||||
if c["compress_ratios"][layer] > 1:
|
||||
regular(f"{dst}.attn_compressor_gate.weight", f"{src}.attn.compressor.wgate.weight",
|
||||
(hd, dim), QTYPE_F16, "compressor")
|
||||
if layer in c["index_source_layer_ids"]:
|
||||
regular(f"{dst}.indexer.attn_q_b.weight", f"{src}.attn.indexer.wq_b.weight",
|
||||
(ih * idim, qrank), QTYPE_F16, "indexer")
|
||||
regular(f"{dst}.indexer.proj.weight", f"{src}.attn.indexer.weights_proj.weight",
|
||||
(ih, dim), QTYPE_F16, "indexer")
|
||||
regular(f"{dst}.ffn_gate_inp.weight", f"{src}.ffn.gate.weight", (experts, dim), QTYPE_F32, "router")
|
||||
for suffix, target in (("bias", "exp_probs_b.bias"), ("bias_vl", "exp_probs_b_vl.bias")):
|
||||
regular(f"{dst}.{target}", f"{src}.ffn.gate.{suffix}", (experts,), QTYPE_F32, "router")
|
||||
for part, source, shape, qt in (("gate", "w1", (inter, dim), QTYPE_IQ2_XXS),
|
||||
("up", "w3", (inter, dim), QTYPE_IQ2_XXS),
|
||||
("down", "w2", (dim, inter), QTYPE_Q2_K)):
|
||||
regular(f"{dst}.ffn_{part}_shexp.weight", f"{src}.ffn.shared_experts.{source}.weight",
|
||||
shape, QTYPE_Q8_0, "shared")
|
||||
pattern = f"{src}.ffn.experts.{{expert}}.{source}.weight"
|
||||
for expert in range(experts):
|
||||
claim(pattern.format(expert=expert), (shape[0], shape[1] // 2), "I8")
|
||||
plan.append(TensorPlan(f"{dst}.ffn_{part}_exps.weight", (*reversed(shape), experts),
|
||||
QTYPE_MXFP4 if quant == "mxfp4" else QTYPE_Q4_K if quant == "q4" else qt,
|
||||
"experts", source=pattern, expert_layer=layer,
|
||||
expert_part=part, expert_count=experts))
|
||||
if layer in c["engram_layer_ids"]:
|
||||
index = c["engram_layer_ids"].index(layer)
|
||||
rows = c["engram_num_embeddings"][index]
|
||||
engram = f"{src}.engram"
|
||||
claim(f"{engram}.embed.weight", (rows, 256), "F8_E4M3")
|
||||
claim(f"{engram}.embed.scale", (rows, 8), "F8_E8M0")
|
||||
disk.append(TensorPlan(f"{dst}.engram_embd.weight", (264, rows), QTYPE_I8,
|
||||
"engram_disk", source=f"{engram}.embed.weight", raw_copy=True))
|
||||
for part in ("q", "k"):
|
||||
regular(f"{dst}.engram_{part}_norm.weight", f"{engram}.{part}_weight", (hc, dim), QTYPE_F32, "engram")
|
||||
regular(f"{dst}.engram_kv.weight", f"{engram}.wkv.weight", ((hc + 1) * dim, 24 * 256), QTYPE_F16, "engram")
|
||||
omitted = {name for name in db.tensors if name.startswith(("mtp.", "vision.", "aligner.", "image_"))}
|
||||
if consumed | omitted != set(db.tensors):
|
||||
raise ValueError(f"unclaimed source tensors: {sorted(set(db.tensors) - consumed - omitted)[:10]}")
|
||||
offset = 0
|
||||
for item in plan + disk:
|
||||
item.offset = offset
|
||||
item.nbytes = qtype_nbytes(item.qtype, item.shape)
|
||||
offset += align(item.nbytes, GGUF_ALIGNMENT)
|
||||
return plan + disk
|
||||
|
||||
|
||||
class NativeQuantizer(Quantizer):
|
||||
def to_f32(self, db, name, row_start=0, row_count=None):
|
||||
if ".engram.embed." in name:
|
||||
raise ValueError("Engram must never be materialized as a whole float tensor")
|
||||
np = self.np
|
||||
info = db.info(name)
|
||||
if info["dtype"] not in ("I8", "F8_E4M3"):
|
||||
result = super().to_f32(db, name, row_start, row_count)
|
||||
else:
|
||||
shape = info["shape"]
|
||||
codes = np.frombuffer(db.read(name), dtype=np.uint8).reshape(shape)
|
||||
sinfo = db.info(scale_name(name))
|
||||
scales = np.frombuffer(db.read(scale_name(name)), dtype=np.uint8).reshape(sinfo["shape"])
|
||||
if np.any(scales == 255):
|
||||
raise ValueError(f"{name}: nonfinite scale")
|
||||
scales = np.ldexp(np.ones_like(scales, dtype=np.float32), scales.astype(np.int32) - 127)
|
||||
if info["dtype"] == "I8":
|
||||
lut = np.array([0, .5, 1, 1.5, 2, 3, 4, 6, -0., -.5, -1, -1.5, -2, -3, -4, -6], np.float32)
|
||||
result = np.empty((shape[0], shape[1] * 2), dtype=np.float32)
|
||||
result[:, 0::2] = lut[codes & 15]
|
||||
result[:, 1::2] = lut[codes >> 4]
|
||||
result = (result.reshape(shape[0], -1, 32) * scales[:, :, None]).reshape(result.shape)
|
||||
else:
|
||||
if np.any((codes & 127) == 127):
|
||||
raise ValueError(f"{name}: nonfinite FP8 weight")
|
||||
result = self.fp8_lut[codes]
|
||||
for row in range(0, shape[0], 32):
|
||||
for col in range(0, shape[1], 32):
|
||||
result[row:row + 32, col:col + 32] *= scales[row // 32, col // 32]
|
||||
if row_count is not None:
|
||||
result = result[row_start:row_start + row_count]
|
||||
if not np.all(np.isfinite(result)):
|
||||
raise ValueError(f"{name}: nonfinite dequantized weight")
|
||||
return np.ascontiguousarray(result, dtype=np.float32)
|
||||
|
||||
def to_mxfp4(self, db, name):
|
||||
"""Repack released E2M1 pairs into GGUF MXFP4 blocks without touching the values."""
|
||||
np = self.np
|
||||
info = db.info(name)
|
||||
if info["dtype"] != "I8" or info["shape"][1] % 16:
|
||||
raise ValueError(f"{name}: not a packed MXFP4 tensor")
|
||||
rows, blocks = info["shape"][0], info["shape"][1] // 16
|
||||
codes = np.frombuffer(db.read(name), dtype=np.uint8).reshape(rows, blocks, 16)
|
||||
scales = np.frombuffer(db.read(scale_name(name)), dtype=np.uint8).reshape(rows, blocks)
|
||||
if np.any(scales == 255):
|
||||
raise ValueError(f"{name}: nonfinite scale")
|
||||
values = np.empty((rows, blocks, 32), dtype=np.uint8)
|
||||
values[:, :, 0::2] = codes & 15
|
||||
values[:, :, 1::2] = codes >> 4
|
||||
packed = np.empty((rows, blocks, 17), dtype=np.uint8)
|
||||
packed[:, :, 0] = scales
|
||||
packed[:, :, 1:] = values[:, :, :16] | (values[:, :, 16:] << 4)
|
||||
return packed.tobytes()
|
||||
|
||||
def encode(self, array, qtype, imatrix=None):
|
||||
if qtype == QTYPE_F16 and self.np.any(self.np.abs(array) > 65504):
|
||||
raise ValueError("F16 tensor would overflow; preserve this family in BF16/F32")
|
||||
return super().encode(array, qtype, imatrix)
|
||||
|
||||
|
||||
def write_engram(fp, item, db, np):
|
||||
# Keep weights and scales together without changing either byte. A chunk
|
||||
# holds 16K rows (4 MiB of weights), independent of the 94 GiB table size.
|
||||
rows = item.shape[1]
|
||||
for start in range(0, rows, 16384):
|
||||
count = min(16384, rows - start)
|
||||
weights = b"".join(db.iter_read(item.source, start * 256, count * 256))
|
||||
scales = b"".join(db.iter_read(scale_name(item.source), start * 8, count * 8))
|
||||
w = np.frombuffer(weights, dtype=np.uint8).reshape(count, 256)
|
||||
s = np.frombuffer(scales, dtype=np.uint8).reshape(count, 8)
|
||||
if np.any((w & 127) == 127) or np.any(s == 255):
|
||||
raise ValueError(f"{item.name}: nonfinite Engram row near {start}")
|
||||
packed = np.empty((count, 264), dtype=np.uint8)
|
||||
packed[:, :256], packed[:, 256:] = w, s
|
||||
fp.write(packed.tobytes())
|
||||
|
||||
|
||||
def write_gguf(args, plan, records, db):
|
||||
quantizer = NativeQuantizer(args.quants_library)
|
||||
imatrix = Imatrix(args.imatrix, quantizer.np)
|
||||
if args.imatrix:
|
||||
for item in plan:
|
||||
if item.is_expert and item.name not in imatrix.entries:
|
||||
raise ValueError(f"missing imatrix tensor {item.name}")
|
||||
if any(quantizer.np.any(values < 0) for values in imatrix.entries.values()):
|
||||
raise ValueError("negative importance values")
|
||||
data_start, data_bytes = print_plan(plan, records, [], GGUF_ALIGNMENT)
|
||||
partial, journal = args.out + ".partial", args.out + ".partial.json"
|
||||
signature = conversion_signature(plan, records, [], args.imatrix)
|
||||
# A metadata/recipe match alone cannot distinguish two source downloads.
|
||||
source_identity = [(name, db.info(name)) for name in sorted(db.tensors)]
|
||||
signature = hashlib.sha256((signature + json.dumps(source_identity, sort_keys=True)).encode()).hexdigest()
|
||||
if os.path.exists(args.out):
|
||||
raise ValueError(f"refusing to overwrite {args.out}")
|
||||
completed = 0
|
||||
if os.path.exists(partial) or os.path.exists(journal):
|
||||
if not args.resume or not (os.path.exists(partial) and os.path.exists(journal)):
|
||||
raise ValueError("partial file and journal require --resume")
|
||||
completed = load_resume_state(journal, signature, plan)
|
||||
end = data_start + (plan[completed - 1].offset +
|
||||
align(plan[completed - 1].nbytes, GGUF_ALIGNMENT) if completed else 0)
|
||||
free = shutil.disk_usage(os.path.dirname(os.path.abspath(args.out))).free
|
||||
if free < data_start + data_bytes - end + (32 << 30):
|
||||
raise ValueError("insufficient disk space for remaining output plus 32 GiB reserve")
|
||||
header = b"GGUF" + struct.pack("<IQQ", 3, len(plan), len(records))
|
||||
header += b"".join(records) + b"".join(tensor_header(item) for item in plan)
|
||||
header += bytes(data_start - len(header))
|
||||
if os.path.exists(partial):
|
||||
with open(partial, "rb") as fp:
|
||||
if fp.read(data_start) != header or os.fstat(fp.fileno()).st_size < end:
|
||||
raise ValueError("partial GGUF is truncated or has a different header")
|
||||
else:
|
||||
with open(partial, "xb") as fp:
|
||||
fp.write(header)
|
||||
fp.flush()
|
||||
os.fsync(fp.fileno())
|
||||
save_resume_state(journal, signature, 0)
|
||||
with open(partial, "r+b") as fp, concurrent.futures.ThreadPoolExecutor(max_workers=args.threads) as pool:
|
||||
fp.truncate(end)
|
||||
fp.seek(end)
|
||||
for index in range(completed, len(plan)):
|
||||
item = plan[index]
|
||||
started = time.monotonic()
|
||||
missing = 0
|
||||
if fp.tell() != data_start + item.offset:
|
||||
raise ValueError(f"incorrect offset for {item.name}")
|
||||
if item.role == "engram_disk":
|
||||
write_engram(fp, item, db, quantizer.np)
|
||||
elif item.is_expert:
|
||||
def convert(expert):
|
||||
if item.qtype == QTYPE_MXFP4:
|
||||
return quantizer.to_mxfp4(db, item.source.format(expert=expert)), False
|
||||
values = quantizer.to_f32(db, item.source.format(expert=expert))
|
||||
importance = imatrix.expert(item.name, expert, item.shape[0], item.expert_count)
|
||||
return quantizer.encode(values, item.qtype, importance), importance is None
|
||||
for start in range(0, item.expert_count, args.threads):
|
||||
futures = [pool.submit(convert, e) for e in range(start, min(start + args.threads, item.expert_count))]
|
||||
for future in futures:
|
||||
data, fallback = future.result()
|
||||
if len(data) != item.nbytes // item.expert_count:
|
||||
raise ValueError("wrong encoded expert size")
|
||||
fp.write(data)
|
||||
missing += int(fallback)
|
||||
else:
|
||||
fp.write(quantizer.encode(quantizer.to_f32(db, item.source), item.qtype))
|
||||
if fp.tell() != data_start + item.offset + item.nbytes:
|
||||
raise ValueError(f"incorrect payload size for {item.name}")
|
||||
fp.write(bytes(align(item.nbytes, GGUF_ALIGNMENT) - item.nbytes))
|
||||
fp.flush()
|
||||
os.fsync(fp.fileno())
|
||||
save_resume_state(journal, signature, index + 1)
|
||||
print(f"[{index + 1}/{len(plan)}] {item.name}: {item.nbytes / (1 << 30):.3f} GiB, "
|
||||
f"{time.monotonic() - started:.1f}s, uncalibrated_experts={missing}", flush=True)
|
||||
os.rename(partial, args.out)
|
||||
os.unlink(journal)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--hf", required=True)
|
||||
parser.add_argument("--out", required=True)
|
||||
parser.add_argument("--source-revision", required=True)
|
||||
parser.add_argument("--quant", choices=QUANTIZATION, default="q2")
|
||||
parser.add_argument("--imatrix")
|
||||
parser.add_argument("--threads", type=int, default=8)
|
||||
parser.add_argument("--resume", action="store_true")
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
suffix = "dylib" if sys.platform == "darwin" else "so"
|
||||
parser.add_argument("--quants-library", default=os.path.join(os.path.dirname(__file__), f"libds4quants.{suffix}"))
|
||||
args = parser.parse_args()
|
||||
if not re.fullmatch(r"[0-9a-f]{40}", args.source_revision):
|
||||
parser.error("source revision must be a full commit hash")
|
||||
if not 1 <= args.threads <= 32:
|
||||
parser.error("threads must be between 1 and 32")
|
||||
config, records = metadata(args.hf, args.source_revision)
|
||||
db = SourceDB(args.hf, index_validator=lambda _: None, scale_validator=validate_scales)
|
||||
try:
|
||||
plan = build_plan(db, config, args.quant)
|
||||
records.append(kv_string("deepseek41.quantization", QUANTIZATION[args.quant]))
|
||||
records.append(kv_string("deepseek41.calibration", "imatrix" if args.imatrix else "weight-energy bootstrap"))
|
||||
if args.imatrix:
|
||||
records.append(kv_string("quantize.imatrix.file", os.path.basename(args.imatrix)))
|
||||
if args.dry_run:
|
||||
print_plan(plan, records, [], GGUF_ALIGNMENT)
|
||||
for item in plan:
|
||||
print(json.dumps(dataclasses.asdict(item), sort_keys=True))
|
||||
else:
|
||||
write_gguf(args, plan, records, db)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except (OSError, ValueError) as error:
|
||||
sys.exit(f"deepseek41-quantize: {error}")
|
||||
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Audit a completed V4.1 GGUF and sample its payloads against the source.
|
||||
|
||||
Checks the complete tensor layout, then re-encodes selected expert tensors and
|
||||
samples native Engram rows. This is artifact validation, not inference QA.
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import random
|
||||
import sys
|
||||
|
||||
from deepseek41_metadata import GGUF_ALIGNMENT
|
||||
from deepseek41_quantize import (SourceDB, NativeQuantizer, Imatrix, build_plan,
|
||||
validate_scales, scale_name, QUANTIZATION, QTYPE_MXFP4)
|
||||
from glm53_quantize import (align, read_exact, read_u32, read_u64,
|
||||
read_gguf_string, skip_gguf_value)
|
||||
from glm53_validate_gguf import read_selected_metadata
|
||||
|
||||
|
||||
def check_payload(fp, offset, item, db, quantizer, imatrix):
|
||||
if item.role == "engram_disk":
|
||||
rows = item.shape[1]
|
||||
rng = random.Random(41 + rows)
|
||||
selected = {0, 1, rows - 1, 16383, 16384, (1 << 32) // 264}
|
||||
selected.update(rng.randrange(rows) for _ in range(128))
|
||||
for row in sorted(selected):
|
||||
if row >= rows:
|
||||
continue
|
||||
expected = b"".join(db.iter_read(item.source, row * 256, 256))
|
||||
expected += b"".join(db.iter_read(scale_name(item.source), row * 8, 8))
|
||||
fp.seek(offset + row * 264)
|
||||
if read_exact(fp, 264, item.name) != expected:
|
||||
raise ValueError(f"{item.name}: Engram row {row} differs from source")
|
||||
elif item.is_expert:
|
||||
selected = {0, (item.expert_layer * 17 + 41) % item.expert_count, item.expert_count - 1}
|
||||
stride = item.nbytes // item.expert_count
|
||||
for expert in sorted(selected):
|
||||
if item.qtype == QTYPE_MXFP4:
|
||||
expected = quantizer.to_mxfp4(db, item.source.format(expert=expert))
|
||||
else:
|
||||
values = quantizer.to_f32(db, item.source.format(expert=expert))
|
||||
importance = imatrix.expert(item.name, expert, item.shape[0], item.expert_count)
|
||||
expected = quantizer.encode(values, item.qtype, importance)
|
||||
fp.seek(offset + expert * stride)
|
||||
if len(expected) != stride or read_exact(fp, stride, item.name) != expected:
|
||||
raise ValueError(f"{item.name}: encoded expert {expert} differs from source recipe")
|
||||
else:
|
||||
values = quantizer.to_f32(db, item.source)
|
||||
expected = quantizer.encode(values, item.qtype)
|
||||
fp.seek(offset)
|
||||
if len(expected) != item.nbytes or read_exact(fp, item.nbytes, item.name) != expected:
|
||||
raise ValueError(f"{item.name}: payload differs from source recipe")
|
||||
|
||||
|
||||
def validate(args):
|
||||
config = json.loads((Path(args.hf) / "config.json").read_text())
|
||||
db = SourceDB(args.hf, index_validator=lambda _: None, scale_validator=validate_scales)
|
||||
try:
|
||||
plan = build_plan(db, config, args.quant)
|
||||
with open(args.gguf, "rb") as fp:
|
||||
if read_exact(fp, 4, "magic") != b"GGUF" or read_u32(fp, "version") != 3:
|
||||
raise ValueError("expected GGUF v3")
|
||||
if read_u64(fp, "tensor count") != len(plan):
|
||||
raise ValueError("tensor count differs from source plan")
|
||||
metadata = {}
|
||||
for _ in range(read_u64(fp, "metadata count")):
|
||||
key = read_gguf_string(fp, "metadata key")
|
||||
kind = read_u32(fp, "metadata type")
|
||||
if key in {"general.architecture", "general.alignment", "general.source.revision",
|
||||
"deepseek41.calibration", "deepseek41.quantization"}:
|
||||
if key in metadata:
|
||||
raise ValueError(f"duplicate metadata: {key}")
|
||||
metadata[key] = read_selected_metadata(fp, kind)
|
||||
else:
|
||||
skip_gguf_value(fp, kind)
|
||||
expected = {"general.architecture": "deepseek41", "general.alignment": GGUF_ALIGNMENT,
|
||||
"general.source.revision": args.source_revision,
|
||||
"deepseek41.quantization": QUANTIZATION[args.quant],
|
||||
"deepseek41.calibration": "imatrix" if args.imatrix else "weight-energy bootstrap"}
|
||||
if metadata != expected:
|
||||
raise ValueError(f"metadata mismatch: {metadata}")
|
||||
for item in plan:
|
||||
name = read_gguf_string(fp, "tensor name")
|
||||
rank = read_u32(fp, "tensor rank")
|
||||
if rank != len(item.shape):
|
||||
raise ValueError(f"{name}: unexpected rank {rank}")
|
||||
shape = tuple(read_u64(fp, "dimension") for _ in range(rank))
|
||||
kind, offset = read_u32(fp, "tensor type"), read_u64(fp, "tensor offset")
|
||||
if (name, shape, kind, offset) != (item.name, item.shape, item.qtype, item.offset):
|
||||
raise ValueError(f"{item.name}: tensor layout mismatch")
|
||||
start = align(fp.tell(), GGUF_ALIGNMENT)
|
||||
size = start + plan[-1].offset + align(plan[-1].nbytes, GGUF_ALIGNMENT)
|
||||
if os.fstat(fp.fileno()).st_size != size:
|
||||
raise ValueError(f"expected file size {size}")
|
||||
print(f"PASS: {len(plan)} tensor layouts; complete {size}-byte GGUF", flush=True)
|
||||
if args.payload:
|
||||
q = NativeQuantizer(args.quants_library)
|
||||
imatrix = Imatrix(args.imatrix, q.np)
|
||||
if args.imatrix and any(t.is_expert and t.name not in imatrix.entries for t in plan):
|
||||
raise ValueError("imatrix is missing expert tensors")
|
||||
for index, item in enumerate(plan):
|
||||
check_payload(fp, start + item.offset, item, db, q, imatrix)
|
||||
if (index + 1) % 25 == 0 or index + 1 == len(plan):
|
||||
print(f"PASS: source payload checks {index + 1}/{len(plan)}", flush=True)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--hf", required=True)
|
||||
parser.add_argument("--gguf", required=True)
|
||||
parser.add_argument("--source-revision", required=True)
|
||||
parser.add_argument("--quant", choices=QUANTIZATION, default="q2")
|
||||
parser.add_argument("--imatrix")
|
||||
parser.add_argument("--payload", action="store_true")
|
||||
suffix = "dylib" if sys.platform == "darwin" else "so"
|
||||
parser.add_argument("--quants-library", default=str(Path(__file__).with_name(f"libds4quants.{suffix}")))
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
validate(args)
|
||||
except (OSError, ValueError) as error:
|
||||
sys.exit(f"deepseek41-validate: {error}")
|
||||
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Copy the released DeepSeek V4.1 Flash vision encoder into a separate GGUF.
|
||||
|
||||
All 266 tensors remain BF16, byte for byte. Visual router biases are already
|
||||
part of the language GGUF produced by deepseek41_quantize.py.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
from deepseek4_vision import create_gguf, print_summary, validate_gguf
|
||||
from glm53_quantize import (
|
||||
GGUF_ALIGNMENT, QTYPE_BF16, SourceDB, TensorPlan, align,
|
||||
kv_f32, kv_string, kv_u32, qtype_nbytes,
|
||||
)
|
||||
|
||||
SOURCE_REVISION = "df42c109f1defefcbfcedbe7d905718a12266e40"
|
||||
SOURCE_URL = "https://huggingface.co/deepseek-ai/DeepSeek-V4.1-Flash"
|
||||
|
||||
|
||||
def source_shapes():
|
||||
shapes = {
|
||||
"vision.patch_embed.proj.weight": (1024, 588),
|
||||
"vision.patch_embed.proj.bias": (1024,),
|
||||
"vision.norm.weight": (1024,),
|
||||
"aligner.w1.weight": (5120, 9216),
|
||||
"aligner.w1.bias": (5120,),
|
||||
"aligner.w2.weight": (5120, 5120),
|
||||
"aligner.w2.bias": (5120,),
|
||||
"image_start": (5120,),
|
||||
"image_newline": (5120,),
|
||||
"image_end": (5120,),
|
||||
}
|
||||
for layer in range(32):
|
||||
for suffix, shape in {
|
||||
"norm1.weight": (1024,), "norm2.weight": (1024,),
|
||||
"attn.wqkv.weight": (3072, 1024), "attn.wqkv.bias": (3072,),
|
||||
"attn.wo.weight": (1024, 1024), "attn.wo.bias": (1024,),
|
||||
"mlp.w1.weight": (5632, 1024), "mlp.w2.weight": (1024, 2816),
|
||||
}.items():
|
||||
shapes[f"vision.blocks.{layer}.{suffix}"] = shape
|
||||
return shapes
|
||||
|
||||
|
||||
def validate_inventory(tensors):
|
||||
selected = {name for name in tensors if name.startswith(("vision.", "aligner.", "image_"))}
|
||||
expected = set(source_shapes())
|
||||
if selected != expected:
|
||||
raise ValueError(f"vision inventory mismatch: missing {sorted(expected - selected)}, "
|
||||
f"unexpected {sorted(selected - expected)}")
|
||||
|
||||
|
||||
def build_plan(db):
|
||||
validate_inventory(db.tensors)
|
||||
plan, offset = [], 0
|
||||
for name, shape in sorted(source_shapes().items()):
|
||||
info = db.info(name)
|
||||
if info["dtype"] != "BF16" or tuple(info["shape"]) != shape:
|
||||
raise ValueError(f"{name}: expected BF16 {shape}, found {info['dtype']} {info['shape']}")
|
||||
role = "vision" if name.startswith("vision.") else (
|
||||
"aligner" if name.startswith("aligner.") else "image_embedding")
|
||||
item = TensorPlan(name, tuple(reversed(shape)), QTYPE_BF16, role,
|
||||
source=name, raw_copy=True)
|
||||
item.nbytes = qtype_nbytes(item.qtype, item.shape)
|
||||
if item.nbytes != info["nbytes"]:
|
||||
raise ValueError(f"{name}: source byte size mismatch")
|
||||
item.offset = offset
|
||||
offset += align(item.nbytes)
|
||||
plan.append(item)
|
||||
return plan
|
||||
|
||||
|
||||
def sidecar_metadata(hf_dir):
|
||||
config = json.loads((Path(hf_dir) / "config.json").read_text())
|
||||
if config.get("model_type") != "deepseek_v41" or config.get("image_token_id") != 129264:
|
||||
raise ValueError("expected the DeepSeek V4.1 Flash checkpoint")
|
||||
for section, expected in {
|
||||
"text_config": {"hidden_size": 5120, "num_hidden_layers": 40, "n_routed_experts": 384},
|
||||
"vision_config": {"num_hidden_layers": 32, "hidden_size": 1024,
|
||||
"num_attention_heads": 16, "intermediate_size": 2816, "patch_size": 14,
|
||||
"rope_theta": 10000, "downsample_ratio": 3, "max_image_tokens": 1024,
|
||||
"min_pixels": 295936, "max_wh_ratio": None},
|
||||
}.items():
|
||||
for key, value in expected.items():
|
||||
if key not in config[section] or config[section][key] != value:
|
||||
raise ValueError(f"unexpected {section}.{key}: {config[section].get(key)!r}")
|
||||
prefix = "deepseek4-vision"
|
||||
records = [
|
||||
kv_string("general.architecture", prefix),
|
||||
kv_string("general.name", "DeepSeek V4.1 Flash Vision Encoder"),
|
||||
kv_u32("general.alignment", GGUF_ALIGNMENT),
|
||||
kv_string("general.source.url", SOURCE_URL),
|
||||
kv_string("general.source.revision", SOURCE_REVISION),
|
||||
kv_string(f"{prefix}.checkpoint_variant", "v4.1-flash"),
|
||||
kv_f32(f"{prefix}.rope.freq_base", 10000),
|
||||
kv_f32(f"{prefix}.attention.layer_norm_rms_epsilon", 1e-6),
|
||||
]
|
||||
for key, value in {
|
||||
"block_count": 32, "embedding_length": 1024, "feed_forward_length": 2816,
|
||||
"attention.head_count": 16, "projection_length": 5120, "patch_size": 14,
|
||||
"downsample_ratio": 3, "image.max_tokens": 1024, "image.min_pixels": 295936,
|
||||
"image.max_width_height_ratio": 0, "image.token_id": 129264,
|
||||
"language.block_count": 40, "language.expert_count": 384,
|
||||
}.items():
|
||||
records.append(kv_u32(f"{prefix}.{key}", value))
|
||||
return records
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--hf", required=True, help="official V4.1 Flash snapshot")
|
||||
output = parser.add_mutually_exclusive_group(required=True)
|
||||
output.add_argument("--out", help="write this vision GGUF")
|
||||
output.add_argument("--validate", metavar="GGUF")
|
||||
output.add_argument("--dry-run", action="store_true")
|
||||
parser.add_argument("--verify-payload", action="store_true")
|
||||
parser.add_argument("--expected-sha256")
|
||||
args = parser.parse_args()
|
||||
if (args.verify_payload or args.expected_sha256) and not args.validate:
|
||||
parser.error("--verify-payload and --expected-sha256 require --validate")
|
||||
records = sidecar_metadata(args.hf)
|
||||
db = SourceDB(args.hf, validate_inventory, lambda _: None)
|
||||
try:
|
||||
plan = build_plan(db)
|
||||
if args.out:
|
||||
create_gguf(args.out, plan, records, db, overwrite=False)
|
||||
elif args.validate:
|
||||
validate_gguf(args.validate, plan, records, db,
|
||||
args.verify_payload, args.expected_sha256)
|
||||
else:
|
||||
print_summary(plan, records)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except (KeyError, OSError, ValueError) as error:
|
||||
print(f"deepseek41-vision: {error}", file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
@@ -0,0 +1,415 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Create or validate the standalone DeepSeek V4 Flash Vision sidecar GGUF."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import struct
|
||||
import sys
|
||||
|
||||
from glm53_quantize import (
|
||||
GGUF_ALIGNMENT,
|
||||
GGUF_FLOAT32,
|
||||
GGUF_STRING,
|
||||
GGUF_UINT32,
|
||||
GGUF_VERSION,
|
||||
QTYPE_BF16,
|
||||
QTYPE_F32,
|
||||
QTYPE_NAMES,
|
||||
SourceDB,
|
||||
TensorPlan,
|
||||
align,
|
||||
fail,
|
||||
kv_f32,
|
||||
kv_string,
|
||||
kv_u32,
|
||||
pack_string,
|
||||
qtype_nbytes,
|
||||
read_exact,
|
||||
read_gguf_string,
|
||||
read_u32,
|
||||
read_u64,
|
||||
skip_gguf_value,
|
||||
tensor_header,
|
||||
)
|
||||
|
||||
|
||||
SOURCE_URL = "https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash-Vision-Exp"
|
||||
SOURCE_REVISION = "e46e16bf6035c6f317eb2ac7458eb0362926d402"
|
||||
ARCHITECTURE = "deepseek4-vision"
|
||||
ROUTER_RE = re.compile(r"^(layers\.(\d+)|mtp\.(\d+))\.ffn\.gate\.bias_vl$")
|
||||
HASH_BIAS_RE = re.compile(r"^layers\.([0-2])\.ffn\.gate\.bias$")
|
||||
|
||||
|
||||
def validate_deepseek4_index(weight_map):
|
||||
if len(weight_map) != 72633:
|
||||
fail(f"expected 72,633 source tensors, found {len(weight_map)}")
|
||||
shards = set(weight_map.values())
|
||||
expected_shards = {
|
||||
f"model-{index:05d}-of-00048.safetensors"
|
||||
for index in range(1, 49)
|
||||
}
|
||||
if shards != expected_shards:
|
||||
fail("source shard inventory differs from the pinned checkpoint")
|
||||
selected = {name for name in weight_map if selected_role(name)}
|
||||
validate_inventory(selected)
|
||||
if "embed.weight" not in weight_map or "head.weight" not in weight_map:
|
||||
fail("source language tensor inventory is incomplete")
|
||||
|
||||
|
||||
def validate_deepseek4_fp8_scales(tensors):
|
||||
fp8 = {name: info for name, info in tensors.items()
|
||||
if info["dtype"] == "F8_E4M3"}
|
||||
if len(fp8) != 390:
|
||||
fail(f"expected 390 FP8 language tensors, found {len(fp8)}")
|
||||
for name, info in fp8.items():
|
||||
if not name.endswith(".weight") or len(info["shape"]) != 2:
|
||||
fail(f"unexpected FP8 tensor layout: {name}")
|
||||
scale_name = name.removesuffix(".weight") + ".scale"
|
||||
scale = tensors.get(scale_name)
|
||||
expected_shape = [
|
||||
(info["shape"][0] + 127) // 128,
|
||||
(info["shape"][1] + 127) // 128,
|
||||
]
|
||||
if not scale or scale["dtype"] != "F8_E8M0" or \
|
||||
scale["shape"] != expected_shape:
|
||||
fail(f"{name}: invalid native FP8 scale tensor {scale_name}")
|
||||
|
||||
|
||||
def load_config(hf_dir):
|
||||
with open(os.path.join(hf_dir, "config.json"), encoding="utf-8") as fp:
|
||||
config = json.load(fp)
|
||||
expected = {
|
||||
"model_type": "deepseek_v4",
|
||||
"hidden_size": 4096,
|
||||
"n_routed_experts": 256,
|
||||
"num_hidden_layers": 43,
|
||||
"vision_n_layers": 32,
|
||||
"vision_dim": 1024,
|
||||
"vision_n_heads": 16,
|
||||
"vision_inter_dim": 2816,
|
||||
"vision_patch_size": 14,
|
||||
"vision_rope_theta": 10000.0,
|
||||
"vision_downsample_ratio": 3,
|
||||
"vision_max_n_token": 384,
|
||||
"vision_min_pixels": 147456,
|
||||
"vision_max_wh_ratio": 8,
|
||||
}
|
||||
for key, value in expected.items():
|
||||
if config.get(key) != value:
|
||||
fail(f"unexpected config {key}: {config.get(key)!r}")
|
||||
if config.get("rms_norm_eps") != 1e-20:
|
||||
fail(f"unexpected language rms_norm_eps: {config.get('rms_norm_eps')!r}")
|
||||
return config
|
||||
|
||||
|
||||
def selected_role(name):
|
||||
if name.startswith("vision."):
|
||||
return "vision"
|
||||
if name.startswith("aligner."):
|
||||
return "aligner"
|
||||
if name in {"image_start", "image_pad", "image_newline", "image_end"}:
|
||||
return "image_embedding"
|
||||
if ROUTER_RE.match(name):
|
||||
return "visual_router_bias"
|
||||
if HASH_BIAS_RE.match(name):
|
||||
return "hash_router_bias"
|
||||
return None
|
||||
|
||||
|
||||
def validate_inventory(names):
|
||||
names = set(names)
|
||||
vision = {name for name in names if name.startswith("vision.")}
|
||||
aligner = {name for name in names if name.startswith("aligner.")}
|
||||
image = {name for name in names if name.startswith("image_")}
|
||||
visual_bias = {name for name in names if ROUTER_RE.match(name)}
|
||||
hash_bias = {name for name in names if HASH_BIAS_RE.match(name)}
|
||||
if len(vision) != 259:
|
||||
fail(f"expected 259 ViT tensors, found {len(vision)}")
|
||||
if len(aligner) != 4:
|
||||
fail(f"expected 4 aligner tensors, found {len(aligner)}")
|
||||
if image != {"image_start", "image_pad", "image_newline", "image_end"}:
|
||||
fail(f"unexpected image embedding inventory: {sorted(image)}")
|
||||
expected_visual = {
|
||||
*(f"layers.{layer}.ffn.gate.bias_vl" for layer in range(43)),
|
||||
*(f"mtp.{stage}.ffn.gate.bias_vl" for stage in range(3)),
|
||||
}
|
||||
if visual_bias != expected_visual:
|
||||
fail("visual router bias inventory differs from the pinned checkpoint")
|
||||
expected_hash = {f"layers.{layer}.ffn.gate.bias" for layer in range(3)}
|
||||
if hash_bias != expected_hash:
|
||||
fail("hash router bias inventory differs from the pinned checkpoint")
|
||||
if len(names) != 316:
|
||||
fail(f"expected 316 sidecar tensors, found {len(names)}")
|
||||
|
||||
|
||||
def build_plan(db):
|
||||
selected = sorted(name for name in db.tensors if selected_role(name))
|
||||
validate_inventory(selected)
|
||||
plan = []
|
||||
offset = 0
|
||||
for name in selected:
|
||||
info = db.info(name)
|
||||
role = selected_role(name)
|
||||
expected_dtype = "BF16" if role in {
|
||||
"vision", "aligner", "image_embedding"
|
||||
} else "F32"
|
||||
if info["dtype"] != expected_dtype:
|
||||
fail(f"{name}: expected {expected_dtype}, got {info['dtype']}")
|
||||
qtype = QTYPE_BF16 if expected_dtype == "BF16" else QTYPE_F32
|
||||
item = TensorPlan(
|
||||
name=name,
|
||||
shape=tuple(reversed(info["shape"])),
|
||||
qtype=qtype,
|
||||
role=role,
|
||||
source=name,
|
||||
raw_copy=True,
|
||||
)
|
||||
item.nbytes = qtype_nbytes(item.qtype, item.shape)
|
||||
if item.nbytes != info["nbytes"]:
|
||||
fail(f"{name}: planned {item.nbytes} bytes, source has {info['nbytes']}")
|
||||
item.offset = offset
|
||||
offset += align(item.nbytes)
|
||||
plan.append(item)
|
||||
return plan
|
||||
|
||||
|
||||
def sidecar_metadata(hf_dir, source_revision):
|
||||
config = load_config(hf_dir)
|
||||
prefix = ARCHITECTURE
|
||||
return [
|
||||
kv_string("general.architecture", ARCHITECTURE),
|
||||
kv_string("general.name", "DeepSeek V4 Flash Vision Encoder"),
|
||||
kv_u32("general.alignment", GGUF_ALIGNMENT),
|
||||
kv_string("general.source.url", SOURCE_URL),
|
||||
kv_string("general.source.revision", source_revision),
|
||||
kv_string(f"{prefix}.checkpoint_variant", "vision-exp"),
|
||||
kv_u32(f"{prefix}.block_count", config["vision_n_layers"]),
|
||||
kv_u32(f"{prefix}.embedding_length", config["vision_dim"]),
|
||||
kv_u32(f"{prefix}.feed_forward_length", config["vision_inter_dim"]),
|
||||
kv_u32(f"{prefix}.attention.head_count", config["vision_n_heads"]),
|
||||
kv_u32(f"{prefix}.projection_length", config["hidden_size"]),
|
||||
kv_u32(f"{prefix}.patch_size", config["vision_patch_size"]),
|
||||
kv_f32(f"{prefix}.rope.freq_base", config["vision_rope_theta"]),
|
||||
kv_f32(f"{prefix}.attention.layer_norm_rms_epsilon", 1e-6),
|
||||
kv_u32(f"{prefix}.downsample_ratio", config["vision_downsample_ratio"]),
|
||||
kv_u32(f"{prefix}.image.max_tokens", config["vision_max_n_token"]),
|
||||
kv_u32(f"{prefix}.image.min_pixels", config["vision_min_pixels"]),
|
||||
kv_u32(f"{prefix}.image.max_width_height_ratio", config["vision_max_wh_ratio"]),
|
||||
kv_u32(f"{prefix}.language.block_count", config["num_hidden_layers"]),
|
||||
kv_u32(f"{prefix}.language.expert_count", config["n_routed_experts"]),
|
||||
]
|
||||
|
||||
|
||||
def layout(plan, metadata):
|
||||
header_bytes = 4 + 4 + 8 + 8
|
||||
header_bytes += sum(len(record) for record in metadata)
|
||||
header_bytes += sum(len(tensor_header(item)) for item in plan)
|
||||
data_offset = align(header_bytes)
|
||||
data_bytes = sum(align(item.nbytes) for item in plan)
|
||||
return data_offset, data_bytes
|
||||
|
||||
|
||||
def print_summary(plan, metadata):
|
||||
data_offset, data_bytes = layout(plan, metadata)
|
||||
print(f"tensors: {len(plan)}")
|
||||
print(f"metadata_records: {len(metadata)}")
|
||||
print(f"metadata_bytes: {data_offset}")
|
||||
print(f"tensor_bytes: {sum(item.nbytes for item in plan)}")
|
||||
print(f"file_bytes: {data_offset + data_bytes}")
|
||||
roles = {}
|
||||
for item in plan:
|
||||
roles[item.role] = roles.get(item.role, 0) + item.nbytes
|
||||
for role, size in sorted(roles.items()):
|
||||
print(f"role_bytes: {role} {size}")
|
||||
return data_offset, data_bytes
|
||||
|
||||
|
||||
def create_gguf(path, plan, metadata, db, overwrite):
|
||||
data_offset, data_bytes = print_summary(plan, metadata)
|
||||
required = data_offset + data_bytes + 4 * (1 << 30)
|
||||
free = shutil.disk_usage(os.path.dirname(os.path.abspath(path))).free
|
||||
if free < required:
|
||||
fail(f"insufficient free space: need output plus reserve {required}, have {free}")
|
||||
if os.path.exists(path) and not overwrite:
|
||||
fail(f"output exists: {path}; use --overwrite")
|
||||
partial = path + ".partial"
|
||||
if os.path.exists(partial):
|
||||
if not overwrite:
|
||||
fail(f"partial output exists: {partial}; use --overwrite")
|
||||
os.unlink(partial)
|
||||
|
||||
with open(partial, "wb") as fp:
|
||||
fp.write(b"GGUF")
|
||||
fp.write(struct.pack("<IQQ", GGUF_VERSION, len(plan), len(metadata)))
|
||||
for record in metadata:
|
||||
fp.write(record)
|
||||
for item in plan:
|
||||
fp.write(tensor_header(item))
|
||||
if fp.tell() > data_offset:
|
||||
fail("GGUF header exceeds its planned data offset")
|
||||
fp.write(bytes(data_offset - fp.tell()))
|
||||
for index, item in enumerate(plan, 1):
|
||||
if fp.tell() != data_offset + item.offset:
|
||||
fail(f"{item.name}: output offset mismatch")
|
||||
written = 0
|
||||
for chunk in db.iter_read(item.source):
|
||||
fp.write(chunk)
|
||||
written += len(chunk)
|
||||
if written != item.nbytes:
|
||||
fail(f"{item.name}: copied {written} bytes, expected {item.nbytes}")
|
||||
fp.write(bytes(align(item.nbytes) - item.nbytes))
|
||||
if index % 25 == 0 or index == len(plan):
|
||||
print(f"copied tensors: {index}/{len(plan)}", file=sys.stderr, flush=True)
|
||||
fp.flush()
|
||||
os.fsync(fp.fileno())
|
||||
os.replace(partial, path)
|
||||
|
||||
|
||||
def read_metadata_value(fp, value_type):
|
||||
if value_type == GGUF_STRING:
|
||||
return read_gguf_string(fp, "GGUF metadata string")
|
||||
if value_type == GGUF_UINT32:
|
||||
return read_u32(fp, "GGUF metadata uint32")
|
||||
if value_type == GGUF_FLOAT32:
|
||||
return struct.unpack("<f", read_exact(fp, 4, "GGUF metadata float32"))[0]
|
||||
skip_gguf_value(fp, value_type)
|
||||
return None
|
||||
|
||||
|
||||
def file_sha256(path):
|
||||
digest = hashlib.sha256()
|
||||
with open(path, "rb") as fp:
|
||||
while chunk := fp.read(16 << 20):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def validate_gguf(path, plan, expected_metadata, db, verify_payload, expected_sha256):
|
||||
expected_values = {}
|
||||
for record in expected_metadata:
|
||||
reader = io.BytesIO(record)
|
||||
key = read_gguf_string(reader, "expected metadata key")
|
||||
value_type = read_u32(reader, "expected metadata type")
|
||||
expected_values[key] = read_metadata_value(reader, value_type)
|
||||
|
||||
with open(path, "rb") as fp:
|
||||
if read_exact(fp, 4, "GGUF magic") != b"GGUF":
|
||||
fail(f"{path}: not a GGUF file")
|
||||
version = read_u32(fp, "GGUF version")
|
||||
if version != GGUF_VERSION:
|
||||
fail(f"expected GGUF v{GGUF_VERSION}, got v{version}")
|
||||
tensor_count = read_u64(fp, "GGUF tensor count")
|
||||
metadata_count = read_u64(fp, "GGUF metadata count")
|
||||
if tensor_count != len(plan):
|
||||
fail(f"tensor count {tensor_count} != expected {len(plan)}")
|
||||
if metadata_count != len(expected_metadata):
|
||||
fail(f"metadata count {metadata_count} != expected {len(expected_metadata)}")
|
||||
|
||||
actual_values = {}
|
||||
for _ in range(metadata_count):
|
||||
key = read_gguf_string(fp, "GGUF metadata key")
|
||||
value_type = read_u32(fp, "GGUF metadata type")
|
||||
actual_values[key] = read_metadata_value(fp, value_type)
|
||||
if actual_values != expected_values:
|
||||
fail("GGUF metadata differs from the pinned conversion metadata")
|
||||
|
||||
for index, item in enumerate(plan):
|
||||
name = read_gguf_string(fp, f"tensor {index} name")
|
||||
rank = read_u32(fp, f"tensor {index} rank")
|
||||
shape = tuple(read_u64(fp, f"tensor {index} dimension") for _ in range(rank))
|
||||
qtype = read_u32(fp, f"tensor {index} type")
|
||||
offset = read_u64(fp, f"tensor {index} offset")
|
||||
if (name, shape, qtype, offset) != (item.name, item.shape, item.qtype, item.offset):
|
||||
fail(f"tensor {index} header differs from the conversion plan: {name}")
|
||||
|
||||
data_offset, data_bytes = layout(plan, expected_metadata)
|
||||
actual_size = os.fstat(fp.fileno()).st_size
|
||||
if actual_size != data_offset + data_bytes:
|
||||
fail(f"file size {actual_size} != expected {data_offset + data_bytes}")
|
||||
verified = 0
|
||||
if verify_payload:
|
||||
for index, item in enumerate(plan, 1):
|
||||
fp.seek(data_offset + item.offset)
|
||||
for source in db.iter_read(item.source):
|
||||
if read_exact(fp, len(source), item.name) != source:
|
||||
fail(f"{item.name}: payload differs from the official source")
|
||||
verified += len(source)
|
||||
padding = read_exact(fp, align(item.nbytes) - item.nbytes, f"{item.name} padding")
|
||||
if any(padding):
|
||||
fail(f"{item.name}: nonzero alignment padding")
|
||||
if index % 25 == 0 or index == len(plan):
|
||||
print(f"verified payloads: {index}/{len(plan)}", file=sys.stderr, flush=True)
|
||||
|
||||
digest = file_sha256(path)
|
||||
if expected_sha256 and digest.lower() != expected_sha256.lower():
|
||||
fail(f"SHA-256 {digest} != expected {expected_sha256.lower()}")
|
||||
print(f"validated {path}: {len(plan)} tensors, {actual_size} bytes")
|
||||
print(f"SHA-256: {digest}")
|
||||
if verify_payload:
|
||||
print(f"source payloads matched byte for byte: {verified} bytes")
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--hf", required=True, help="official Vision-Exp snapshot")
|
||||
output = parser.add_mutually_exclusive_group()
|
||||
output.add_argument("--out", help="create this vision GGUF")
|
||||
output.add_argument("--validate", metavar="GGUF", help="validate an existing vision GGUF")
|
||||
parser.add_argument("--source-revision", default=SOURCE_REVISION)
|
||||
parser.add_argument("--verify-payload", action="store_true")
|
||||
parser.add_argument("--expected-sha256")
|
||||
parser.add_argument("--overwrite", action="store_true")
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
args = parser.parse_args()
|
||||
if args.source_revision != SOURCE_REVISION:
|
||||
parser.error(f"unsupported source revision: {args.source_revision}")
|
||||
if not args.dry_run and not args.out and not args.validate:
|
||||
parser.error("one of --out, --validate, or --dry-run is required")
|
||||
if args.verify_payload and not args.validate:
|
||||
parser.error("--verify-payload requires --validate")
|
||||
if args.expected_sha256 and not args.validate:
|
||||
parser.error("--expected-sha256 requires --validate")
|
||||
return args
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
db = SourceDB(
|
||||
args.hf,
|
||||
validate_deepseek4_index,
|
||||
validate_deepseek4_fp8_scales,
|
||||
)
|
||||
try:
|
||||
plan = build_plan(db)
|
||||
metadata = sidecar_metadata(args.hf, args.source_revision)
|
||||
if args.dry_run:
|
||||
print_summary(plan, metadata)
|
||||
elif args.out:
|
||||
create_gguf(args.out, plan, metadata, db, args.overwrite)
|
||||
print(f"deepseek4-vision: wrote {args.out}", file=sys.stderr)
|
||||
else:
|
||||
validate_gguf(
|
||||
args.validate,
|
||||
plan,
|
||||
metadata,
|
||||
db,
|
||||
args.verify_payload,
|
||||
args.expected_sha256,
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except (KeyError, OSError, ValueError, json.JSONDecodeError) as error:
|
||||
print(f"deepseek4-vision: error: {error}", file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Regenerate ds4_qwen4_unicode.inc, the codepoint classes of the Qwen3.8
|
||||
("qwen35") pre-tokenizer regex, from the `regex` module's Unicode tables.
|
||||
Use a current `regex` release; the generated header records its version.
|
||||
|
||||
Usage: python gen_qwen4_unicode.py > ../ds4_qwen4_unicode.inc
|
||||
"""
|
||||
import sys
|
||||
|
||||
import regex
|
||||
|
||||
CLASSES = {
|
||||
"letter": r"\p{L}",
|
||||
"mark": r"\p{M}",
|
||||
"number": r"\p{N}",
|
||||
"space": r"\s",
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
out = sys.stdout
|
||||
out.write("/* Generated by gguf-tools/gen_qwen4_unicode.py from the `regex` module's\n")
|
||||
out.write(f" * Unicode tables (regex {regex.__version__}); codepoint classes for the\n")
|
||||
out.write(" * Qwen3.8 pre-tokenizer. */\n\n")
|
||||
out.write("typedef struct { uint32_t lo, hi; } ds4_qwen4_urange;\n\n")
|
||||
for index, (name, cls) in enumerate(CLASSES.items()):
|
||||
if index:
|
||||
out.write("\n")
|
||||
pat = regex.compile(cls, regex.V1)
|
||||
ranges = []
|
||||
lo = prev = None
|
||||
for cp in range(0x110000):
|
||||
m = False if 0xD800 <= cp <= 0xDFFF else pat.match(chr(cp)) is not None
|
||||
if m:
|
||||
if lo is None:
|
||||
lo = cp
|
||||
prev = cp
|
||||
elif lo is not None:
|
||||
ranges.append((lo, prev))
|
||||
lo = None
|
||||
if lo is not None:
|
||||
ranges.append((lo, prev))
|
||||
out.write(f"static const ds4_qwen4_urange ds4_qwen4_{name}_ranges[] = {{\n")
|
||||
for i in range(0, len(ranges), 4):
|
||||
out.write(" " + " ".join(f"{{0x{a:05x}, 0x{b:05x}}}," for a, b in ranges[i:i + 4]) + "\n")
|
||||
out.write("};\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+324
@@ -0,0 +1,324 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build DwarfStar's asymmetric Q2 GGUF for the full GLM-5.3 model."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
import glm53_quantize as q
|
||||
from glm53_manifest import validate_glm53_full_index
|
||||
|
||||
|
||||
EXPERT_COUNT = 256
|
||||
BLOCK_COUNT = 79
|
||||
MTP_BLOCK = 78
|
||||
|
||||
|
||||
def source_prefix(layer):
|
||||
return f"model.layers.{layer}"
|
||||
|
||||
|
||||
def add_experts(plan, db, layer, part, qtype):
|
||||
source = f"{source_prefix(layer)}.mlp.experts.{{expert}}.{part}_proj.weight"
|
||||
shape = db.info(source.format(expert=0))["shape"]
|
||||
if len(shape) != 2:
|
||||
q.fail(f"expert source is not a matrix: {source.format(expert=0)}")
|
||||
for expert in range(EXPERT_COUNT):
|
||||
name = source.format(expert=expert)
|
||||
if db.info(name)["shape"] != shape:
|
||||
q.fail(f"expert shape mismatch: {name}")
|
||||
item = q.TensorPlan(
|
||||
f"blk.{layer}.ffn_{part}_exps.weight",
|
||||
(shape[1], shape[0], EXPERT_COUNT),
|
||||
qtype,
|
||||
f"routed_{part}",
|
||||
source=source,
|
||||
expert_layer=layer,
|
||||
expert_part=part,
|
||||
expert_count=EXPERT_COUNT,
|
||||
)
|
||||
item.nbytes = q.qtype_nbytes(qtype, item.shape)
|
||||
plan.append(item)
|
||||
|
||||
|
||||
def indexer_owner(layer, indexer_types):
|
||||
if layer == MTP_BLOCK:
|
||||
return layer
|
||||
owner = None
|
||||
for current in range(layer + 1):
|
||||
if indexer_types[current] == "full":
|
||||
owner = current
|
||||
if owner is None:
|
||||
q.fail(f"layer {layer} has no indexer owner")
|
||||
return owner
|
||||
|
||||
|
||||
def add_attention(plan, db, layer, indexer_types):
|
||||
prefix = f"{source_prefix(layer)}.self_attn"
|
||||
owner_prefix = f"{source_prefix(indexer_owner(layer, indexer_types))}.self_attn"
|
||||
mapping = (
|
||||
("attn_q_a", prefix, "q_a_proj.weight", q.QTYPE_Q8_0),
|
||||
("attn_q_a_norm", prefix, "q_a_layernorm.weight", q.QTYPE_F32),
|
||||
("attn_q_b", prefix, "q_b_proj.weight", q.QTYPE_Q8_0),
|
||||
("attn_kv_a_mqa", prefix, "kv_a_proj_with_mqa.weight", q.QTYPE_Q8_0),
|
||||
("attn_kv_a_norm", prefix, "kv_a_layernorm.weight", q.QTYPE_F32),
|
||||
("attn_output", prefix, "o_proj.weight", q.QTYPE_Q8_0),
|
||||
("indexer.attn_q_b", owner_prefix, "indexer.wq_b.weight", q.QTYPE_Q8_0),
|
||||
("indexer.attn_k", owner_prefix, "indexer.wk.weight", q.QTYPE_Q8_0),
|
||||
("indexer.k_norm", owner_prefix, "indexer.k_norm.weight", q.QTYPE_F32),
|
||||
("indexer.k_norm", owner_prefix, "indexer.k_norm.bias", q.QTYPE_F32),
|
||||
("indexer.proj", owner_prefix, "indexer.weights_proj.weight", q.QTYPE_F32),
|
||||
)
|
||||
for target, source_base, source_tail, qtype in mapping:
|
||||
suffix = ".bias" if source_tail.endswith(".bias") else ".weight"
|
||||
target_name = target + (".bias" if suffix == ".bias" else ".weight")
|
||||
q.add_regular(
|
||||
plan,
|
||||
db,
|
||||
f"blk.{layer}.{target_name}",
|
||||
f"{source_base}.{source_tail}",
|
||||
qtype,
|
||||
"dsa",
|
||||
)
|
||||
|
||||
combined = f"{prefix}.kv_b_proj.weight"
|
||||
expected = [64 * (192 + 256), 512]
|
||||
if db.info(combined)["shape"] != expected:
|
||||
q.fail(f"unexpected kv_b shape for layer {layer}: {db.info(combined)['shape']}")
|
||||
q.add_regular(
|
||||
plan,
|
||||
db,
|
||||
f"blk.{layer}.attn_k_b.weight",
|
||||
combined,
|
||||
q.QTYPE_Q8_0,
|
||||
"dsa",
|
||||
shape=(192, 512, 64),
|
||||
transform="kv_b_k",
|
||||
)
|
||||
q.add_regular(
|
||||
plan,
|
||||
db,
|
||||
f"blk.{layer}.attn_v_b.weight",
|
||||
combined,
|
||||
q.QTYPE_Q8_0,
|
||||
"dsa",
|
||||
shape=(512, 256, 64),
|
||||
transform="kv_b_v",
|
||||
)
|
||||
|
||||
|
||||
def add_ffn(plan, db, layer, provisional_q2k=False):
|
||||
prefix = f"{source_prefix(layer)}.mlp"
|
||||
if layer < 3:
|
||||
for part in ("gate", "up", "down"):
|
||||
q.add_regular(
|
||||
plan,
|
||||
db,
|
||||
f"blk.{layer}.ffn_{part}.weight",
|
||||
f"{prefix}.{part}_proj.weight",
|
||||
q.QTYPE_Q8_0,
|
||||
"dense_ffn",
|
||||
)
|
||||
return
|
||||
|
||||
q.add_regular(
|
||||
plan,
|
||||
db,
|
||||
f"blk.{layer}.ffn_gate_inp.weight",
|
||||
f"{prefix}.gate.weight",
|
||||
q.QTYPE_F32,
|
||||
"router",
|
||||
)
|
||||
q.add_regular(
|
||||
plan,
|
||||
db,
|
||||
f"blk.{layer}.exp_probs_b.bias",
|
||||
f"{prefix}.gate.e_score_correction_bias",
|
||||
q.QTYPE_F32,
|
||||
"router",
|
||||
)
|
||||
expert_qtype = (
|
||||
q.QTYPE_Q2_K
|
||||
if provisional_q2k or layer == MTP_BLOCK
|
||||
else q.QTYPE_IQ2_XXS
|
||||
)
|
||||
for part in ("gate", "up", "down"):
|
||||
add_experts(plan, db, layer, part, expert_qtype)
|
||||
for part in ("gate", "up", "down"):
|
||||
q.add_regular(
|
||||
plan,
|
||||
db,
|
||||
f"blk.{layer}.ffn_{part}_shexp.weight",
|
||||
f"{prefix}.shared_experts.{part}_proj.weight",
|
||||
q.QTYPE_Q8_0,
|
||||
"shared_expert",
|
||||
)
|
||||
|
||||
|
||||
def build_plan(db, config, provisional_q2k=False):
|
||||
plan = []
|
||||
q.add_regular(plan, db, "token_embd.weight", "model.embed_tokens.weight", q.QTYPE_Q8_0, "embedding")
|
||||
indexer_types = config["indexer_types"]
|
||||
if len(indexer_types) != MTP_BLOCK:
|
||||
q.fail(f"expected {MTP_BLOCK} indexer types, got {len(indexer_types)}")
|
||||
|
||||
for layer in range(BLOCK_COUNT):
|
||||
prefix = source_prefix(layer)
|
||||
q.add_regular(
|
||||
plan,
|
||||
db,
|
||||
f"blk.{layer}.attn_norm.weight",
|
||||
f"{prefix}.input_layernorm.weight",
|
||||
q.QTYPE_F32,
|
||||
"norm",
|
||||
)
|
||||
add_attention(plan, db, layer, indexer_types)
|
||||
q.add_regular(
|
||||
plan,
|
||||
db,
|
||||
f"blk.{layer}.ffn_norm.weight",
|
||||
f"{prefix}.post_attention_layernorm.weight",
|
||||
q.QTYPE_F32,
|
||||
"norm",
|
||||
)
|
||||
add_ffn(plan, db, layer, provisional_q2k)
|
||||
if layer == MTP_BLOCK:
|
||||
q.add_regular(
|
||||
plan,
|
||||
db,
|
||||
f"blk.{layer}.nextn.eh_proj.weight",
|
||||
f"{prefix}.eh_proj.weight",
|
||||
q.QTYPE_Q8_0,
|
||||
"mtp",
|
||||
)
|
||||
for target, source in (
|
||||
("enorm", "enorm.weight"),
|
||||
("hnorm", "hnorm.weight"),
|
||||
("shared_head_norm", "shared_head.norm.weight"),
|
||||
):
|
||||
q.add_regular(
|
||||
plan,
|
||||
db,
|
||||
f"blk.{layer}.nextn.{target}.weight",
|
||||
f"{prefix}.{source}",
|
||||
q.QTYPE_F32,
|
||||
"mtp",
|
||||
)
|
||||
|
||||
q.add_regular(plan, db, "output_norm.weight", "model.norm.weight", q.QTYPE_F32, "output")
|
||||
q.add_regular(plan, db, "output.weight", "lm_head.weight", q.QTYPE_Q8_0, "output")
|
||||
|
||||
names = [item.name for item in plan]
|
||||
if len(names) != 1809:
|
||||
q.fail(f"full GLM-5.3 plan has {len(names)} tensors, expected 1809")
|
||||
if len(names) != len(set(names)):
|
||||
q.fail("duplicate GGUF tensor names in full GLM-5.3 plan")
|
||||
offset = 0
|
||||
for item in plan:
|
||||
item.offset = offset
|
||||
offset += q.align(item.nbytes)
|
||||
return plan
|
||||
|
||||
|
||||
def model_metadata(hf_dir, source_revision):
|
||||
with open(os.path.join(hf_dir, "chat_template.jinja"), "rb") as fp:
|
||||
chat_template = fp.read()
|
||||
return [
|
||||
q.kv_string("general.architecture", "glm-dsa"),
|
||||
q.kv_string("general.name", "GLM-5.3"),
|
||||
q.kv_string("general.basename", "GLM-5.3"),
|
||||
q.kv_string("general.version", "5.3"),
|
||||
q.kv_string("general.license", "glm-5.3"),
|
||||
q.kv_string("general.source.repo_url", "https://huggingface.co/zai-org/GLM-5.3"),
|
||||
q.kv_string("general.source.revision", source_revision),
|
||||
q.kv_u32("general.alignment", q.GGUF_ALIGNMENT),
|
||||
q.kv_u32("general.quantization_version", 2),
|
||||
q.kv_u32("glm-dsa.block_count", BLOCK_COUNT),
|
||||
q.kv_u32("glm-dsa.nextn_predict_layers", 1),
|
||||
q.kv_u64("glm-dsa.context_length", 1048576),
|
||||
q.kv_u32("glm-dsa.embedding_length", 6144),
|
||||
q.kv_u32("glm-dsa.vocab_size", 154880),
|
||||
q.kv_u32("glm-dsa.feed_forward_length", 12288),
|
||||
q.kv_u32("glm-dsa.expert_feed_forward_length", 2048),
|
||||
q.kv_u32("glm-dsa.expert_count", EXPERT_COUNT),
|
||||
q.kv_u32("glm-dsa.expert_used_count", 8),
|
||||
q.kv_u32("glm-dsa.expert_shared_count", 1),
|
||||
q.kv_u32("glm-dsa.leading_dense_block_count", 3),
|
||||
q.kv_u32("glm-dsa.expert_group_count", 1),
|
||||
q.kv_u32("glm-dsa.expert_group_used_count", 1),
|
||||
q.kv_u32("glm-dsa.expert_gating_func", 2),
|
||||
q.kv_f32("glm-dsa.expert_weights_scale", 2.5),
|
||||
q.kv_bool("glm-dsa.expert_weights_norm", True),
|
||||
q.kv_f32("glm-dsa.attention.layer_norm_rms_epsilon", 1.0e-5),
|
||||
q.kv_u32("glm-dsa.attention.head_count", 64),
|
||||
q.kv_u32("glm-dsa.attention.head_count_kv", 1),
|
||||
q.kv_u32("glm-dsa.attention.key_length", 576),
|
||||
q.kv_u32("glm-dsa.attention.value_length", 512),
|
||||
q.kv_u32("glm-dsa.attention.key_length_mla", 256),
|
||||
q.kv_u32("glm-dsa.attention.value_length_mla", 256),
|
||||
q.kv_u32("glm-dsa.attention.q_lora_rank", 2048),
|
||||
q.kv_u32("glm-dsa.attention.kv_lora_rank", 512),
|
||||
q.kv_u32("glm-dsa.attention.indexer.head_count", 32),
|
||||
q.kv_u32("glm-dsa.attention.indexer.key_length", 128),
|
||||
q.kv_u32("glm-dsa.attention.indexer.top_k", 2048),
|
||||
q.kv_u32("glm-dsa.rope.dimension_count", 64),
|
||||
q.kv_f32("glm-dsa.rope.freq_base", 8000000.0),
|
||||
q.kv_string("tokenizer.chat_template", chat_template),
|
||||
]
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--hf", required=True, help="official zai-org/GLM-5.3 FP8 snapshot")
|
||||
parser.add_argument("--tokenizer-template", required=True, help="compatible GLM GGUF tokenizer source")
|
||||
parser.add_argument("--out", help="output GGUF")
|
||||
parser.add_argument("--imatrix", help="DwarfStar/llama.cpp importance matrix")
|
||||
parser.add_argument(
|
||||
"--provisional-q2k",
|
||||
action="store_true",
|
||||
help="use Q2_K for every routed expert to build a fast calibration model",
|
||||
)
|
||||
parser.add_argument("--threads", type=int, default=8)
|
||||
parser.add_argument("--source-revision", default="e0b07fd2751b42d5efa199cc02c2b271deadc516")
|
||||
parser.add_argument("--quants-library", help="path to libds4quants")
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
parser.add_argument("--resume", action="store_true")
|
||||
parser.add_argument("--overwrite", action="store_true")
|
||||
args = parser.parse_args()
|
||||
if args.threads < 1 or args.threads > 64:
|
||||
parser.error("--threads must be between 1 and 64")
|
||||
if not args.dry_run and not args.out:
|
||||
parser.error("--out is required unless --dry-run is used")
|
||||
return args
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
with open(os.path.join(args.hf, "config.json"), "rb") as fp:
|
||||
config = json.load(fp)
|
||||
if config.get("architectures") != ["GlmMoeDsaForCausalLM"]:
|
||||
q.fail(f"unexpected architecture: {config.get('architectures')!r}")
|
||||
db = q.SourceDB(args.hf, validate_glm53_full_index)
|
||||
try:
|
||||
plan = build_plan(db, config, args.provisional_q2k)
|
||||
tokenizer_records, template_tokens = q.load_tokenizer_records(args.tokenizer_template)
|
||||
q.validate_tokenizer_template(args.hf, template_tokens, 154880)
|
||||
kv_records = model_metadata(args.hf, args.source_revision)
|
||||
if args.dry_run:
|
||||
q.print_plan(plan, kv_records, tokenizer_records)
|
||||
else:
|
||||
q.write_gguf(args, plan, kv_records, tokenizer_records, db)
|
||||
print(f"glm53-full-quantize: wrote {args.out}", file=sys.stderr)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except (OSError, ValueError, json.JSONDecodeError) as error:
|
||||
print(f"glm53-full-quantize: error: {error}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
@@ -240,6 +240,62 @@ def validate_glm53_index(weight_map):
|
||||
fail(f"missing required tensors: {missing}")
|
||||
|
||||
|
||||
def validate_glm53_full_index(weight_map):
|
||||
names = set(weight_map)
|
||||
layers = set()
|
||||
indexer_layers = set()
|
||||
sparse_layers = set()
|
||||
expert_ids = {}
|
||||
expert_parts = {}
|
||||
layer_re = re.compile(r"^model\.layers\.(\d+)\.(.+)$")
|
||||
expert_re = re.compile(r"^mlp\.experts\.(\d+)\.(gate|up|down)_proj\.weight$")
|
||||
|
||||
allowed_top = {"model.embed_tokens.weight", "model.norm.weight", "lm_head.weight"}
|
||||
for name in names:
|
||||
match = layer_re.match(name)
|
||||
if not match:
|
||||
if name not in allowed_top and not name.endswith("_scale_inv"):
|
||||
fail(f"unknown full GLM-5.3 tensor: {name}")
|
||||
continue
|
||||
layer = int(match.group(1))
|
||||
tail = match.group(2)
|
||||
layers.add(layer)
|
||||
if tail == "self_attn.indexer.wk.weight":
|
||||
indexer_layers.add(layer)
|
||||
expert = expert_re.match(tail)
|
||||
if expert:
|
||||
sparse_layers.add(layer)
|
||||
expert_id = int(expert.group(1))
|
||||
expert_ids.setdefault(layer, set()).add(expert_id)
|
||||
expert_parts.setdefault((layer, expert_id), set()).add(expert.group(2))
|
||||
|
||||
expect_equal(layers, set(range(79)), "full GLM-5.3 layer set")
|
||||
expect_equal(sparse_layers, set(range(3, 79)), "full GLM-5.3 sparse FFN layer set")
|
||||
expected_indexers = {0, 1, 2, 78} | set(range(6, 78, 4))
|
||||
expect_equal(indexer_layers, expected_indexers, "full GLM-5.3 indexer owner layers")
|
||||
for layer in range(3, 79):
|
||||
expect_equal(expert_ids.get(layer), set(range(256)), f"layer {layer} expert ids")
|
||||
for expert in range(256):
|
||||
expect_equal(
|
||||
expert_parts.get((layer, expert)),
|
||||
{"gate", "up", "down"},
|
||||
f"layer {layer} expert {expert} projections",
|
||||
)
|
||||
|
||||
required = {
|
||||
"model.embed_tokens.weight",
|
||||
"model.norm.weight",
|
||||
"lm_head.weight",
|
||||
"model.layers.78.eh_proj.weight",
|
||||
"model.layers.78.enorm.weight",
|
||||
"model.layers.78.hnorm.weight",
|
||||
"model.layers.78.shared_head.norm.weight",
|
||||
}
|
||||
missing = sorted(required - names)
|
||||
if missing:
|
||||
fail(f"missing required full GLM-5.3 tensors: {missing}")
|
||||
|
||||
|
||||
def validate_fp8_scales(tensors):
|
||||
for name, info in tensors.items():
|
||||
if info["dtype"] != "F8_E4M3" or not name.endswith(".weight"):
|
||||
|
||||
@@ -39,6 +39,7 @@ QTYPE_Q8_K = 15
|
||||
QTYPE_IQ2_XXS = 16
|
||||
QTYPE_I8 = 24
|
||||
QTYPE_BF16 = 30
|
||||
QTYPE_MXFP4 = 39
|
||||
|
||||
QTYPE_NAMES = {
|
||||
QTYPE_F32: "F32",
|
||||
@@ -50,6 +51,7 @@ QTYPE_NAMES = {
|
||||
QTYPE_IQ2_XXS: "IQ2_XXS",
|
||||
QTYPE_I8: "I8",
|
||||
QTYPE_BF16: "BF16",
|
||||
QTYPE_MXFP4: "MXFP4",
|
||||
}
|
||||
|
||||
QTYPE_LAYOUT = {
|
||||
@@ -62,6 +64,7 @@ QTYPE_LAYOUT = {
|
||||
QTYPE_IQ2_XXS: (256, 66),
|
||||
QTYPE_I8: (1, 1),
|
||||
QTYPE_BF16: (1, 2),
|
||||
QTYPE_MXFP4: (32, 17),
|
||||
}
|
||||
|
||||
LAYER_PREFIX = "model.language_model.layers"
|
||||
@@ -276,6 +279,7 @@ class TensorPlan:
|
||||
row_count: int | None = None
|
||||
expert_layer: int | None = None
|
||||
expert_part: str | None = None
|
||||
expert_count: int | None = None
|
||||
transform: str | None = None
|
||||
raw_copy: bool = False
|
||||
expert_scale: bool = False
|
||||
@@ -288,11 +292,22 @@ class TensorPlan:
|
||||
|
||||
|
||||
class SourceDB:
|
||||
def __init__(self, hf_dir):
|
||||
def __init__(
|
||||
self,
|
||||
hf_dir,
|
||||
index_validator=validate_glm53_index,
|
||||
scale_validator=validate_fp8_scales,
|
||||
tensor_prefix=None,
|
||||
):
|
||||
self.hf_dir = hf_dir
|
||||
index_path = os.path.join(hf_dir, "model.safetensors.index.json")
|
||||
document, self.weight_map = load_index(index_path)
|
||||
validate_glm53_index(self.weight_map)
|
||||
index_validator(self.weight_map)
|
||||
if tensor_prefix is not None:
|
||||
self.weight_map = {name: shard for name, shard in self.weight_map.items()
|
||||
if name.startswith(tensor_prefix)}
|
||||
if not self.weight_map:
|
||||
fail(f"source index has no tensors matching {tensor_prefix!r}")
|
||||
self.declared_bytes = document.get("metadata", {}).get("total_size")
|
||||
self.tensors = {}
|
||||
self._fds = {}
|
||||
@@ -303,6 +318,8 @@ class SourceDB:
|
||||
if not os.path.isfile(path):
|
||||
fail(f"missing source shard {path}")
|
||||
for name, info in load_safetensors_header(path).items():
|
||||
if tensor_prefix is not None and not name.startswith(tensor_prefix):
|
||||
continue
|
||||
if self.weight_map.get(name) != shard:
|
||||
fail(f"index assigns {name} to {self.weight_map.get(name)!r}, not {shard}")
|
||||
if name in self.tensors:
|
||||
@@ -312,7 +329,7 @@ class SourceDB:
|
||||
if set(self.tensors) != set(self.weight_map):
|
||||
missing = sorted(set(self.weight_map) - set(self.tensors))
|
||||
fail(f"source headers are incomplete; first missing tensor is {missing[0]}")
|
||||
validate_fp8_scales(self.tensors)
|
||||
scale_validator(self.tensors)
|
||||
|
||||
def info(self, name):
|
||||
try:
|
||||
@@ -405,8 +422,10 @@ def add_experts(plan, db, layer, part, qtype):
|
||||
(shape[1], shape[0], 288),
|
||||
qtype,
|
||||
f"routed_{part}",
|
||||
source=f"{source_prefix(layer)}.mlp.experts.{{expert}}.{part}_proj.weight",
|
||||
expert_layer=layer,
|
||||
expert_part=part,
|
||||
expert_count=288,
|
||||
)
|
||||
item.nbytes = qtype_nbytes(qtype, item.shape)
|
||||
plan.append(item)
|
||||
@@ -545,10 +564,8 @@ def native_fp8_plan(db, plan):
|
||||
covered = set()
|
||||
for item in plan:
|
||||
if item.is_expert:
|
||||
first = (
|
||||
f"{source_prefix(item.expert_layer)}.mlp.experts.0."
|
||||
f"{item.expert_part}_proj.weight"
|
||||
)
|
||||
expert_count = item.expert_count or 288
|
||||
first = item.source.format(expert=0)
|
||||
info = db.info(first)
|
||||
if info["dtype"] != "F8_E4M3":
|
||||
fail(f"native expert source is not FP8 E4M3: {first}")
|
||||
@@ -561,21 +578,20 @@ def native_fp8_plan(db, plan):
|
||||
fail(f"native FP8 scale is not F32: {first}_scale_inv")
|
||||
scale = TensorPlan(
|
||||
item.name + "_scale_inv",
|
||||
(scale_info["shape"][1], scale_info["shape"][0], 288),
|
||||
(scale_info["shape"][1], scale_info["shape"][0], expert_count),
|
||||
QTYPE_F32,
|
||||
item.role + "_scale",
|
||||
source=item.source,
|
||||
expert_layer=item.expert_layer,
|
||||
expert_part=item.expert_part,
|
||||
expert_count=expert_count,
|
||||
raw_copy=True,
|
||||
expert_scale=True,
|
||||
)
|
||||
scale.nbytes = qtype_nbytes(scale.qtype, scale.shape)
|
||||
native.append(scale)
|
||||
for expert in range(288):
|
||||
source = (
|
||||
f"{source_prefix(item.expert_layer)}.mlp.experts.{expert}."
|
||||
f"{item.expert_part}_proj.weight"
|
||||
)
|
||||
for expert in range(expert_count):
|
||||
source = item.source.format(expert=expert)
|
||||
if db.info(source)["dtype"] != "F8_E4M3":
|
||||
fail(f"native expert source is not FP8 E4M3: {source}")
|
||||
if db.info(source)["shape"] != info["shape"]:
|
||||
@@ -796,7 +812,7 @@ class Quantizer:
|
||||
elif info["dtype"] == "F16":
|
||||
array = np.frombuffer(raw, dtype="<f2").astype(np.float32).reshape(shape)
|
||||
elif info["dtype"] == "F8_E4M3":
|
||||
if len(shape) != 2 or shape[0] % 128 or shape[1] % 128:
|
||||
if len(shape) != 2:
|
||||
fail(f"unsupported FP8 shape for {name}: {shape}")
|
||||
codes = np.frombuffer(raw, dtype=np.uint8).reshape(shape)
|
||||
if np.any((codes & 0x7F) == 0x7F):
|
||||
@@ -805,8 +821,15 @@ class Quantizer:
|
||||
scale_info = db.info(scale_name)
|
||||
if scale_info["dtype"] != "F32":
|
||||
fail(f"{scale_name} is not F32")
|
||||
expected_scale_shape = [(dim + 127) // 128 for dim in shape]
|
||||
if scale_info["shape"] != expected_scale_shape:
|
||||
fail(
|
||||
f"{scale_name} has shape {scale_info['shape']}, "
|
||||
f"expected {expected_scale_shape}"
|
||||
)
|
||||
scales = np.frombuffer(db.read(scale_name), dtype="<f4").reshape(scale_info["shape"])
|
||||
expanded = np.repeat(np.repeat(scales, 128, axis=0), 128, axis=1)
|
||||
expanded = expanded[: shape[0], : shape[1]]
|
||||
array = self.fp8_lut[codes] * expanded
|
||||
else:
|
||||
fail(f"unsupported source dtype {info['dtype']} for {name}")
|
||||
@@ -879,11 +902,11 @@ class Imatrix:
|
||||
fail(f"nonfinite imatrix values for {name}")
|
||||
self.entries[name] = values
|
||||
|
||||
def expert(self, tensor_name, expert, width):
|
||||
def expert(self, tensor_name, expert, width, expert_count=288):
|
||||
values = self.entries.get(tensor_name)
|
||||
if values is None:
|
||||
return None
|
||||
expected = 288 * width
|
||||
expected = expert_count * width
|
||||
if values.size != expected:
|
||||
fail(f"imatrix {tensor_name} has {values.size} values, expected {expected}")
|
||||
result = values[expert * width : (expert + 1) * width]
|
||||
@@ -901,7 +924,7 @@ def tensor_header(item):
|
||||
)
|
||||
|
||||
|
||||
def print_plan(plan, kv_records, tokenizer_records):
|
||||
def print_plan(plan, kv_records, tokenizer_records, alignment=GGUF_ALIGNMENT):
|
||||
by_type = {}
|
||||
by_role = {}
|
||||
for item in plan:
|
||||
@@ -909,8 +932,8 @@ def print_plan(plan, kv_records, tokenizer_records):
|
||||
by_role[item.role] = by_role.get(item.role, 0) + item.nbytes
|
||||
kv_bytes = sum(map(len, kv_records)) + sum(map(len, tokenizer_records))
|
||||
tensor_info_bytes = sum(len(tensor_header(item)) for item in plan)
|
||||
data_offset = align(4 + 4 + 8 + 8 + kv_bytes + tensor_info_bytes)
|
||||
data_bytes = sum(align(item.nbytes) for item in plan)
|
||||
data_offset = align(4 + 4 + 8 + 8 + kv_bytes + tensor_info_bytes, alignment)
|
||||
data_bytes = sum(align(item.nbytes, alignment) for item in plan)
|
||||
print(f"tensors: {len(plan)}")
|
||||
print(f"metadata_records: {len(kv_records) + len(tokenizer_records)}")
|
||||
print(f"metadata_bytes: {data_offset}")
|
||||
@@ -928,23 +951,32 @@ def transform_regular(values, item):
|
||||
return values
|
||||
if item.transform not in ("kv_b_k", "kv_b_v"):
|
||||
fail(f"unknown transform {item.transform} for {item.name}")
|
||||
if values.shape != (64 * 512, 512):
|
||||
fail(f"{item.name}: expected combined kv_b shape (32768, 512), got {values.shape}")
|
||||
per_head = values.reshape(64, 512, 512)
|
||||
if values.ndim != 2 or len(item.shape) != 3:
|
||||
fail(f"{item.name}: invalid combined kv_b shapes {values.shape} -> {item.shape}")
|
||||
n_head = item.shape[2]
|
||||
rank = item.shape[1] if item.transform == "kv_b_k" else item.shape[0]
|
||||
selected = item.shape[0] if item.transform == "kv_b_k" else item.shape[1]
|
||||
if values.shape[1] != rank or values.shape[0] % n_head:
|
||||
fail(f"{item.name}: invalid combined kv_b shape {values.shape}")
|
||||
combined = values.shape[0] // n_head
|
||||
other = combined - selected
|
||||
if other <= 0:
|
||||
fail(f"{item.name}: invalid combined kv_b dimensions {combined} and {selected}")
|
||||
k_dim = selected if item.transform == "kv_b_k" else other
|
||||
v_dim = other if item.transform == "kv_b_k" else selected
|
||||
per_head = values.reshape(n_head, k_dim + v_dim, rank)
|
||||
if item.transform == "kv_b_k":
|
||||
return per_head[:, :256, :].transpose(0, 2, 1).copy()
|
||||
return per_head[:, 256:, :].copy()
|
||||
return per_head[:, :k_dim, :].transpose(0, 2, 1).copy()
|
||||
return per_head[:, k_dim:, :].copy()
|
||||
|
||||
|
||||
def iter_native_tensor_bytes(item, db, np):
|
||||
if not item.raw_copy:
|
||||
fail(f"{item.name}: tensor is not a native payload")
|
||||
if item.is_expert:
|
||||
for expert in range(288):
|
||||
source = (
|
||||
f"{source_prefix(item.expert_layer)}.mlp.experts.{expert}."
|
||||
f"{item.expert_part}_proj.weight"
|
||||
)
|
||||
expert_count = item.expert_count or 288
|
||||
for expert in range(expert_count):
|
||||
source = item.source.format(expert=expert)
|
||||
if item.expert_scale:
|
||||
source += "_scale_inv"
|
||||
yield from db.iter_read(source)
|
||||
@@ -981,26 +1013,25 @@ def write_regular(fp, item, db, quantizer):
|
||||
|
||||
|
||||
def write_experts(fp, item, db, quantizer, imatrix, threads):
|
||||
layer = item.expert_layer
|
||||
part = item.expert_part
|
||||
expert_count = item.expert_count or 288
|
||||
width = item.shape[0]
|
||||
|
||||
def convert(expert):
|
||||
source = f"{source_prefix(layer)}.mlp.experts.{expert}.{part}_proj.weight"
|
||||
source = item.source.format(expert=expert)
|
||||
if item.raw_copy:
|
||||
if item.expert_scale:
|
||||
source += "_scale_inv"
|
||||
return db.read(source), False
|
||||
values = quantizer.to_f32(db, source)
|
||||
weights = imatrix.expert(item.name, expert, width)
|
||||
weights = imatrix.expert(item.name, expert, width, expert_count)
|
||||
return quantizer.encode(values, item.qtype, weights), weights is None
|
||||
|
||||
per_expert = item.nbytes // 288
|
||||
per_expert = item.nbytes // expert_count
|
||||
fallback_count = 0
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor:
|
||||
window = max(threads * 2, 1)
|
||||
for start in range(0, 288, window):
|
||||
futures = [executor.submit(convert, expert) for expert in range(start, min(start + window, 288))]
|
||||
for start in range(0, expert_count, window):
|
||||
futures = [executor.submit(convert, expert) for expert in range(start, min(start + window, expert_count))]
|
||||
for future in futures:
|
||||
data, fallback = future.result()
|
||||
fallback_count += int(fallback)
|
||||
@@ -1010,12 +1041,22 @@ def write_experts(fp, item, db, quantizer, imatrix, threads):
|
||||
if imatrix.entries and fallback_count:
|
||||
print(
|
||||
f"glm53-quantize: {item.name} used weight-based fallback for "
|
||||
f"{fallback_count}/288 unobserved experts",
|
||||
f"{fallback_count}/{expert_count} unobserved experts",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
|
||||
def conversion_signature(plan, kv_records, tokenizer_records):
|
||||
def file_sha256(path):
|
||||
if not path:
|
||||
return None
|
||||
digest = hashlib.sha256()
|
||||
with open(path, "rb") as fp:
|
||||
while chunk := fp.read(8 << 20):
|
||||
digest.update(chunk)
|
||||
return digest.digest()
|
||||
|
||||
|
||||
def conversion_signature(plan, kv_records, tokenizer_records, imatrix_path=None):
|
||||
digest = hashlib.sha256()
|
||||
for record in kv_records + tokenizer_records:
|
||||
digest.update(record)
|
||||
@@ -1023,7 +1064,12 @@ def conversion_signature(plan, kv_records, tokenizer_records):
|
||||
digest.update(tensor_header(item))
|
||||
digest.update((item.source or "").encode())
|
||||
digest.update((item.transform or "").encode())
|
||||
digest.update(struct.pack("<I", item.expert_count or 0))
|
||||
digest.update(bytes((item.raw_copy, item.expert_scale)))
|
||||
imatrix_digest = file_sha256(imatrix_path)
|
||||
if imatrix_digest is not None:
|
||||
digest.update(b"imatrix\0")
|
||||
digest.update(imatrix_digest)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
@@ -1066,7 +1112,7 @@ def write_gguf(args, plan, kv_records, tokenizer_records, db):
|
||||
|
||||
partial = args.out + ".partial"
|
||||
resume_path = partial + ".resume.json"
|
||||
signature = conversion_signature(plan, kv_records, tokenizer_records)
|
||||
signature = conversion_signature(plan, kv_records, tokenizer_records, args.imatrix)
|
||||
if os.path.exists(args.out) and not args.overwrite:
|
||||
fail(f"output exists: {args.out}; use --overwrite")
|
||||
if args.overwrite:
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Restore a page-aligned V4.1 GGUF from verified standard GGUF split files.
|
||||
|
||||
The downloader verifies each input's published SHA-256. This helper preserves
|
||||
all tensor bytes and metadata, removes split metadata, restores 16 KiB alignment,
|
||||
and verifies the original whole-file checksum before publishing the output.
|
||||
Inputs are retained; an interrupted .assembling file can be resumed.
|
||||
"""
|
||||
import argparse
|
||||
import fcntl
|
||||
import hashlib
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import struct
|
||||
import sys
|
||||
|
||||
ALIGNMENT = 16384
|
||||
CHUNK = 16 << 20
|
||||
SCALARS = {0: "B", 1: "b", 2: "H", 3: "h", 4: "I", 5: "i", 6: "f",
|
||||
7: "?", 10: "Q", 11: "q", 12: "d"}
|
||||
TYPES = {0: (1, 4), 1: (1, 2), 8: (32, 34), 24: (1, 1), 39: (32, 17)}
|
||||
SPLIT_KEYS = {"split.no", "split.count", "split.tensors.count"}
|
||||
|
||||
|
||||
def align(n, alignment):
|
||||
return (n + alignment - 1) // alignment * alignment
|
||||
|
||||
|
||||
def read(fp, n):
|
||||
data = fp.read(n)
|
||||
if len(data) != n:
|
||||
raise ValueError("Truncated GGUF")
|
||||
return data
|
||||
|
||||
|
||||
def scalar(fp, kind):
|
||||
return struct.unpack("<" + kind, read(fp, struct.calcsize("<" + kind)))[0]
|
||||
|
||||
|
||||
def string(fp):
|
||||
size = scalar(fp, "Q")
|
||||
if size > 128 << 20:
|
||||
raise ValueError("GGUF string exceeds header limit")
|
||||
return read(fp, size).decode("utf-8")
|
||||
|
||||
|
||||
def skip_value(fp, kind, file_size, depth=0):
|
||||
if depth > 4:
|
||||
raise ValueError("Nested GGUF metadata exceeds limit")
|
||||
if kind == 8:
|
||||
fp.seek(scalar(fp, "Q"), 1)
|
||||
elif kind == 9:
|
||||
element, count = scalar(fp, "I"), scalar(fp, "Q")
|
||||
if element in SCALARS:
|
||||
fp.seek(count * struct.calcsize("<" + SCALARS[element]), 1)
|
||||
else:
|
||||
if count > file_size:
|
||||
raise ValueError("Invalid GGUF array length")
|
||||
for _ in range(count):
|
||||
skip_value(fp, element, file_size, depth + 1)
|
||||
elif kind in SCALARS:
|
||||
fp.seek(struct.calcsize("<" + SCALARS[kind]), 1)
|
||||
else:
|
||||
raise ValueError("Unsupported GGUF metadata type")
|
||||
if fp.tell() > min(file_size, 128 << 20):
|
||||
raise ValueError("Invalid GGUF metadata extent")
|
||||
|
||||
|
||||
def read_header(fp, file_size):
|
||||
if read(fp, 4) != b"GGUF" or scalar(fp, "I") != 3:
|
||||
raise ValueError("Expected GGUF v3")
|
||||
nt, nk = scalar(fp, "Q"), scalar(fp, "Q")
|
||||
if nt > 100000 or nk > 100000:
|
||||
raise ValueError("Invalid GGUF directory count")
|
||||
metadata, values, kinds, tensors = [], {}, {}, []
|
||||
for _ in range(nk):
|
||||
start = fp.tell()
|
||||
key, kind = string(fp), scalar(fp, "I")
|
||||
if key in kinds:
|
||||
raise ValueError("Duplicate GGUF metadata key")
|
||||
kinds[key] = kind
|
||||
value_start = fp.tell()
|
||||
skip_value(fp, kind, file_size)
|
||||
end = fp.tell()
|
||||
if kind in SCALARS or kind == 8:
|
||||
fp.seek(value_start)
|
||||
values[key] = string(fp) if kind == 8 else scalar(fp, SCALARS[kind])
|
||||
fp.seek(start)
|
||||
metadata.append((key, read(fp, end - start)))
|
||||
alignment = values.get("general.alignment", 32)
|
||||
if (not isinstance(alignment, int) or not 1 <= alignment <= ALIGNMENT or
|
||||
alignment & (alignment - 1)):
|
||||
raise ValueError("Invalid GGUF alignment")
|
||||
offset = 0
|
||||
for _ in range(nt):
|
||||
name, rank = string(fp), scalar(fp, "I")
|
||||
if not 1 <= rank <= 4:
|
||||
raise ValueError("Invalid tensor rank")
|
||||
shape = [scalar(fp, "Q") for _ in range(rank)]
|
||||
kind, relative = scalar(fp, "I"), scalar(fp, "Q")
|
||||
if kind not in TYPES or any(n == 0 for n in shape):
|
||||
raise ValueError("Unsupported MXFP4 model tensor")
|
||||
block, size = TYPES[kind]
|
||||
if shape[0] % block or relative != offset:
|
||||
raise ValueError("Invalid tensor shape or offset")
|
||||
nbytes = math.prod(shape) // block * size
|
||||
tensors.append((name, shape, kind, relative, nbytes))
|
||||
offset += align(nbytes, alignment)
|
||||
data_offset = align(fp.tell(), alignment)
|
||||
if data_offset + offset != file_size:
|
||||
raise ValueError("Incorrect shard file size")
|
||||
return metadata, values, kinds, tensors, data_offset
|
||||
|
||||
|
||||
def encoded_string(value):
|
||||
data = value.encode("utf-8")
|
||||
return struct.pack("<Q", len(data)) + data
|
||||
|
||||
|
||||
def merge_plan(headers):
|
||||
all_tensors, names = [], set()
|
||||
for number, (metadata, values, kinds, tensors, _) in enumerate(headers):
|
||||
if (kinds.get("split.no") != 2 or kinds.get("split.count") != 2 or
|
||||
kinds.get("split.tensors.count") != 5 or
|
||||
values.get("split.no") != number or
|
||||
values.get("split.count") != len(headers)):
|
||||
raise ValueError("Incorrect shard order/count or split metadata")
|
||||
if number and any(key not in SPLIT_KEYS for key, _ in metadata):
|
||||
raise ValueError("Unexpected metadata outside first shard")
|
||||
for tensor in tensors:
|
||||
if tensor[0] in names:
|
||||
raise ValueError("Duplicate tensor across shards")
|
||||
names.add(tensor[0])
|
||||
all_tensors.append(tensor)
|
||||
if any(h[1].get("split.tensors.count") != len(all_tensors) for h in headers):
|
||||
raise ValueError("Incomplete GGUF shard set")
|
||||
first = headers[0]
|
||||
if (first[1].get("general.architecture") != "deepseek41" or
|
||||
first[2].get("general.alignment") != 4):
|
||||
raise ValueError("Expected DeepSeek V4.1 metadata and explicit alignment")
|
||||
metadata = []
|
||||
for key, raw in first[0]:
|
||||
if key in SPLIT_KEYS:
|
||||
continue
|
||||
if key.startswith("split."):
|
||||
raise ValueError("Unexpected split metadata")
|
||||
if key == "general.alignment":
|
||||
raw = encoded_string(key) + struct.pack("<II", 4, ALIGNMENT)
|
||||
metadata.append(raw)
|
||||
header = bytearray(b"GGUF" + struct.pack("<IQQ", 3, len(all_tensors), len(metadata)))
|
||||
header.extend(b"".join(metadata))
|
||||
offset = 0
|
||||
for name, shape, kind, _, nbytes in all_tensors:
|
||||
header.extend(encoded_string(name) + struct.pack("<I", len(shape)))
|
||||
header.extend(struct.pack("<" + "Q" * len(shape), *shape))
|
||||
header.extend(struct.pack("<IQ", kind, offset))
|
||||
offset += align(nbytes, ALIGNMENT)
|
||||
header.extend(bytes(align(len(header), ALIGNMENT) - len(header)))
|
||||
return bytes(header), len(header) + offset
|
||||
|
||||
|
||||
def verify(path, expected_bytes, expected_sha):
|
||||
if path.stat().st_size != expected_bytes:
|
||||
raise ValueError("Incorrect file size: " + str(path))
|
||||
print("Verifying SHA-256: " + str(path), flush=True)
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as fp:
|
||||
for data in iter(lambda: fp.read(CHUNK), b""):
|
||||
digest.update(data)
|
||||
if digest.hexdigest() != expected_sha:
|
||||
raise ValueError("Checksum mismatch: " + str(path))
|
||||
|
||||
|
||||
def merge(paths, output, expected_bytes, expected_sha):
|
||||
pending = Path(str(output) + ".assembling")
|
||||
with Path(str(pending) + ".lock").open("a") as lock:
|
||||
fcntl.flock(lock, fcntl.LOCK_EX)
|
||||
if output.exists():
|
||||
verify(output, expected_bytes, expected_sha)
|
||||
return
|
||||
headers = []
|
||||
for path in paths:
|
||||
with path.open("rb") as fp:
|
||||
headers.append(read_header(fp, os.fstat(fp.fileno()).st_size))
|
||||
header, total = merge_plan(headers)
|
||||
if total != expected_bytes:
|
||||
raise ValueError("Merged layout does not match the original GGUF size")
|
||||
completed = pending.stat().st_size if pending.exists() else 0
|
||||
if completed > total:
|
||||
raise ValueError("Invalid partial assembly; move it aside before retrying")
|
||||
if shutil.disk_usage(output.parent).free < total - completed + (1 << 30):
|
||||
raise ValueError("Not enough disk space to merge MXFP4; keep the shards and retry")
|
||||
print("Merging MXFP4 shards (retaining inputs; restoring 16 KiB alignment)", flush=True)
|
||||
with pending.open("r+b" if pending.exists() else "x+b") as dst:
|
||||
def append(data):
|
||||
# Verify an existing prefix before appending. Never truncate an
|
||||
# interrupted or unrelated file to make it fit this assembly.
|
||||
overlap = min(len(data), max(0, completed - dst.tell()))
|
||||
if overlap and read(dst, overlap) != data[:overlap]:
|
||||
raise ValueError("Partial assembly differs from shards; move it aside before retrying")
|
||||
dst.write(data[overlap:])
|
||||
|
||||
append(header)
|
||||
for path, h in zip(paths, headers):
|
||||
with path.open("rb") as src:
|
||||
for _, _, _, offset, nbytes in h[3]:
|
||||
src.seek(h[4] + offset)
|
||||
left = nbytes
|
||||
while left:
|
||||
data = read(src, min(CHUNK, left))
|
||||
append(data)
|
||||
left -= len(data)
|
||||
append(bytes(align(nbytes, ALIGNMENT) - nbytes))
|
||||
dst.flush()
|
||||
os.fsync(dst.fileno())
|
||||
verify(pending, expected_bytes, expected_sha)
|
||||
# Publish without overwriting an output created by another process.
|
||||
os.link(pending, output)
|
||||
pending.unlink()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--output", required=True, type=Path)
|
||||
parser.add_argument("--expected-bytes", required=True, type=int)
|
||||
parser.add_argument("--sha256", required=True)
|
||||
parser.add_argument("shards", nargs="+", type=Path)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
merge(args.shards, args.output, args.expected_bytes, args.sha256)
|
||||
except (OSError, ValueError, OverflowError) as exc:
|
||||
sys.exit(str(exc))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -3,7 +3,7 @@
|
||||
This directory contains the prompts, tracked official fixtures, and scripts used
|
||||
to compare local GGUF variants against hosted-model continuations.
|
||||
|
||||
The metric is target-token negative log likelihood: collect a deterministic
|
||||
The main metric is target-token negative log likelihood: collect an
|
||||
official continuation, then ask each local GGUF how much probability it assigns
|
||||
to that exact continuation token by token. This avoids judging quality from one
|
||||
sampled answer.
|
||||
@@ -18,12 +18,23 @@ calling hosted APIs:
|
||||
- `data/glm53-flash-openrouter-zai-fp8-100`: 100 GLM 5.3 Flash continuations
|
||||
from OpenRouter's pinned Z.AI FP8 endpoint. The endpoint does not expose
|
||||
logprobs, so these are deterministic continuation fixtures.
|
||||
- `data/glm53-flash-openrouter-zai-fp8-long`: eight code-context continuations
|
||||
from the same FP8 endpoint, with 3K-7K prompts that exercise sparse attention.
|
||||
Use the rendered prompts below to preserve the API's reasoning prefix.
|
||||
- `data/flash`: 100 DeepSeek V4 Flash 0731 continuations collected from the
|
||||
official DeepSeek API with `top_logprobs=20`.
|
||||
- `data/pro`: 100 DeepSeek V4 PRO preview continuations collected from the
|
||||
official DeepSeek API with `top_logprobs=20`.
|
||||
- `data/pro-0813`: 100 DeepSeek V4 PRO 0813 continuations collected from the
|
||||
official DeepSeek API with `top_logprobs=20`.
|
||||
- `data/qwen38-flash-alibaba-100`: 100 non-thinking Qwen3.8 Flash
|
||||
continuations from Alibaba through OpenRouter, with top-five logprobs.
|
||||
- `data/qwen38-flash-alibaba-long`: 12 archive/code continuations from the
|
||||
same endpoint, with prompts from 2K to 24K tokens.
|
||||
- [deepseek-v4.1-flash-20260919-router](deepseek-v4.1-flash-20260919-router/README.md):
|
||||
112 fresh official V4.1 Flash continuations, including 12 longer prompts up
|
||||
to 23K tokens. Temperature-1 samples with top-20 logprobs, intended for
|
||||
paired numerical comparisons. These are not V4 Flash or Vision-Exp vectors.
|
||||
|
||||
DeepSeek V4 Flash also has tracked official smoke vectors in
|
||||
`tests/test-vectors/`. Those vectors drive `./ds4_test --logprob-vectors` and
|
||||
@@ -32,17 +43,25 @@ include short prompts plus long-prompt attention cases.
|
||||
The hosted APIs expose output-token logprobs and top-logprob alternatives, not
|
||||
full vocabulary logits.
|
||||
|
||||
The scorer verifies each API token's bytes against the local token boundaries,
|
||||
not just the number of tokens. If they differ, it still scores the continuation
|
||||
text but skips API logprob comparisons for that case. Alternatives containing
|
||||
Unicode replacement characters are excluded because the original token bytes
|
||||
may have been lost by the provider.
|
||||
|
||||
## 2. Collect Official Continuations
|
||||
|
||||
For the tracked DeepSeek V4 Flash 0731 fixture:
|
||||
The Flash 0731 fixture is historical. The official API now redirects the old
|
||||
Flash names to V4.1; do not overwrite `data/flash` with new calls. Collect
|
||||
current V4.1 references in a separate directory:
|
||||
|
||||
```sh
|
||||
export DEEPSEEK_API_KEY=...
|
||||
python3 gguf-tools/quality-testing/collect_official.py \
|
||||
--model deepseek-v4-flash \
|
||||
--model deepseek-flash \
|
||||
--endpoint https://api.deepseek.com/chat/completions \
|
||||
--prompts gguf-tools/quality-testing/prompts.jsonl \
|
||||
--out gguf-tools/quality-testing/data/flash \
|
||||
--out /tmp/deepseek-v4.1-flash-new \
|
||||
--count 100 \
|
||||
--max-tokens 24 \
|
||||
--top-logprobs 20 \
|
||||
@@ -113,13 +132,45 @@ The prompt list is tracked in `prompts.jsonl`. Curated fixture directories are
|
||||
also tracked after review; ad-hoc API collection directories should stay
|
||||
untracked until they are intentionally promoted into the release QA set.
|
||||
|
||||
Use `--system TEXT` when the reference needs an explicit system message.
|
||||
`--system ''` sends an empty message, which is different from omitting it:
|
||||
some providers insert a default system prompt when none is supplied. The
|
||||
collector records this setting and rejects a changed system message on resume.
|
||||
For a nonempty system message, render the matching local chat prompt and use
|
||||
`--rendered-prompt`; the scorer's ordinary prompt mode adds no system message.
|
||||
|
||||
For Qwen, use the following settings in a new output directory:
|
||||
|
||||
```sh
|
||||
python3 gguf-tools/quality-testing/collect_official.py \
|
||||
--out /tmp/qwen-official \
|
||||
--model qwen/qwen3.8-flash \
|
||||
--endpoint https://openrouter.ai/api/v1/chat/completions \
|
||||
--provider-order alibaba --require-parameters \
|
||||
--require-response-provider Alibaba --require-response-model qwen/qwen3.8-flash \
|
||||
--system '' --thinking omit --reasoning-effort none \
|
||||
--count 100 --max-tokens 64 --top-logprobs 5
|
||||
```
|
||||
|
||||
This reads `OPENROUTER_API_KEY` from the environment and disables provider
|
||||
fallbacks. Omitting `--system ''` caused Alibaba to add 13 prompt tokens in
|
||||
the September 2026 checks. The empty message matches Qwen's published
|
||||
no-thinking template and the scorer's ordinary prompt mode.
|
||||
|
||||
Alibaba does not disclose this endpoint's precision. The
|
||||
[official model card](https://huggingface.co/Qwen/Qwen3.8-Flash-Next) describes
|
||||
hosted Flash as based on Flash Next, not as a byte-identical checkpoint.
|
||||
Agreement with this service is an external quality check, not proof of exact
|
||||
native-weight inference.
|
||||
|
||||
## 3. Build The Local Scorer
|
||||
|
||||
```sh
|
||||
make -C gguf-tools quality-score
|
||||
```
|
||||
|
||||
The scorer links against the DS4 runtime and uses Metal by default.
|
||||
The scorer links against the DS4 runtime, using Metal on macOS and CUDA on
|
||||
Linux. On a DGX Spark, pass `CUDA_ARCH=sm_121` to the build command.
|
||||
|
||||
Build the optional llama.cpp control scorer with:
|
||||
|
||||
@@ -151,6 +202,71 @@ Use `data/flash/manifest.tsv` for Flash GGUFs and
|
||||
care which model produced the manifest; the manifest path selects the
|
||||
continuation set.
|
||||
|
||||
Add `--continued-prefill N` to process each prompt in two calls, leaving its
|
||||
last `N` tokens for the second call. This checks continued-prefill quality
|
||||
against the same official answers. Every prompt must contain more than `N`
|
||||
tokens; otherwise the scorer fails instead of silently skipping the test.
|
||||
|
||||
Add `--session-batch N` (2 to 8) to score an official continuation alongside
|
||||
unrelated, independently advancing sessions. The scorer rotates their row
|
||||
order on every step. Compare against a run without this option using the same
|
||||
manifest, model and context; budget memory for all `N` sessions.
|
||||
|
||||
Check session isolation separately on a dedicated Metal host:
|
||||
|
||||
```sh
|
||||
DS4_TEST_MODEL=MODEL.gguf DS4_TEST_BATCH_ISOLATION=1 \
|
||||
DS4_TEST_SESSION_COUNT=4 DS4_TEST_DECODE_STEPS=32 \
|
||||
MTL_DEBUG_LAYER=1 ./tests/test_metal_session_batch
|
||||
```
|
||||
|
||||
This requires exact logits when companion prompts and row order change, then
|
||||
checks a continued prefill and resumed serial decoding. It also checks that
|
||||
invalid batches leave the target unchanged. It does not replace serial/batch
|
||||
quality comparisons: different arithmetic can round differently without
|
||||
sessions contaminating one another. Use `DS4_TEST_PROMPT_FILE` and
|
||||
`DS4_TEST_CONTEXT_SIZE` to repeat at longer prefixes. For physical TP, the test
|
||||
also accepts `DS4_TEST_TP_RDMA_DEVICE` and `DS4_TEST_TP_GID_INDEX` alongside
|
||||
its coordinator/worker settings. Throughput measurements must run separately
|
||||
without Metal API validation.
|
||||
|
||||
For Metal V4.1 prefill scheduling changes, also run:
|
||||
|
||||
```sh
|
||||
make tests/test_deepseek41_prefill
|
||||
./tests/test_deepseek41_prefill --dispatch
|
||||
MTL_DEBUG_LAYER=1 ./tests/test_deepseek41_prefill MODEL.gguf speed-bench/promessi_sposi.txt
|
||||
```
|
||||
|
||||
The model test uses SSD streaming and two 128K sessions. Run it alone on a
|
||||
dedicated host with at least 128 GiB RAM. It alternates small and large appends
|
||||
through 113K context, checking dispatch, progress, unchanged-prefix reuse,
|
||||
saved state and subsequent decoding against a control without decoder deferral.
|
||||
Keep the official continuation checks too: this scheduling test does not judge
|
||||
quality across different floating-point operation orders. Measure speed without
|
||||
Metal API validation, separately for initial and continued prefills.
|
||||
|
||||
Run the same mixed-prefix test over physical RDMA on two dedicated Metal hosts.
|
||||
Start the worker with the same model and a 131072-token context, then the test
|
||||
on the coordinator (replace the host and RDMA device names):
|
||||
|
||||
```sh
|
||||
# Worker
|
||||
MTL_DEBUG_LAYER=1 ./ds4 -m MODEL.gguf --ctx 131072 \
|
||||
--tensor-parallel --role worker --coordinator COORDINATOR 19455 \
|
||||
--transport rdma --rdma-device WORKER_DEVICE --rdma-gid-index 1
|
||||
|
||||
# Coordinator
|
||||
MTL_DEBUG_LAYER=1 ./tests/test_deepseek41_prefill --tensor-parallel \
|
||||
MODEL.gguf speed-bench/promessi_sposi.txt COORDINATOR 19455 COORDINATOR_DEVICE 1
|
||||
```
|
||||
|
||||
This compares equal TP prefill partitions and queued versus synchronous decode,
|
||||
including full logits and cache state. TP snapshots rebuild both ranks from
|
||||
tokens, so restoration is compared with an equivalent fresh replay. Also run
|
||||
the official scorer in TP mode for initial and continued prompts; scheduling
|
||||
parity alone does not establish model quality.
|
||||
|
||||
Add `--quality` to disable DS4's speed-oriented numerical shortcuts. For an
|
||||
independent llama.cpp comparison of a DeepSeek V4 GGUF, use the same manifest
|
||||
and the token-identical DS4 prompt renderer:
|
||||
@@ -164,6 +280,25 @@ gguf-tools/quality-testing/score_llama \
|
||||
deepseek-ds4
|
||||
```
|
||||
|
||||
GLM's hosted endpoint requires reasoning. A no-thinking local prompt does not
|
||||
match a reply generated after reasoning, even at temperature zero. For the long
|
||||
GLM fixtures, render the official `Reasoning Effort: Low` template and include
|
||||
the returned reasoning before scoring the answer:
|
||||
|
||||
```sh
|
||||
python3 gguf-tools/quality-testing/render_glm_references.py \
|
||||
gguf-tools/quality-testing/data/glm53-flash-openrouter-zai-fp8-long
|
||||
gguf-tools/quality-testing/score_official /path/to/GLM-5.3-Flash-Q2.gguf \
|
||||
gguf-tools/quality-testing/data/glm53-flash-openrouter-zai-fp8-long/manifest-rendered.tsv \
|
||||
/tmp/glm-long.tsv 8192 --rendered-prompt
|
||||
```
|
||||
|
||||
`--rendered-prompt` reads the prompt file with its existing chat markers; it
|
||||
does not add another chat template. The renderer can also be applied to the
|
||||
older 100-case GLM 5.3 Flash fixture. Keep rendered and legacy no-thinking
|
||||
scores separate: their prefixes differ. Z.AI supplies no token logprobs, so
|
||||
judge these runs by continuation NLL and greedy agreement, not logprob parity.
|
||||
|
||||
For a full-residency vs SSD-streaming comparison, score the same model twice and
|
||||
add the streaming flags to one run:
|
||||
|
||||
@@ -191,7 +326,10 @@ Output fields:
|
||||
- `first_token_matches`: how often the local greedy first token matches the
|
||||
official first token.
|
||||
- `avg_greedy_lcp`: average greedy longest common prefix against the official
|
||||
continuation.
|
||||
continuation. This measures exact agreement, not general answer quality;
|
||||
one early mismatch discards all later agreement. Do not use it alone as a
|
||||
quality gate, especially for sampled references. Compare paired NLL and
|
||||
API probability agreement under matching execution settings as well.
|
||||
- `api_target_mae`: when the manifest includes `response_file`, absolute
|
||||
local-vs-API logprob delta for aligned official output tokens.
|
||||
- `api_top_coverage`: fraction of API top-logprob alternatives that map exactly
|
||||
@@ -202,3 +340,70 @@ Output fields:
|
||||
local top-N for the same position.
|
||||
- `api_top_mae`: local-vs-API logprob MAE over mapped API top alternatives.
|
||||
- `api_pair_rate`: pairwise ordering agreement among mapped API alternatives.
|
||||
|
||||
## 6. Validate regression artifacts
|
||||
|
||||
Two standard-library Python tools check saved artifacts without running inference. For matched `score_official` runs with API logprob coverage, require the complete intended manifest and finite, consistent counts and scores:
|
||||
|
||||
```sh
|
||||
python3 gguf-tools/quality-testing/validate_scores.py \
|
||||
--manifest gguf-tools/quality-testing/data/flash/manifest.tsv \
|
||||
--baseline /tmp/old.tsv --candidate /tmp/new.tsv --strict-identical > /tmp/scores.json
|
||||
```
|
||||
|
||||
Exit 0 with `--strict-identical` requires every per-case value to match; omit it to report valid differences without deciding their acceptability. Unlike `compare_scores.py`, validation rejects missing/duplicate cases, malformed fields and changed coverage denominators. This stricter mode requires the current four-column manifest and scorer TSV format with nonzero API coverage; use the existing comparator for continuation-only references without API logprobs.
|
||||
|
||||
Compare complete vocabulary dumps from `ds4-bench --dump-frontier-logits-dir`:
|
||||
|
||||
```sh
|
||||
python3 gguf-tools/quality-testing/compare_frontier_logits.py \
|
||||
/tmp/old-logits /tmp/new-logits --frontiers 2048 4096 --ctx 8192 \
|
||||
--model /models/model.gguf --backend rocm --quality false --quant-bits 2 --vocab 129280 \
|
||||
--output /tmp/frontiers.json
|
||||
```
|
||||
|
||||
Supply the exact expected metadata: `ctx` is allocated capacity; each frontier is total context, and prefill is its increase from the previous frontier (initially zero). Each directory must contain exactly the requested dumps. Exit 0 requires complete finite float32 bit identity; exit 1 reports drift, invalid artifacts or an operational error. JSON distinguishes `identical`, `drift` and `invalid`, with full-vector hashes, differing counts and maximum absolute differences. The output path must be new. The parser follows the writer's nine-significant-digit float format and preserves signed zero.
|
||||
|
||||
Matching argmax is insufficient: a measured 8K–64K comparison retained all four argmax IDs while full vectors differed, with maximum absolute difference 7.45228. This is numerical drift, not by itself proof of worse generation quality. Record prompt/weight hashes, source/build identity, settings and successful run completion separately; these tools cannot recover that provenance from score tables or dumps, and do not replace model-quality checks.
|
||||
|
||||
Run the host-only positive and negative controls:
|
||||
|
||||
```sh
|
||||
python3 -m unittest discover -s gguf-tools/quality-testing/tests -v
|
||||
```
|
||||
|
||||
## 7. Qwen3.8 Spark Baseline
|
||||
|
||||
Measured on 2026-09-15, using one DGX Spark per quant. The short set has 100
|
||||
cases and 5,696 target tokens; the long set has 12 cases and 766 target tokens,
|
||||
with prefixes from 1,971 to 23,985 tokens. These are ordinary decoding and
|
||||
prefill checks, without MTP or vision.
|
||||
|
||||
| Suite | GGUF | API top-token agreement | NLL, default | NLL, `--quality` |
|
||||
| --- | --- | ---: | ---: | ---: |
|
||||
| Short | Q2 | 88.90% | 0.352980 | 0.352915 |
|
||||
| Short | Q4 | 92.08% | 0.290297 | 0.290538 |
|
||||
| Long | Q2 | 93.99% | 0.154199 | 0.154106 |
|
||||
| Long | Q4 | 97.26% | 0.124611 | 0.125546 |
|
||||
|
||||
Agreement is measured after feeding the same official continuation prefix,
|
||||
not as a percentage of correct answers. The two short cases with damaged
|
||||
emoji metadata contribute to NLL but not API agreement, which covers 5,568
|
||||
short-set tokens. All 766 long-set tokens align. Every local prompt and target
|
||||
token ID was also checked against Qwen's published tokenizer.
|
||||
|
||||
The default fast path shows no material loss relative to `--quality` in these
|
||||
sets. The paired case-bootstrap intervals include zero for both quants and
|
||||
both suites. Q4 is closer to the hosted reference than Q2 on average.
|
||||
These measurements do not prove identical native weights or correctness at
|
||||
every context length.
|
||||
|
||||
Leaving the last 1 or 256 prompt tokens for a separate continued prefill
|
||||
changed aggregate NLL by less than 0.0008 for either quant. Independent CLI
|
||||
answers at about 2K and 24K context also retrieved the correct archive owner
|
||||
and revision, and gave the correct clamp outputs and boundary tests, for
|
||||
both Q2 and Q4.
|
||||
|
||||
Raw per-case scores, weight/source hashes and comparison details are retained
|
||||
in [short results](data/qwen38-flash-alibaba-100/cuda-spark-20260915/results.json)
|
||||
and [long results](data/qwen38-flash-alibaba-long/cuda-spark-20260915/results.json).
|
||||
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
@@ -134,13 +135,17 @@ def request_one(
|
||||
provider_order: list[str],
|
||||
provider_allow_fallbacks: bool,
|
||||
provider_require_parameters: bool,
|
||||
temperature: float = 0,
|
||||
system: str | None = None,
|
||||
) -> dict:
|
||||
payload = {
|
||||
"model": model,
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"temperature": 0,
|
||||
"temperature": temperature,
|
||||
"stream": False,
|
||||
}
|
||||
if system is not None:
|
||||
payload["messages"].insert(0, {"role": "system", "content": system})
|
||||
if top_logprobs > 0:
|
||||
payload["logprobs"] = True
|
||||
payload["top_logprobs"] = top_logprobs
|
||||
@@ -181,6 +186,8 @@ def fetch_with_retry(
|
||||
provider_order: list[str],
|
||||
provider_allow_fallbacks: bool,
|
||||
provider_require_parameters: bool,
|
||||
temperature: float = 0,
|
||||
system: str | None = None,
|
||||
) -> dict:
|
||||
delay = 1.0
|
||||
for attempt in range(6):
|
||||
@@ -198,6 +205,8 @@ def fetch_with_retry(
|
||||
provider_order,
|
||||
provider_allow_fallbacks,
|
||||
provider_require_parameters,
|
||||
temperature,
|
||||
system,
|
||||
)
|
||||
except urllib.error.HTTPError as e:
|
||||
body = e.read().decode("utf-8", "replace")
|
||||
@@ -218,11 +227,17 @@ def main() -> int:
|
||||
ap.add_argument("--out", default="gguf-tools/quality-testing/data")
|
||||
ap.add_argument("--prompts", default="gguf-tools/quality-testing/prompts.jsonl")
|
||||
ap.add_argument("--model", default=MODEL)
|
||||
ap.add_argument("--system", default=None,
|
||||
help="explicit system message, including an empty string; "
|
||||
"some providers insert a default when this is omitted")
|
||||
ap.add_argument("--endpoint", default=ENDPOINT)
|
||||
ap.add_argument("--api-key-env", default=None)
|
||||
ap.add_argument("--count", type=int, default=100)
|
||||
ap.add_argument("--max-tokens", type=int, default=24)
|
||||
ap.add_argument("--top-logprobs", type=int, default=5)
|
||||
ap.add_argument("--temperature", type=float, default=0,
|
||||
help="sampling temperature, 0..2; use 1 for unscaled logprobs "
|
||||
"from providers reporting probabilities after sampling transforms")
|
||||
ap.add_argument("--thinking", choices=("disabled", "enabled", "omit"), default="disabled")
|
||||
ap.add_argument("--reasoning-effort",
|
||||
choices=("xhigh", "high", "medium", "low", "minimal", "none", "omit"),
|
||||
@@ -235,11 +250,17 @@ def main() -> int:
|
||||
ap.add_argument("--allow-provider-fallbacks", action="store_true")
|
||||
ap.add_argument("--require-parameters", action="store_true",
|
||||
help="for OpenRouter, route only to endpoints advertising the requested parameters")
|
||||
ap.add_argument("--require-response-model",
|
||||
help="reject a response that reports a different model ID")
|
||||
ap.add_argument("--require-response-provider",
|
||||
help="reject a response from a different provider")
|
||||
ap.add_argument("--resume", action="store_true",
|
||||
help="reuse complete cases already present in the output directory")
|
||||
args = ap.parse_args()
|
||||
if args.top_logprobs < 0 or args.top_logprobs > 20:
|
||||
raise SystemExit("--top-logprobs must be between 0 and 20")
|
||||
if not math.isfinite(args.temperature) or not 0 <= args.temperature <= 2:
|
||||
raise SystemExit("--temperature must be between 0 and 2")
|
||||
|
||||
openrouter = "openrouter.ai" in args.endpoint
|
||||
api_key_env = args.api_key_env or ("OPENROUTER_API_KEY" if openrouter else "DEEPSEEK_API_KEY")
|
||||
@@ -265,6 +286,12 @@ def main() -> int:
|
||||
(out / "prompts").mkdir(parents=True, exist_ok=True)
|
||||
(out / "continuations").mkdir(parents=True, exist_ok=True)
|
||||
(out / "responses").mkdir(parents=True, exist_ok=True)
|
||||
system_path = out / "request_system.json"
|
||||
if args.resume and (system_path.exists() or any((out / "responses").glob("*.json"))):
|
||||
saved_system = json.loads(system_path.read_text()) if system_path.exists() else None
|
||||
if saved_system != args.system:
|
||||
raise RuntimeError("saved system message differs from --system; use a new output directory")
|
||||
system_path.write_text(json.dumps(args.system, ensure_ascii=False) + "\n", encoding="utf-8")
|
||||
|
||||
manifest = out / "manifest.tsv"
|
||||
rows = []
|
||||
@@ -308,6 +335,8 @@ def main() -> int:
|
||||
provider_order,
|
||||
args.allow_provider_fallbacks,
|
||||
provider_require_parameters,
|
||||
args.temperature,
|
||||
args.system,
|
||||
)
|
||||
choice = response["choices"][0]
|
||||
content = choice.get("message", {}).get("content")
|
||||
@@ -319,6 +348,19 @@ def main() -> int:
|
||||
else:
|
||||
raise RuntimeError(f"{case_id}: provider returned three empty continuations")
|
||||
choice = response["choices"][0]
|
||||
if (args.require_response_model and
|
||||
response.get("model") != args.require_response_model):
|
||||
raise RuntimeError(
|
||||
f"{case_id}: expected model {args.require_response_model!r}, "
|
||||
f"got {response.get('model')!r}"
|
||||
)
|
||||
if (args.require_response_provider and
|
||||
response.get("provider") != args.require_response_provider):
|
||||
raise RuntimeError(
|
||||
f"{case_id}: expected provider "
|
||||
f"{args.require_response_provider!r}, got "
|
||||
f"{response.get('provider')!r}"
|
||||
)
|
||||
if response.get("model"):
|
||||
observed_models.add(response["model"])
|
||||
if response.get("provider"):
|
||||
@@ -342,10 +384,11 @@ def main() -> int:
|
||||
"schema": "ds4-official-continuations-v1",
|
||||
"created_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||
"model": args.model,
|
||||
"system": args.system,
|
||||
"endpoint": args.endpoint,
|
||||
"observed_models": sorted(observed_models),
|
||||
"observed_providers": sorted(observed_providers),
|
||||
"temperature": 0,
|
||||
"temperature": args.temperature,
|
||||
"max_tokens": args.max_tokens,
|
||||
"top_logprobs": args.top_logprobs,
|
||||
"thinking": thinking,
|
||||
@@ -354,6 +397,8 @@ def main() -> int:
|
||||
"provider_order": provider_order,
|
||||
"allow_provider_fallbacks": args.allow_provider_fallbacks,
|
||||
"require_parameters": provider_require_parameters,
|
||||
"required_response_model": args.require_response_model,
|
||||
"required_response_provider": args.require_response_provider,
|
||||
"case_count": len(rows),
|
||||
}
|
||||
(out / "collection.json").write_text(
|
||||
|
||||
Loaded 100 of 1820 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user