Compare commits
| 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 | ||
|
|
b1b4ea0364 | ||
|
|
6cf658a4da | ||
|
|
10badf696c | ||
|
|
9254c3daf4 | ||
|
|
e1147cdcbd | ||
|
|
87495f6cc9 | ||
|
|
639d4eb8d1 | ||
|
|
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 | ||
|
|
8adb22178c | ||
|
|
767e517639 | ||
|
|
c6433cb51b | ||
|
|
ab993cb2a7 | ||
|
|
b8cfc1431f | ||
|
|
04478e0ff6 | ||
|
|
1df8ff0d9f | ||
|
|
fea665ce46 | ||
|
|
0415d3b0d6 | ||
|
|
747eafc2c2 | ||
|
|
9f95d9f5ce | ||
|
|
b0c31af367 | ||
|
|
47713296d5 | ||
|
|
147109a3cc | ||
|
|
8db89fe083 | ||
|
|
c1d4597a80 | ||
|
|
c35cf38ba9 | ||
|
|
3c63c06c5a | ||
|
|
ed5c1a92c6 | ||
|
|
abd4ed3885 | ||
|
|
8d683d6616 | ||
|
|
769a8ba42d | ||
|
|
ecdea42ee7 | ||
|
|
babca3d0fe | ||
|
|
7891f278d0 | ||
|
|
47ecd56841 | ||
|
|
affa9c3054 | ||
|
|
df1a49a50d | ||
|
|
bfd0682494 | ||
|
|
5d90ebace6 | ||
|
|
f52dc1ca91 | ||
|
|
4ac740ad23 | ||
|
|
27180ec19f | ||
|
|
39a8f18b03 | ||
|
|
ada805ad59 | ||
|
|
4d7028e066 | ||
|
|
84cc882352 | ||
|
|
5186e2cdc3 | ||
|
|
42033ee3f4 | ||
|
|
0ad494e514 | ||
|
|
fd2d5e05a1 | ||
|
|
5b738048fc | ||
|
|
acb66c66c4 | ||
|
|
c6743a4fbf | ||
|
|
023614eab7 | ||
|
|
319614935f | ||
|
|
0658dac845 | ||
|
|
0ead8a87b0 | ||
|
|
e6a6ab518a | ||
|
|
0e8fb54487 | ||
|
|
4a0b305789 | ||
|
|
1be41c85f4 | ||
|
|
92d83e6ecd | ||
|
|
ad4d05c824 | ||
|
|
ecec0cc31d | ||
|
|
3826f52175 | ||
|
|
c8163cb1a8 | ||
|
|
b64d3e0689 | ||
|
|
71d8c2a072 | ||
|
|
bfeb61d178 | ||
|
|
1bc0f44b86 | ||
|
|
0e125e6e54 | ||
|
|
4e401a10e1 | ||
|
|
f75a065678 | ||
|
|
bcd1e57c5e | ||
|
|
0e89a0eeff | ||
|
|
8a703b6707 | ||
|
|
e9ded97afe | ||
|
|
b030961104 | ||
|
|
e0c63d9345 | ||
|
|
532ec8b784 | ||
|
|
222b2cb9f9 | ||
|
|
6747e7718d | ||
|
|
0ab7aaf0f8 | ||
|
|
7fb28303df | ||
|
|
d14ce35661 | ||
|
|
464c1f6b62 | ||
|
|
7adb2a497d | ||
|
|
355da75342 | ||
|
|
a968c08d76 | ||
|
|
51a1c14f35 | ||
|
|
fe2d3b0c12 | ||
|
|
7694112475 | ||
|
|
24fa85eafa | ||
|
|
b1f88933ce | ||
|
|
af80694e95 | ||
|
|
a169cffa4e | ||
|
|
5c318ed804 | ||
|
|
8340f351a6 | ||
|
|
0fa15c6c67 | ||
|
|
422ceb1a0b | ||
|
|
727836a79d | ||
|
|
9735ad8014 | ||
|
|
c7689dbcf0 | ||
|
|
f02d79cb6b | ||
|
|
8e49a5cc2f | ||
|
|
ae504c963e | ||
|
|
8e0f997333 | ||
|
|
55b17c6a80 | ||
|
|
0dd0d36617 | ||
|
|
6841f07239 | ||
|
|
7b55707c00 | ||
|
|
b7e9f00911 | ||
|
|
6537ba089b | ||
|
|
67d97ab48d | ||
|
|
df341214a9 | ||
|
|
8579ec82c0 | ||
|
|
d0a04acead | ||
|
|
1464c0f202 | ||
|
|
73dd0f9004 | ||
|
|
1c5e24b448 | ||
|
|
108eb3abe3 | ||
|
|
3b3b29eb3c | ||
|
|
fa4a475eca | ||
|
|
9f3256179f | ||
|
|
e21f84db12 | ||
|
|
1728313187 | ||
|
|
bff6f5eb01 | ||
|
|
cfaee47995 | ||
|
|
574ccbba32 | ||
|
|
0976bdd68b | ||
|
|
56ec892607 | ||
|
|
45f4ef9fec | ||
|
|
08fecd939e | ||
|
|
c784026f67 | ||
|
|
6f087f4258 | ||
|
|
0f62c620e8 | ||
|
|
e50104f60f | ||
|
|
80df56af40 | ||
|
|
4893e0c40f | ||
|
|
7bec128aaa | ||
|
|
1f862bd282 | ||
|
|
860e673123 | ||
|
|
725b084db3 | ||
|
|
0a62b39686 | ||
|
|
48392e3d36 | ||
|
|
ac5cb2c85f | ||
|
|
24903f33e4 | ||
|
|
81f8534e3b | ||
|
|
d69a017181 | ||
|
|
96c3ba4777 | ||
|
|
f284165dc8 | ||
|
|
c0fc1f704e | ||
|
|
a51e6ec6e9 | ||
|
|
4591cb175e | ||
|
|
74e8f11918 | ||
|
|
df048879fa | ||
|
|
d250a7c07c | ||
|
|
2f49b27edd | ||
|
|
6882a0f2f4 | ||
|
|
9e1988bcd1 | ||
|
|
f23f13d5c3 | ||
|
|
1e8f16ca05 | ||
|
|
54b36ed9ba | ||
|
|
11da8b334f | ||
|
|
45a9dcb0d3 | ||
|
|
2510636cb0 | ||
|
|
4f11251b5f | ||
|
|
4ab75720eb | ||
|
|
4e80dd7502 | ||
|
|
367a64c1bc | ||
|
|
4d7fbbf5f3 | ||
|
|
cfb1ff9a07 | ||
|
|
dd837f033d | ||
|
|
764987bc2c | ||
|
|
ea2e76681c | ||
|
|
34b3862d80 | ||
|
|
7a84671c07 | ||
|
|
41a3c79d47 | ||
|
|
5920d6808b | ||
|
|
5e61ad5ca9 | ||
|
|
312934c14a | ||
|
|
b7f5cddc61 | ||
|
|
0e609ab85b | ||
|
|
c9e1013e71 | ||
|
|
26e8e88665 | ||
|
|
9463fd73d9 | ||
|
|
f6f5b92631 | ||
|
|
fa8b0b9301 | ||
|
|
7a424a456a | ||
|
|
0a7ad776b9 | ||
|
|
2dc022d90d | ||
|
|
80afbe7836 | ||
|
|
507fd7c6a9 | ||
|
|
784bc77b21 | ||
|
|
34d8046bbf | ||
|
|
fd6d056f5c | ||
|
|
84d2de4b3f | ||
|
|
d7a89093fa | ||
|
|
24f2c83d50 | ||
|
|
3ce677710e | ||
|
|
efdadd41e2 | ||
|
|
646db66656 | ||
|
|
cb5103449b | ||
|
|
427e281d24 | ||
|
|
e0824ddcaa | ||
|
|
a185c36dc4 | ||
|
|
ef8d923ad7 | ||
|
|
66cbce5289 | ||
|
|
36ee8c1407 | ||
|
|
36cd0ca915 | ||
|
|
005afedcd9 | ||
|
|
63d9874c54 | ||
|
|
fc9efd1cc7 | ||
|
|
519c4d8fe7 |
No files matched your search
+117
-1
@@ -4,15 +4,131 @@
|
||||
/ds4-eval
|
||||
/ds4-agent
|
||||
/ds4_native
|
||||
/ds4_agent_test
|
||||
/ds4_server_test
|
||||
/ds4_test
|
||||
/ds4flash.gguf
|
||||
/TODO.md
|
||||
/gguf/
|
||||
/gguf
|
||||
/core
|
||||
/core.*
|
||||
/tests/test_engine_mgpu_placement
|
||||
/tests/test_engine_mgpu_runtime
|
||||
/tests/test_engine_mgpu_refusal
|
||||
/tests/test_engine_correctness
|
||||
/tests/test_gpu_args
|
||||
/tests/test_gpu_lookup_cache_strict
|
||||
/tests/test_gpu_model_cache
|
||||
/tests/test_gpu_xdev
|
||||
/tests/test_cuda_mixed_batch
|
||||
/tests/test_cuda_session_batch
|
||||
/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__/
|
||||
*.pyc
|
||||
/gguf-tools/libds4quants.dylib
|
||||
/gguf-tools/libds4quants.so
|
||||
/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
|
||||
|
||||
+2053
-31
File diff suppressed because it is too large.
Load diff
+3
-133
@@ -1,136 +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`.
|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,157 @@
|
||||
# Vendored llama.cpp mmq kernels
|
||||
|
||||
This directory contains source files copied verbatim from
|
||||
[llama.cpp's `ggml-cuda` backend](https://github.com/ggml-org/llama.cpp/tree/master/ggml/src/ggml-cuda),
|
||||
plus a thin ds4-side adapter (`ds4_ggml_stubs.{h,cu}` and `ds4_mmq.{h,cu}`)
|
||||
that lets the templated CUDA kernels compile and link without the full ggml
|
||||
runtime.
|
||||
|
||||
## Why these files are vendored, not submoduled
|
||||
|
||||
ds4 is a flat, self-contained C/CUDA codebase with no third-party build
|
||||
dependencies. A submodule would force consumers to clone, configure, and
|
||||
build all of llama.cpp just to use a few quantized-matmul kernels. Vendoring
|
||||
keeps ds4 self-contained at the cost of a periodic re-sync.
|
||||
|
||||
## Upstream pin
|
||||
|
||||
| Field | Value |
|
||||
|---------------|--------------------------------------------------------------------------------------------------------------------|
|
||||
| Source | https://github.com/ggml-org/llama.cpp |
|
||||
| Commit | `5c0e9468378eba6bf3cc1989ff5d62fbbe4d9e3a` |
|
||||
| Commit date | 2026-05-14 |
|
||||
| Commit title | `ggml-hexagon: cpy: add contiguous fast-path in reshape copy (#23076)` |
|
||||
| License | MIT (`LICENSE` at the repository root, copyright "2023-2026 The ggml authors"). Compatible with ds4's MIT license. |
|
||||
|
||||
## File inventory
|
||||
|
||||
| File | Origin in llama.cpp | Status | Lines |
|
||||
|-----------------------|----------------------------------------------|--------------------------------------------------------------------------|-------|
|
||||
| `mmq.cuh` | `ggml/src/ggml-cuda/mmq.cuh` | verbatim | 4176 |
|
||||
| `mma.cuh` | `ggml/src/ggml-cuda/mma.cuh` | verbatim | 1456 |
|
||||
| `vecdotq.cuh` | `ggml/src/ggml-cuda/vecdotq.cuh` | verbatim | 1317 |
|
||||
| `quantize.cuh` | `ggml/src/ggml-cuda/quantize.cuh` | verbatim | 41 |
|
||||
| `quantize.cu` | `ggml/src/ggml-cuda/quantize.cu` | verbatim | 443 |
|
||||
| `mmid.cuh` | `ggml/src/ggml-cuda/mmid.cuh` | verbatim | 5 |
|
||||
| `mmid.cu` | `ggml/src/ggml-cuda/mmid.cu` | verbatim | 164 |
|
||||
| `mmvq.cuh` | `ggml/src/ggml-cuda/mmvq.cuh` | patched (Step 6): `mul_mat_vec_q_switch_type` proto exposed; ggml-tensor entries gated on `DS4_MMVQ_INCLUDE_GGML_ENTRIES` | ~36 |
|
||||
| `mmvq.cu` | `ggml/src/ggml-cuda/mmvq.cu` | patched: `mul_mat_vec_q_switch_type` promoted from `static`; `ggml_cuda_mul_mat_vec_q` + `ggml_cuda_op_mul_mat_vec_q` gated on `DS4_MMVQ_INCLUDE_GGML_ENTRIES` | 1163 |
|
||||
| `unary.cuh` | `ggml/src/ggml-cuda/unary.cuh` | verbatim (needed by `mmvq.cu` for inline GLU epilogues) | 114 |
|
||||
| `common.cuh` | `ggml/src/ggml-cuda/common.cuh` | verbatim | 1489 |
|
||||
| `ggml-common.h` | `ggml/src/ggml-common.h` | verbatim | 1900 |
|
||||
| `vendors/cuda.h` | `ggml/src/ggml-cuda/vendors/cuda.h` | verbatim | 28 |
|
||||
| `ggml.h` | (new) | redirect to `ds4_ggml_stubs.h` | 5 |
|
||||
| `ggml-impl.h` | (new) | redirect to `ds4_ggml_stubs.h` | 5 |
|
||||
| `ggml-cuda.h` | (new) | redirect to `ds4_ggml_stubs.h` | 5 |
|
||||
| `ds4_ggml_stubs.h` | (new) | shim: ggml_type enum, macros, info struct, type_size lookups | ~280 |
|
||||
| `ds4_ggml_stubs.cu` | (new) | shim impls: `ggml_cuda_info`, naive pool, `ggml_backend_cuda_context::*` | ~110 |
|
||||
| `ds4_mmq.h` | (new) | public C ABI for ds4 to call | ~70 |
|
||||
| `ds4_mmq.cu` | (new) | host wrappers; Phase 0 instantiates `mul_mat_q_case<Q8_0>` only | ~120 |
|
||||
|
||||
**Total vendored:** ~11,000 lines of CUDA. **Total shim/adapter:** ~600 lines.
|
||||
|
||||
## What llama.cpp's `mmq.cu` does that we don't vendor
|
||||
|
||||
The upstream `ggml/src/ggml-cuda/mmq.cu` (372 lines) is the ggml-backend
|
||||
dispatch entry. It talks to `ggml_tensor` / `ggml_backend_buffer_get_usage`,
|
||||
does its own activation Q8_1 quantization, and wires into the ggml op graph.
|
||||
We **don't vendor it.** Instead `ds4_mmq.cu` provides ds4-style entries
|
||||
that bypass the ggml graph and call the per-type `mul_mat_q_case<T>` directly.
|
||||
|
||||
## Symbol-resolution table
|
||||
|
||||
Symbols the vendored files reference, and how they resolve in this directory:
|
||||
|
||||
| Symbol category | Resolution |
|
||||
|------------------------------------------------|--------------------------------------------------------------------------------------------------|
|
||||
| `GGML_ASSERT`, `GGML_ABORT`, `GGML_UNUSED` | Macros in `ds4_ggml_stubs.h` |
|
||||
| `GGML_PAD`, `GGML_UNUSED_VARS` | Macros in `ds4_ggml_stubs.h` |
|
||||
| `GGML_CUDA_CC_*` constants | Defined in vendored `common.cuh` |
|
||||
| `GGML_TYPE_*` enum values | Defined in `ds4_ggml_stubs.h::enum ggml_type` |
|
||||
| `GGML_CUDA_MAX_DEVICES`, `GGML_CUDA_NAME` | Macros in `ds4_ggml_stubs.h` |
|
||||
| `GGML_MAX_DIMS`, `GGML_MAX_SRC` | Macros in `ds4_ggml_stubs.h` |
|
||||
| `block_q8_0`, `block_q2_K`, `block_iq2_xxs` | Defined in vendored `ggml-common.h` (gated by `GGML_COMMON_IMPL_CUDA`) |
|
||||
| `ggml_half`, `ggml_half2` | `uint16_t` typedefs in `ds4_ggml_stubs.h` |
|
||||
| `ggml_type_size`, `ggml_blck_size` | Inline implementations in `ds4_ggml_stubs.h` |
|
||||
| `ggml_cuda_info()` | Singleton in `ds4_ggml_stubs.cu`, populated via `cudaGetDeviceProperties` |
|
||||
| `ggml_cuda_get_device`, `ggml_cuda_set_device` | Thin wrappers in `ds4_ggml_stubs.cu` |
|
||||
| `ggml_time_us` | `std::chrono::steady_clock` in `ds4_ggml_stubs.cu` |
|
||||
| `ggml_cuda_pool`, `ggml_cuda_pool_alloc` | Defined in vendored `common.cuh`. Concrete `ds4_naive_pool` subclass in `ds4_ggml_stubs.cu` |
|
||||
| `ggml_backend_cuda_context` | Defined in vendored `common.cuh`. `new_pool_for_device` and `~dtor` provided in `ds4_ggml_stubs.cu` |
|
||||
| `ggml_tensor` | Forward declaration in `ds4_ggml_stubs.h`. Never dereferenced - only held as pointer. |
|
||||
| `ggml_glu_op` | Enum stub in `ds4_ggml_stubs.h` (fusion args present in common.cuh but unused by ds4) |
|
||||
| `CUDA_CHECK`, `CUBLAS_CHECK` | Macros in `ds4_ggml_stubs.h` |
|
||||
| `ggml_cuda_launch_mm_ids_helper` | Defined in vendored `mmid.cu` |
|
||||
| `ggml_cuda_should_use_mmq` | Defined in vendored `mmq.cu` - **we re-implement in `ds4_mmq.cu`** since we don't vendor mmq.cu |
|
||||
| `ggml_cuda_mul_mat_q*` | Upstream's host entry. **Replaced** by `ds4_mmq_q8_0_dense` and family. |
|
||||
|
||||
## Things we deliberately do NOT support
|
||||
|
||||
- **CUDA graphs.** ds4 manages its own streams; `USE_CUDA_GRAPH` is undefined.
|
||||
- **HIP / MUSA / AMD backends.** vendored code's HIP/MUSA `#ifdef` branches are dead in our build but kept for upstream diff-cleanliness.
|
||||
- **The full `ggml_tensor` type.** No tensor introspection - shapes and strides come in via raw arguments to `ds4_mmq_*`.
|
||||
- **`ggml_op` graph evaluation.** We call kernels directly.
|
||||
|
||||
## 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
|
||||
cd /tmp/llama-research && git fetch && git checkout <NEW_COMMIT>
|
||||
cd /Users/ent/code/ds4-mmq-lift/cuda/mmq
|
||||
cp /tmp/llama-research/ggml/src/ggml-cuda/{mmq.cuh,mma.cuh,vecdotq.cuh,quantize.cuh,quantize.cu,mmid.cuh,mmid.cu,common.cuh} .
|
||||
cp /tmp/llama-research/ggml/src/ggml-common.h .
|
||||
cp /tmp/llama-research/ggml/src/ggml-cuda/vendors/cuda.h vendors/
|
||||
# Verify the shim still covers all referenced symbols:
|
||||
grep -hoE 'GGML_[A-Z_]+|ggml_[a-z_]+' *.cuh *.cu *.h | sort -u > /tmp/symbols.new
|
||||
diff /tmp/symbols.last /tmp/symbols.new # check for newly-introduced names
|
||||
# Update this VENDOR.md's commit pin and run `make cuda CUDA_ARCH=sm_120`.
|
||||
```
|
||||
|
||||
If the new symbols are minor (e.g., new `GGML_CUDA_CC_*` constants), they
|
||||
likely come from `common.cuh` which we vendor and don't need any shim
|
||||
changes. If they're new `ggml_*` host functions (rare, but possible if
|
||||
upstream adds a new helper), extend `ds4_ggml_stubs.h`.
|
||||
|
||||
## Testing matrix
|
||||
|
||||
| Test | Status | Phase |
|
||||
|-----------------------------------------------|------------------------------|-----------|
|
||||
| `nvcc -c cuda/mmq/ds4_mmq.cu` builds cleanly | **passes** (sm_120, nvcc 13) | Phase 0 |
|
||||
| Q8_0 dense parity vs CPU reference | **passes** (4 shapes) | Phase 2 |
|
||||
| Q2_K dense parity | **passes** (4 shapes) | Phase 3 |
|
||||
| IQ2_XXS dense parity | **passes** (4 shapes) | Phase 3 |
|
||||
| MoE `_id` Q8_0 parity | **passes** (3 shapes) | Phase 4 |
|
||||
| MoE `_id` Q2_K parity | **passes** (3 shapes) | Phase 4 |
|
||||
| MoE `_id` IQ2_XXS parity | **passes** (4 shapes) | Phase 4 |
|
||||
| `make ds4-bench` with mmq integration | **builds and runs** | Phase 5/6 |
|
||||
| Frontier sweep, ctx 2k-16k, V4 Flash IQ2XXS | **see results below** | Phase 7 |
|
||||
|
||||
## Validated performance
|
||||
|
||||
PRO 6000 Blackwell (sm_120), CUDA 13.0, V4 Flash IQ2_XXS GGUF (86.7 GB),
|
||||
default `DS4_CUDA_MMQ_MOE_MIN_TOKENS=2` (legacy decode preserved):
|
||||
|
||||
| ctx | baseline pf t/s | mmq pf t/s | speedup | baseline gen t/s | mmq gen t/s | gen ratio |
|
||||
|--------|-----------------|------------|------------|------------------|-------------|-----------|
|
||||
| 2048 | 373.21 | 1033.42 | **2.77x** | 40.48 | 39.33 | 0.972x |
|
||||
| 4096 | 366.25 | 1041.39 | **2.84x** | 39.64 | 38.64 | 0.975x |
|
||||
| 6144 | 364.81 | 1025.24 | **2.81x** | 39.50 | 38.55 | 0.976x |
|
||||
| 8192 | 364.01 | 1026.71 | **2.82x** | 38.81 | 37.88 | 0.976x |
|
||||
| 10240 | 361.75 | 1019.53 | **2.82x** | 38.45 | 37.57 | 0.977x |
|
||||
| 12288 | 360.52 | 1013.17 | **2.81x** | 38.31 | 37.22 | 0.972x |
|
||||
| 14336 | 359.15 | 1004.45 | **2.80x** | 38.09 | 37.18 | 0.976x |
|
||||
| 16384 | 357.99 | 1001.29 | **2.80x** | 38.71 | 37.86 | 0.978x |
|
||||
|
||||
Sustained ~2.80x prefill speedup across the swept context range; gen
|
||||
within 2.5% of baseline (run-to-run variance). See `local/docs/`
|
||||
(auto-round companion repo) for the full Phase 0-7 execution log,
|
||||
parity-test output, and detailed plan.
|
||||
+1489
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,238 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Implementations of the ggml-API stubs declared in ds4_ggml_stubs.h plus
|
||||
// the bodies of ggml_backend_cuda_context / ggml_cuda_info /
|
||||
// new_pool_for_device that the vendored common.cuh declares without
|
||||
// defining.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Common.cuh declares `const ggml_cuda_device_info & ggml_cuda_info();` -
|
||||
// we provide the body. The struct layout (common.cuh:1091) is:
|
||||
// { int device_count; cuda_device_info devices[GGML_CUDA_MAX_DEVICES];
|
||||
// std::array<float, GGML_CUDA_MAX_DEVICES> default_tensor_split; }
|
||||
// where cuda_device_info has { cc, nsm, smpb, smpbo, integrated, vmm,
|
||||
// vmm_granularity, total_vram, warp_size, supports_cooperative_launch }.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
const ggml_cuda_device_info & ggml_cuda_info() {
|
||||
static ggml_cuda_device_info info;
|
||||
static std::once_flag once;
|
||||
std::call_once(once, []{
|
||||
int count = 0;
|
||||
cudaError_t err = cudaGetDeviceCount(&count);
|
||||
if (err != cudaSuccess) {
|
||||
fprintf(stderr, "ggml_cuda_info: cudaGetDeviceCount failed: %s\n", cudaGetErrorString(err));
|
||||
count = 0;
|
||||
}
|
||||
if (count > GGML_CUDA_MAX_DEVICES) count = GGML_CUDA_MAX_DEVICES;
|
||||
info.device_count = count;
|
||||
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;
|
||||
info.devices[i].total_vram = p.totalGlobalMem;
|
||||
info.devices[i].warp_size = p.warpSize;
|
||||
info.devices[i].supports_cooperative_launch = p.cooperativeLaunch != 0;
|
||||
}
|
||||
});
|
||||
return info;
|
||||
}
|
||||
|
||||
// ggml_cuda_get_device / ggml_cuda_set_device are declared (not defined) in
|
||||
// common.cuh. We provide thin wrappers.
|
||||
|
||||
int ggml_cuda_get_device() {
|
||||
int dev = 0;
|
||||
(void)cudaGetDevice(&dev);
|
||||
return dev;
|
||||
}
|
||||
|
||||
void ggml_cuda_set_device(int device) {
|
||||
int cur = -1;
|
||||
(void)cudaGetDevice(&cur);
|
||||
if (cur != device) {
|
||||
CUDA_CHECK(cudaSetDevice(device));
|
||||
}
|
||||
}
|
||||
|
||||
int64_t ggml_time_us() {
|
||||
using clock = std::chrono::steady_clock;
|
||||
static const auto t0 = clock::now();
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(clock::now() - t0).count();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// ggml_cuda_error: invoked by the CUDA_CHECK / CUBLAS_CHECK macros defined
|
||||
// in common.cuh on the error path. Marked [[noreturn]] in the declaration
|
||||
// (common.cuh:155) - abort() satisfies that contract.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
[[noreturn]] void ggml_cuda_error(const char * stmt, const char * func, const char * file, int line, const char * msg) {
|
||||
fprintf(stderr, "CUDA error: %s\n call: %s\n in: %s at %s:%d\n", msg, stmt, func, file, line);
|
||||
fflush(stderr);
|
||||
abort();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Concrete device scratch pool.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
|
||||
/* Thread-local stream that ds4_naive_pool uses for cudaMallocAsync /
|
||||
* cudaFreeAsync. Defaults to cudaStreamPerThread (preserves prior
|
||||
* behaviour). Step 8 / CUDA Graphs sets this to the capture stream
|
||||
* before allocating, so the alloc node lives on the captured stream
|
||||
* and capture is not invalidated. Set via ds4_pool_set_stream() from
|
||||
* ds4_mmq.cu wrappers; an explicit stream=0 means the legacy default
|
||||
* stream so pool ops stay ordered with legacy-stream kernels. */
|
||||
static thread_local cudaStream_t t_ds4_pool_stream = cudaStreamPerThread;
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
extern "C" void ds4_pool_set_stream(cudaStream_t stream) {
|
||||
t_ds4_pool_stream = stream;
|
||||
}
|
||||
|
||||
extern "C" cudaStream_t ds4_pool_get_stream(void) {
|
||||
return t_ds4_pool_stream;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct ds4_naive_pool : public ggml_cuda_pool {
|
||||
int device;
|
||||
|
||||
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 {
|
||||
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
|
||||
|
||||
std::unique_ptr<ggml_cuda_pool> ggml_backend_cuda_context::new_pool_for_device(int device, int /*stream_no*/) {
|
||||
return std::unique_ptr<ggml_cuda_pool>(new ds4_naive_pool(device));
|
||||
}
|
||||
|
||||
ggml_backend_cuda_context::~ggml_backend_cuda_context() {
|
||||
if (copy_event) {
|
||||
(void)cudaEventDestroy(copy_event);
|
||||
copy_event = nullptr;
|
||||
}
|
||||
// streams[][], cublas_handles[], and pools[][] are owned-by-value
|
||||
// (cudaStream_t and cublasHandle_t are opaque handles - destroying the
|
||||
// context "should" tear them down, but in our shim ds4 manages streams
|
||||
// externally and we leave them alone. The pools auto-destruct via
|
||||
// unique_ptr.).
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// ds4_ggml_stubs.h - minimal ggml-API stubs for ds4's vendored mmq kernels.
|
||||
//
|
||||
// The mmq.cuh / mma.cuh / vecdotq.cuh / quantize.cuh / mmid.cuh / common.cuh
|
||||
// files in this directory are vendored verbatim from llama.cpp's ggml-cuda
|
||||
// backend (MIT, copyright 2023-2026 The ggml authors). They transitively
|
||||
// #include "ggml.h", "ggml-impl.h", "ggml-cuda.h" - in ds4 those names
|
||||
// resolve to thin redirect headers in this directory which all #include this
|
||||
// stubs file.
|
||||
//
|
||||
// This file declares the minimum surface of the ggml API that the vendored
|
||||
// CUDA code references, EXCLUDING what's already provided by common.cuh
|
||||
// itself (compute-capability constants, MMA flags, ggml_cuda_device_info,
|
||||
// ggml_cuda_pool, ggml_cuda_pool_alloc, ggml_backend_cuda_context, the
|
||||
// CUDA_CHECK / CUBLAS_CHECK macros, ggml_cuda_get_device, ggml_cuda_set_device,
|
||||
// ggml_cuda_info). Those names live in common.cuh and we let it own them.
|
||||
//
|
||||
// Things this header DOES provide:
|
||||
// * GGML_ASSERT / GGML_ABORT / GGML_UNUSED / GGML_UNUSED_VARS / GGML_PAD
|
||||
// * GGML_MAX_DIMS / GGML_MAX_SRC / GGML_CUDA_NAME / GGML_CUDA_MAX_DEVICES /
|
||||
// GGML_CUDA_MAX_STREAMS / GGML_LOG_DEBUG
|
||||
// * enum ggml_type (all 21 mmq type codes - we only USE a subset for V4
|
||||
// Flash but the switch in mmq.cu's downstream replacement must compile)
|
||||
// * enum ggml_glu_op (just for the unused mm_fusion_args fields)
|
||||
// * struct ggml_tensor (complete enough for common.cuh's
|
||||
// ggml_cuda_concurrent_event::is_valid() to compile - we never call it)
|
||||
// * int64_t ggml_nbytes(const ggml_tensor *) (stub - never called)
|
||||
// * int64_t ggml_time_us() (used by USE_CUDA_GRAPH paths we disable)
|
||||
// * inline ggml_type_size() / ggml_blck_size() lookups
|
||||
//
|
||||
// Things ggml-common.h (vendored) owns:
|
||||
// * ggml_half / ggml_half2 typedefs
|
||||
// * GGML_EXTENSION macro
|
||||
// * block_q*, block_iq* struct definitions
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Macros
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#ifndef GGML_ASSERT
|
||||
#define GGML_ASSERT(cond) \
|
||||
do { if (!(cond)) { \
|
||||
fprintf(stderr, "GGML_ASSERT(%s) failed at %s:%d\n", #cond, __FILE__, __LINE__); \
|
||||
abort(); \
|
||||
} } while (0)
|
||||
#endif
|
||||
|
||||
#ifndef GGML_ABORT
|
||||
#define GGML_ABORT(fmt, ...) \
|
||||
do { \
|
||||
fprintf(stderr, "GGML_ABORT: " fmt " at %s:%d\n", ##__VA_ARGS__, __FILE__, __LINE__); \
|
||||
abort(); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#ifndef GGML_UNUSED
|
||||
#define GGML_UNUSED(x) ((void)(x))
|
||||
#endif
|
||||
|
||||
// Variadic GGML_UNUSED_VARS: drop up to 12 unused names without warnings.
|
||||
#ifndef GGML_UNUSED_VARS
|
||||
#define GGML_UNUSED_VARS_1(_1) GGML_UNUSED(_1)
|
||||
#define GGML_UNUSED_VARS_2(_1,_2) GGML_UNUSED(_1); GGML_UNUSED(_2)
|
||||
#define GGML_UNUSED_VARS_3(_1,_2,_3) GGML_UNUSED_VARS_2(_1,_2); GGML_UNUSED(_3)
|
||||
#define GGML_UNUSED_VARS_4(_1,_2,_3,_4) GGML_UNUSED_VARS_3(_1,_2,_3); GGML_UNUSED(_4)
|
||||
#define GGML_UNUSED_VARS_5(_1,_2,_3,_4,_5) GGML_UNUSED_VARS_4(_1,_2,_3,_4); GGML_UNUSED(_5)
|
||||
#define GGML_UNUSED_VARS_6(_1,_2,_3,_4,_5,_6) GGML_UNUSED_VARS_5(_1,_2,_3,_4,_5); GGML_UNUSED(_6)
|
||||
#define GGML_UNUSED_VARS_7(_1,_2,_3,_4,_5,_6,_7) GGML_UNUSED_VARS_6(_1,_2,_3,_4,_5,_6); GGML_UNUSED(_7)
|
||||
#define GGML_UNUSED_VARS_8(_1,_2,_3,_4,_5,_6,_7,_8) GGML_UNUSED_VARS_7(_1,_2,_3,_4,_5,_6,_7); GGML_UNUSED(_8)
|
||||
#define GGML_UNUSED_VARS_9(_1,_2,_3,_4,_5,_6,_7,_8,_9) GGML_UNUSED_VARS_8(_1,_2,_3,_4,_5,_6,_7,_8); GGML_UNUSED(_9)
|
||||
#define GGML_UNUSED_VARS_10(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10) GGML_UNUSED_VARS_9(_1,_2,_3,_4,_5,_6,_7,_8,_9); GGML_UNUSED(_10)
|
||||
#define GGML_UNUSED_VARS_11(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11) GGML_UNUSED_VARS_10(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10); GGML_UNUSED(_11)
|
||||
#define GGML_UNUSED_VARS_12(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12) GGML_UNUSED_VARS_11(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11); GGML_UNUSED(_12)
|
||||
#define GGML_UNUSED_VARS_PICK(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,NAME,...) NAME
|
||||
#define GGML_UNUSED_VARS(...) \
|
||||
GGML_UNUSED_VARS_PICK(__VA_ARGS__, \
|
||||
GGML_UNUSED_VARS_12, GGML_UNUSED_VARS_11, GGML_UNUSED_VARS_10, \
|
||||
GGML_UNUSED_VARS_9, GGML_UNUSED_VARS_8, GGML_UNUSED_VARS_7, \
|
||||
GGML_UNUSED_VARS_6, GGML_UNUSED_VARS_5, GGML_UNUSED_VARS_4, \
|
||||
GGML_UNUSED_VARS_3, GGML_UNUSED_VARS_2, GGML_UNUSED_VARS_1)(__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#ifndef GGML_PAD
|
||||
#define GGML_PAD(x, n) (((x) + (n) - 1) / (n) * (n))
|
||||
#endif
|
||||
|
||||
#ifndef GGML_MAX_DIMS
|
||||
#define GGML_MAX_DIMS 4
|
||||
#endif
|
||||
|
||||
#ifndef GGML_MAX_SRC
|
||||
#define GGML_MAX_SRC 10
|
||||
#endif
|
||||
|
||||
#ifndef GGML_CUDA_NAME
|
||||
#define GGML_CUDA_NAME "DS4_CUDA"
|
||||
#endif
|
||||
|
||||
#ifndef GGML_CUDA_MAX_DEVICES
|
||||
#define GGML_CUDA_MAX_DEVICES 16
|
||||
#endif
|
||||
|
||||
#ifndef GGML_CUDA_MAX_STREAMS
|
||||
#define GGML_CUDA_MAX_STREAMS 8
|
||||
#endif
|
||||
|
||||
#ifndef GGML_LOG_DEBUG
|
||||
#define GGML_LOG_DEBUG(...) ((void)0)
|
||||
#endif
|
||||
|
||||
// Cuda-graphs are explicitly disabled - ds4 manages its own streams.
|
||||
#undef GGML_CUDA_USE_GRAPHS
|
||||
#undef GGML_HIP_GRAPHS
|
||||
#undef GGML_MUSA_GRAPHS
|
||||
|
||||
// GGML_EXTENSION: ggml-common.h provides the canonical definition. We leave
|
||||
// it undefined here so the vendored header's `#define GGML_EXTENSION
|
||||
// __extension__` wins.
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Quantization type enum.
|
||||
//
|
||||
// Order matches llama.cpp's enum ggml_type. Values are pinned because the
|
||||
// mmq switch uses them as case labels.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
enum ggml_type {
|
||||
GGML_TYPE_F32 = 0,
|
||||
GGML_TYPE_F16 = 1,
|
||||
GGML_TYPE_Q4_0 = 2,
|
||||
GGML_TYPE_Q4_1 = 3,
|
||||
// GGML_TYPE_Q4_2 / Q4_3 deprecated
|
||||
GGML_TYPE_Q5_0 = 6,
|
||||
GGML_TYPE_Q5_1 = 7,
|
||||
GGML_TYPE_Q8_0 = 8,
|
||||
GGML_TYPE_Q8_1 = 9,
|
||||
GGML_TYPE_Q2_K = 10,
|
||||
GGML_TYPE_Q3_K = 11,
|
||||
GGML_TYPE_Q4_K = 12,
|
||||
GGML_TYPE_Q5_K = 13,
|
||||
GGML_TYPE_Q6_K = 14,
|
||||
GGML_TYPE_Q8_K = 15,
|
||||
GGML_TYPE_IQ2_XXS = 16,
|
||||
GGML_TYPE_IQ2_XS = 17,
|
||||
GGML_TYPE_IQ3_XXS = 18,
|
||||
GGML_TYPE_IQ1_S = 19,
|
||||
GGML_TYPE_IQ4_NL = 20,
|
||||
GGML_TYPE_IQ3_S = 21,
|
||||
GGML_TYPE_IQ2_S = 22,
|
||||
GGML_TYPE_IQ4_XS = 23,
|
||||
GGML_TYPE_I8 = 24,
|
||||
GGML_TYPE_I16 = 25,
|
||||
GGML_TYPE_I32 = 26,
|
||||
GGML_TYPE_I64 = 27,
|
||||
GGML_TYPE_F64 = 28,
|
||||
GGML_TYPE_IQ1_M = 29,
|
||||
GGML_TYPE_BF16 = 30,
|
||||
GGML_TYPE_MXFP4 = 39,
|
||||
GGML_TYPE_NVFP4 = 40,
|
||||
GGML_TYPE_Q1_0 = 41,
|
||||
GGML_TYPE_COUNT = 42,
|
||||
};
|
||||
|
||||
enum ggml_glu_op {
|
||||
GGML_GLU_OP_REGLU,
|
||||
GGML_GLU_OP_GEGLU,
|
||||
GGML_GLU_OP_SWIGLU,
|
||||
GGML_GLU_OP_SWIGLU_OAI, // referenced by mmvq.cu's fused-GLU epilogue
|
||||
GGML_GLU_OP_COUNT,
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// ggml_tensor: complete enough for common.cuh's
|
||||
// ggml_cuda_concurrent_event::is_valid() to compile cleanly. We NEVER
|
||||
// instantiate or dereference one of these - the concurrent path is
|
||||
// disabled.
|
||||
//
|
||||
// Field set matches the upstream order/types so cudaGraph node_properties
|
||||
// (which holds a `ggml_tensor node` by value inside `#ifdef USE_CUDA_GRAPH`)
|
||||
// also compiles. Sizes are conservative for storage; ds4 never copies into
|
||||
// these.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
struct ggml_tensor {
|
||||
enum ggml_type type;
|
||||
int32_t op; // enum ggml_op (opaque to us)
|
||||
int32_t flags;
|
||||
int64_t ne[GGML_MAX_DIMS]; // shape
|
||||
size_t nb[GGML_MAX_DIMS]; // stride bytes
|
||||
int32_t op_params[16]; // GGML_MAX_OP_PARAMS / sizeof(int32_t)
|
||||
struct ggml_tensor * src[GGML_MAX_SRC];
|
||||
struct ggml_tensor * view_src;
|
||||
size_t view_offs;
|
||||
void * data;
|
||||
char name[64]; // GGML_MAX_NAME
|
||||
void * extra;
|
||||
char padding[8];
|
||||
};
|
||||
|
||||
// ggml_nbytes: byte size of tensor data. We never call this; provide a
|
||||
// stub so common.cuh's is_valid() compiles. If anything does call it the
|
||||
// returned 0 will surface as an immediate logic error.
|
||||
static inline int64_t ggml_nbytes(const struct ggml_tensor * /*t*/) { return 0; }
|
||||
|
||||
// Microsecond timer (used only inside USE_CUDA_GRAPH paths we disable).
|
||||
int64_t ggml_time_us();
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Inline size traits.
|
||||
//
|
||||
// Lookup tables hand-aligned with llama.cpp's ggml_type_traits in
|
||||
// ggml/src/ggml.c. Only types referenced by the vendored switch are needed.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
inline size_t ggml_type_size(enum ggml_type t) {
|
||||
switch (t) {
|
||||
case GGML_TYPE_F32: return 4;
|
||||
case GGML_TYPE_F16: return 2;
|
||||
case GGML_TYPE_BF16: return 2;
|
||||
case GGML_TYPE_I8: return 1;
|
||||
case GGML_TYPE_I16: return 2;
|
||||
case GGML_TYPE_I32: return 4;
|
||||
case GGML_TYPE_I64: return 8;
|
||||
case GGML_TYPE_F64: return 8;
|
||||
case GGML_TYPE_Q4_0: return 18;
|
||||
case GGML_TYPE_Q4_1: return 20;
|
||||
case GGML_TYPE_Q5_0: return 22;
|
||||
case GGML_TYPE_Q5_1: return 24;
|
||||
case GGML_TYPE_Q8_0: return 34;
|
||||
case GGML_TYPE_Q8_1: return 36;
|
||||
case GGML_TYPE_Q2_K: return 84;
|
||||
case GGML_TYPE_Q3_K: return 110;
|
||||
case GGML_TYPE_Q4_K: return 144;
|
||||
case GGML_TYPE_Q5_K: return 176;
|
||||
case GGML_TYPE_Q6_K: return 210;
|
||||
case GGML_TYPE_Q8_K: return 292;
|
||||
case GGML_TYPE_IQ2_XXS: return 66;
|
||||
case GGML_TYPE_IQ2_XS: return 74;
|
||||
case GGML_TYPE_IQ2_S: return 82;
|
||||
case GGML_TYPE_IQ3_XXS: return 98;
|
||||
case GGML_TYPE_IQ3_S: return 110;
|
||||
case GGML_TYPE_IQ1_S: return 50;
|
||||
case GGML_TYPE_IQ1_M: return 56;
|
||||
case GGML_TYPE_IQ4_NL: return 18;
|
||||
case GGML_TYPE_IQ4_XS: return 136;
|
||||
case GGML_TYPE_MXFP4: return 17;
|
||||
case GGML_TYPE_NVFP4: return 18;
|
||||
case GGML_TYPE_Q1_0: return 36;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline int64_t ggml_blck_size(enum ggml_type t) {
|
||||
switch (t) {
|
||||
case GGML_TYPE_F32:
|
||||
case GGML_TYPE_F16:
|
||||
case GGML_TYPE_BF16:
|
||||
case GGML_TYPE_I8:
|
||||
case GGML_TYPE_I16:
|
||||
case GGML_TYPE_I32:
|
||||
case GGML_TYPE_I64:
|
||||
case GGML_TYPE_F64:
|
||||
return 1;
|
||||
case GGML_TYPE_Q4_0:
|
||||
case GGML_TYPE_Q4_1:
|
||||
case GGML_TYPE_Q5_0:
|
||||
case GGML_TYPE_Q5_1:
|
||||
case GGML_TYPE_Q8_0:
|
||||
case GGML_TYPE_Q8_1:
|
||||
case GGML_TYPE_IQ4_NL:
|
||||
case GGML_TYPE_MXFP4:
|
||||
return 32;
|
||||
case GGML_TYPE_NVFP4:
|
||||
return 64;
|
||||
case GGML_TYPE_Q2_K:
|
||||
case GGML_TYPE_Q3_K:
|
||||
case GGML_TYPE_Q4_K:
|
||||
case GGML_TYPE_Q5_K:
|
||||
case GGML_TYPE_Q6_K:
|
||||
case GGML_TYPE_Q8_K:
|
||||
case GGML_TYPE_IQ2_XXS:
|
||||
case GGML_TYPE_IQ2_XS:
|
||||
case GGML_TYPE_IQ2_S:
|
||||
case GGML_TYPE_IQ3_XXS:
|
||||
case GGML_TYPE_IQ3_S:
|
||||
case GGML_TYPE_IQ1_S:
|
||||
case GGML_TYPE_IQ1_M:
|
||||
case GGML_TYPE_IQ4_XS:
|
||||
case GGML_TYPE_Q1_0:
|
||||
return 256;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
+5706
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,897 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// ds4_mmq.h - public C ABI for ds4's quantized matmul kernels.
|
||||
//
|
||||
// All functions are extern "C" so ds4.c / ds4_cuda.cu can call them
|
||||
// without C++ compilation. Functions return 0 on success and non-zero on
|
||||
// failure (with stderr error message). Device pointers are caller-owned.
|
||||
//
|
||||
// Phase 0: skeleton only. Q8_0 dense entry compiles and instantiates
|
||||
// mul_mat_q_case<Q8_0> but is not yet wired into ds4_cuda.cu.
|
||||
// Phase 1: Q8_1 activation quantizer wrapper added.
|
||||
// Phase 2: Q8_0 dense entry verified against cublas+dequant baseline.
|
||||
// Phase 3: Q2_K + IQ2_XXS dense entries.
|
||||
// Phase 4: MoE _id variants of all three.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(GGML_USE_HIP)
|
||||
#include "vendors/hip.h"
|
||||
#else
|
||||
#include <cuda_runtime.h>
|
||||
#endif
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// One-time init. Sets the current CUDA device and triggers lazy population
|
||||
// of the device-info singleton. Safe to call repeatedly.
|
||||
//
|
||||
// device: CUDA device ordinal (0 for the primary GPU).
|
||||
// Returns 0 on success.
|
||||
int ds4_mmq_init(int device);
|
||||
void ds4_mmq_set_aligned_q81_scratch(void *ptr, size_t bytes);
|
||||
|
||||
// Query whether ds4_mmq is willing to handle a given matmul. Returns
|
||||
// 1 if mmq is faster than dequant+cublas for this shape on this device,
|
||||
// 0 otherwise (caller should fall back to its existing dequant+cublas path).
|
||||
//
|
||||
// Wraps ggml_cuda_should_use_mmq. type_x uses ds4 quant codes which match
|
||||
// ggml's enum:
|
||||
// 8 = Q8_0
|
||||
// 10 = Q2_K
|
||||
// 16 = IQ2_XXS
|
||||
//
|
||||
// ne11: batch dimension (number of activation columns / tokens).
|
||||
// n_experts: 0 for dense matmul, >0 for MoE (e.g. 256 for V4 Flash).
|
||||
int ds4_mmq_should_use(int type_x, int64_t ne11, int64_t n_experts);
|
||||
|
||||
// Dense matmul entry points. Per-type wrappers that all share the same
|
||||
// underlying mul_mat_q template, parameterised by the weight quant type.
|
||||
//
|
||||
// All three variants compute:
|
||||
//
|
||||
// out[col, row] = sum_k W[row, k] * X[k, col] 0 <= row < M, 0 <= col < N
|
||||
//
|
||||
// Layouts (matching ggml + llama.cpp mmq conventions, all on device):
|
||||
// W: [M rows, K cols], row-major, packed in the type-specific block
|
||||
// format. K must be a multiple of 256.
|
||||
// X_f32: [N rows, K cols] F32 row-major (logical [K, N] with K
|
||||
// innermost - i.e. for each "column" col of the logical [K, N]
|
||||
// matrix, K contiguous floats live at X[col*K .. col*K + K]).
|
||||
// out_f32: caller-allocated, M*N floats. mmq writes in column-major:
|
||||
// out[col*M + row]. Callers expecting row-major must transpose.
|
||||
//
|
||||
// Returns 0 on success, non-zero on validation or launch failure.
|
||||
|
||||
int ds4_mmq_q8_0_dense(
|
||||
const void * W_q8_0,
|
||||
const float * X_f32,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int N,
|
||||
int K,
|
||||
cudaStream_t stream);
|
||||
|
||||
// v0.5 flat-pool p5a: same contract as ds4_mmq_q8_0_dense but the
|
||||
// activation arrives PRE-QUANTIZED in the block_q8_1_mmq D4 layout
|
||||
// (producer-emitted, op-for-op equal to quantize_mmq_q8_1<D4>; the fused
|
||||
// own out_a kernel is the producer). Y_q8_mmq must hold N*(K/128)
|
||||
// 144-byte blocks (ib = kseg*N + row) plus mmq_x_max tail slack, which
|
||||
// this entry zeroes. Requires K such that GGML_PAD(K, MATRIX_ROW_PADDING)
|
||||
// == K. Returns 0 on success; callers fall back to the quantizing entry
|
||||
// on any non-zero.
|
||||
int ds4_mmq_q8_0_dense_preq(
|
||||
const void * W_q8_0,
|
||||
const void * Y_q8_mmq,
|
||||
size_t y_bytes,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int N,
|
||||
int K,
|
||||
cudaStream_t stream);
|
||||
|
||||
// p5a verify instrument: reference quantize (dense_impl parameters) into a
|
||||
// caller buffer, for byte-diffing producer emits.
|
||||
int ds4_mmq_q8_0_quantize_ref(
|
||||
const float * X,
|
||||
void * y,
|
||||
size_t y_bytes,
|
||||
int N,
|
||||
int K,
|
||||
cudaStream_t stream);
|
||||
|
||||
// Dense Q8_0 D2R on the kind-5 aligned artifact (weight server
|
||||
// --repack-q8-aligned). Same in/out contract as ds4_mmq_q8_0_dense but W is
|
||||
// the ALIGNED artifact base ([half dq[nblk]][pad64][int8 qs]), and the shape
|
||||
// must satisfy M % 128 == 0 && K % 1024 == 0. Callers gate on n_tok scale
|
||||
// and K <= 4096 (o_proj's K=8192 measured faster on mmq).
|
||||
int ds4_mmq_q8_0_dense_d2r(
|
||||
const void * W_aligned,
|
||||
const float * X_f32,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int N,
|
||||
int K,
|
||||
cudaStream_t stream);
|
||||
|
||||
// flat-pool p5c: D2R dense over a producer-quantized token-major Y
|
||||
// (block_q8_1_mmq D4, ib = kseg*N + row, requires GGML_PAD(K) == K).
|
||||
// y_bytes must cover the payload plus max(mmq_x_max, 128) slack blocks;
|
||||
// the slack is zeroed here each call (S1.1a). Returns 0 on success;
|
||||
// callers fall back to the quantizing entry on any non-zero.
|
||||
int ds4_mmq_q8_0_dense_d2r_preq(
|
||||
const void * W_aligned,
|
||||
const void * Y_q8_mmq,
|
||||
size_t y_bytes,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int N,
|
||||
int K,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_q2_K_dense(
|
||||
const void * W_q2_K,
|
||||
const float * X_f32,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int N,
|
||||
int K,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_iq2_xxs_dense(
|
||||
const void * W_iq2_xxs,
|
||||
const float * X_f32,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int N,
|
||||
int K,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_q4_K_dense(
|
||||
const void * W_q4_K,
|
||||
const float * X_f32,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int N,
|
||||
int K,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_mxfp4_dense(
|
||||
const void * W_mxfp4,
|
||||
const float * X_f32,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int N,
|
||||
int K,
|
||||
cudaStream_t stream);
|
||||
|
||||
// MoE matmul entry points. For each (token, slot-within-token's-top-k) pair
|
||||
// the kernel computes:
|
||||
//
|
||||
// out[col, row] = sum_k W[ids[token, slot], row, k] * X[token, k]
|
||||
//
|
||||
// where col = token * n_expert_used + slot, row in [0, M). The caller is
|
||||
// responsible for any downstream sum-weighted-by-router-weights reduction
|
||||
// across the n_expert_used dimension (Phase 5 wires this into ds4's
|
||||
// existing moe_sum_kernel).
|
||||
//
|
||||
// Layouts:
|
||||
// W: device pointer, [n_experts, M rows, K cols] in the
|
||||
// type-specific block format. Per-expert slab is M*K/blck
|
||||
// blocks stored contiguously; experts are stacked.
|
||||
// X_f32: device pointer, [n_tokens, K] F32 row-major (K innermost).
|
||||
// ids: device pointer, [n_tokens, n_expert_used] int32_t row-major.
|
||||
// ids[t*n_expert_used + s] is the expert id for token t's
|
||||
// s-th routing slot. Values must be in [0, n_experts).
|
||||
// out_f32: caller-allocated, M * n_tokens * n_expert_used floats.
|
||||
// Column-major: out[col*M + row].
|
||||
//
|
||||
// K must be a multiple of 256. n_expert_used must be one of the values
|
||||
// the vendored mm_ids_helper template specialises on: 2, 4, 6, 8, 16, 32
|
||||
// (or any other value, which falls back to the generic path). For V4
|
||||
// Flash, n_expert_used = 6.
|
||||
//
|
||||
// Returns 0 on success, non-zero on validation or launch failure.
|
||||
|
||||
int ds4_mmq_q8_0_moe(
|
||||
const void * W,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_q2_K_moe(
|
||||
const void * W,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_iq2_xxs_moe(
|
||||
const void * W,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
// ds4 (P4 Inc3): same contract as ds4_mmq_q2_K_moe but W_soa is the aligned
|
||||
// row-pair-SoA artifact (weight server --repack-q2k-aligned, layout in
|
||||
// ds4_mmq_q2_k_aligned_bytes' comment) instead of the raw block stream. The
|
||||
// tile loader reads the SoA sections directly -- bit-identical output to the
|
||||
// raw path, no derepack scratch.
|
||||
// CONTRACT DIFFERENCE (P3): unlike the raw entries, the output is NOT
|
||||
// nonfinite-sanitized; the routed-MoE consumers (moe_mmq_swiglu / moe_sum
|
||||
// with guard_nonfinite=1) sanitize at read, so the standalone whole-buffer
|
||||
// pass is skipped. New callers must sanitize at consumption.
|
||||
int ds4_mmq_q2_K_moe_soa(
|
||||
const void * W_soa,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_q4_K_moe(
|
||||
const void * W,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_mxfp4_moe(
|
||||
const void * W,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
// Paired MoE entries. Compute gate AND up over the same activation in a
|
||||
// single call so the Q8_1 quantize of X (and the mm_ids_helper bookkeeping)
|
||||
// happens once instead of twice. Both weights must be the same quant type
|
||||
// and the same shape (M, K, n_experts); out_a / out_b have the same layout
|
||||
// as a single ds4_mmq_<type>_moe call. Saves one launch of
|
||||
// quantize_mmq_q8_1_cuda and one ggml_cuda_launch_mm_ids_helper per MoE
|
||||
// block. See ds4_mmq.cu / routed_moe_launch for the wiring.
|
||||
//
|
||||
// Returns 0 on success; on error neither output is guaranteed valid.
|
||||
|
||||
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);
|
||||
|
||||
// ds4 (P4 Inc3): same contract as ds4_mmq_iq2_xxs_moe_pair but over the
|
||||
// aligned-SoA artifacts (weight server --repack-iq2-aligned); see
|
||||
// ds4_mmq_q2_K_moe_soa.
|
||||
int ds4_mmq_iq2_xxs_moe_pair_soa(
|
||||
const void * Wa_soa,
|
||||
const void * Wb_soa,
|
||||
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);
|
||||
|
||||
/* v0.5 inc-9 (F7): fused target-prefill pipeline over the aligned-SoA
|
||||
* IQ2_XXS gate/up and Q2_K down artifacts. Builds the expert-major
|
||||
* 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.
|
||||
* 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,
|
||||
const void * W_down_soa,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
const float * router_weights,
|
||||
float * gate_f32,
|
||||
float * up_f32,
|
||||
float * mid_f32,
|
||||
float * down_f32,
|
||||
int expert_mid_dim,
|
||||
int expert_in_dim,
|
||||
int out_dim,
|
||||
int n_tokens,
|
||||
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
|
||||
* SwiGLU is quantized directly into down_q8_scratch, and only the pair-major
|
||||
* down output is materialized. Caller-owned scratch keeps this hot path free
|
||||
* of stream-ordered allocations; all three ranges must be distinct, remain
|
||||
* live until return, and be sized to their LOGICAL segments (never an owning
|
||||
* arena's capacity). */
|
||||
/* flat-pool p5c: input_q8_ext, when non-NULL, is a producer-emitted
|
||||
* TOKEN-COMPACT block_q8_1_mmq buffer of X (ib = kseg*n_tokens + row,
|
||||
* n_tokens rows) and the internal activation quantize is skipped; the
|
||||
* gate/up D2R kernel reads it through the p5b ids_src indirection.
|
||||
* Ignored (classic gathered quantize) when DS4_MMQ_NO_YIND is set. */
|
||||
int ds4_mmq_iq2_xxs_q2_K_moe_fused_direct_soa(
|
||||
const void * W_gate_soa,
|
||||
const void * W_up_soa,
|
||||
const void * W_down_soa,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
const float * router_weights,
|
||||
void * input_q8_scratch,
|
||||
size_t input_q8_scratch_bytes,
|
||||
void * down_q8_scratch,
|
||||
size_t down_q8_scratch_bytes,
|
||||
void * work_scratch,
|
||||
size_t work_scratch_bytes,
|
||||
const void * input_q8_ext,
|
||||
size_t input_q8_ext_bytes,
|
||||
float * down_f32,
|
||||
int expert_mid_dim,
|
||||
int expert_in_dim,
|
||||
int out_dim,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
float clamp,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_iq2_xxs_q2_K_moe_fused_direct_scratch_sizes(
|
||||
int expert_mid_dim,
|
||||
int expert_in_dim,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
size_t *input_q8_bytes,
|
||||
size_t *down_q8_bytes,
|
||||
size_t *work_bytes);
|
||||
|
||||
int ds4_mmq_q4_K_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);
|
||||
|
||||
int ds4_mmq_mxfp4_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);
|
||||
|
||||
// MoE vector matmul entries (Step 6). Same signature and semantics as the
|
||||
// ds4_mmq_<type>_moe entries above, but route through llama.cpp's mmvq
|
||||
// kernels instead of mmq. mmvq is structurally optimised for small batch
|
||||
// counts (single-token decode, short prefill), where mmq's tile-based
|
||||
// approach wastes work on empty columns.
|
||||
//
|
||||
// Constraints:
|
||||
// - n_tokens * something must fit under mmvq's per-arch batch cap
|
||||
// (MMVQ_MAX_BATCH_SIZE = 8 on Blackwell). Specifically, ncols_dst as
|
||||
// computed by the wrapper must be <= 8. The wrapper rejects with -1
|
||||
// if the request is too large.
|
||||
// - K must be a multiple of 256 (same as the mmq path).
|
||||
//
|
||||
// Unlike the mmq path, mmvq consumes a CANONICAL block_q8_1 buffer (not
|
||||
// the interleaved block_q8_1_mmq the mmq path uses). The wrapper builds
|
||||
// the canonical buffer internally; callers cannot reuse a Q8_1 buffer
|
||||
// previously built for the mmq path.
|
||||
//
|
||||
// Returns 0 on success, non-zero on validation or launch failure.
|
||||
|
||||
int ds4_mmq_q8_0_moe_vec(
|
||||
const void * W,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_q2_K_moe_vec(
|
||||
const void * W,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_iq2_xxs_moe_vec(
|
||||
const void * W,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_q4_K_moe_vec(
|
||||
const void * W,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_mxfp4_moe_vec(
|
||||
const void * W,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
// Aligned-SoA IQ2_XXS decode matvec (megakernel program M1-Inc1).
|
||||
//
|
||||
// block_iq2_xxs is 66 bytes, so the raw expert stream is only 2-byte aligned
|
||||
// and every 32-bit code word costs two 16-bit loads. W_aligned is a repacked
|
||||
// copy of the SAME bytes with the block scales split out and the code stream
|
||||
// 64B-aligned:
|
||||
//
|
||||
// [ __half dq[nblk] ][ pad to 64B ][ uint2 qs[nblk * 8] ]
|
||||
//
|
||||
// where nblk = n_experts * M * (K / 256) and block linear order matches the
|
||||
// raw tensor byte order (expert-major, then row, then block). The weight
|
||||
// server builds this layout (--repack-iq2-aligned); use
|
||||
// ds4_mmq_iq2_xxs_aligned_bytes to size or validate an artifact.
|
||||
//
|
||||
// Semantics and output layout are identical to ds4_mmq_iq2_xxs_moe_vec at
|
||||
// n_tokens == 1 (the only supported width; other widths return non-zero so
|
||||
// the caller can fall back).
|
||||
uint64_t ds4_mmq_iq2_xxs_aligned_bytes(int M, int K, int n_experts);
|
||||
|
||||
// M1-Inc2b: exact inverse of the weight-server repack. Fills raw_out
|
||||
// (nblk * 66 bytes, raw block_iq2_xxs byte stream) from an aligned artifact,
|
||||
// device->device on `stream`. Lets the batched/mmq raw-layout consumers run
|
||||
// from a device scratch instead of the client mmap when the raw spans were
|
||||
// excluded from the upload.
|
||||
int ds4_mmq_iq2_xxs_aligned_derepack(
|
||||
const void * W_aligned,
|
||||
void * raw_out,
|
||||
int M,
|
||||
int K,
|
||||
int n_experts,
|
||||
cudaStream_t stream);
|
||||
|
||||
// M2 moe-down: aligned row-pair-SoA Q2_K routed-expert decode matvec. Twin
|
||||
// of mul_mat_vec_q_moe<GGML_TYPE_Q2_K, 2> at the down-leg call shape
|
||||
// (n_expert_used == 1: each (token, slot) assignment is its own "token") with
|
||||
// bit-identical outputs; only the weight layout changes. W_aligned is the
|
||||
// weight-server --repack-q2k-aligned artifact (DERIVED_Q2_K_ALIGNED_MOE,
|
||||
// REPLACES the raw range; byte-neutral):
|
||||
//
|
||||
// npair = n_experts * (M/2) * (K/256)
|
||||
// [ uint2 dm2[npair] ][ pad 64B ][ int4 sc4[npair*2] ][ pad 64B ]
|
||||
// [ uint2 qs2[npair*16] ]
|
||||
//
|
||||
// keyed to the kernel's rows_per_block == 2 (see ds4_mmq.cu for the exact
|
||||
// field packing). Use ds4_mmq_q2_k_aligned_bytes to size or validate an
|
||||
// artifact. Unsupported shapes return non-zero so the caller can fall back.
|
||||
uint64_t ds4_mmq_q2_k_aligned_bytes(int M, int K, int n_experts);
|
||||
|
||||
int ds4_mmq_q2_K_aligned_moe_vec(
|
||||
const void * W_aligned,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
// Exact inverse of the weight-server repack: fills raw_out (nblk * 84 bytes,
|
||||
// raw block_q2_K stream) from an aligned artifact, device->device on
|
||||
// `stream`, for the batched/mmq raw-layout consumers (same role as
|
||||
// ds4_mmq_iq2_xxs_aligned_derepack).
|
||||
int ds4_mmq_q2_K_aligned_derepack(
|
||||
const void * W_aligned,
|
||||
void * raw_out,
|
||||
int M,
|
||||
int K,
|
||||
int n_experts,
|
||||
cudaStream_t stream);
|
||||
|
||||
// M1-Inc3: aligned-SoA Q8_0 dense decode matvec. Artifact layout
|
||||
// [__half dq[nblk]][pad to 64B][int8 qs[nblk*32]], nblk = M * (K/32), block
|
||||
// order equal to the raw tensor byte order (weight server
|
||||
// --repack-q8-aligned; raw spans stay served — dense artifacts duplicate,
|
||||
// they do not replace). n_tokens == 1 and K % 1024 == 0 only; other shapes
|
||||
// return non-zero so the caller can fall back to ds4_mmq_q8_0_dense_vec.
|
||||
uint64_t ds4_mmq_q8_0_aligned_bytes(int M, int K);
|
||||
|
||||
int ds4_mmq_q8_0_aligned_dense_vec(
|
||||
const void * W_aligned,
|
||||
const float * X_f32,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int N,
|
||||
int K,
|
||||
cudaStream_t stream);
|
||||
|
||||
// Decode-only pair: quantize the shared activation once and compute both
|
||||
// aligned projections in one row-concatenated launch.
|
||||
int ds4_mmq_q8_0_aligned_dense_vec_pair(
|
||||
const void * W0_aligned,
|
||||
const void * W1_aligned,
|
||||
const float * X_f32,
|
||||
float * out0_f32,
|
||||
float * out1_f32,
|
||||
int M0,
|
||||
int M1,
|
||||
int K,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_iq2_xxs_aligned_moe_vec(
|
||||
const void * W_aligned,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
// M1-Inc2 variants over the same aligned artifacts (n_tokens == 1 only).
|
||||
//
|
||||
// _pair_vec: one activation quantize + one launch computes both raw gate and
|
||||
// up outputs (nonfinite zeroed in-kernel; no sanitize pass needed). The
|
||||
// caller still runs its clamp-aware SwiGLU.
|
||||
//
|
||||
// _gate_up_mid_vec: additionally folds the clamp/SwiGLU/router-weight
|
||||
// epilogue (identical semantics to ds4_mmq_moe_gate_up_mid_q8_1_qwarp32) and
|
||||
// writes mid[slot * M + row] directly.
|
||||
int ds4_mmq_iq2_xxs_aligned_moe_pair_vec(
|
||||
const void * W_gate_aligned,
|
||||
const void * W_up_aligned,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
float * gate_out,
|
||||
float * up_out,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_iq2_xxs_aligned_moe_gate_up_mid_vec(
|
||||
const void * W_gate_aligned,
|
||||
const void * W_up_aligned,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
const float * weights,
|
||||
float * mid_f32,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
float clamp,
|
||||
cudaStream_t stream);
|
||||
|
||||
// Fused down+sum vector entries for routed MoE with top_k=6. These preserve
|
||||
// the canonical Q8_1 activation quantization used by the regular _moe_vec
|
||||
// path, but avoid materializing [token, slot, out_dim] down results and avoid
|
||||
// the separate slot-sum kernel. The caller must already have baked router
|
||||
// weights into X_f32, so the helper computes:
|
||||
//
|
||||
// out[token, row] = sum_slot W[ids[token, slot], row, :] @ X[token, slot, :]
|
||||
//
|
||||
// Layouts:
|
||||
// W: [n_experts, M rows, K cols] in Q2_K or Q4_K blocks
|
||||
// X_f32: [n_tokens * 6, K] F32 row-major
|
||||
// ids: [n_tokens, 6] int32 row-major
|
||||
// out_f32: [n_tokens, M] F32 row-major
|
||||
//
|
||||
// Constraints:
|
||||
// - n_expert_used must be 6
|
||||
// - K must be a multiple of 256
|
||||
//
|
||||
// Returns 0 on success, non-zero on validation or launch failure.
|
||||
|
||||
int ds4_mmq_q2_K_moe_down_sum6_vec(
|
||||
const void * W,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_q4_K_moe_down_sum6_vec(
|
||||
const void * W,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_mxfp4_moe_down_sum6_vec(
|
||||
const void * W,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
// Fused gate+up+SwiGLU vector entries for routed MoE with top_k=6. These use
|
||||
// canonical Q8_1 activation quantization and compute weighted mid directly:
|
||||
//
|
||||
// mid[token, slot, row] =
|
||||
// silu(clamp_gate(W_gate[expert,row] @ X[token]))
|
||||
// * clamp_up(W_up[expert,row] @ X[token])
|
||||
// * router_weight[token, slot]
|
||||
//
|
||||
// Clamp semantics match ds4_cuda.cu: gate is capped above, up is clamped to
|
||||
// [-clamp, clamp], and no clamp is applied when clamp <= 1e-6.
|
||||
//
|
||||
// Layouts:
|
||||
// W_gate/W_up: [n_experts, M rows, K cols] in IQ2_XXS or Q4_K blocks
|
||||
// X_f32: [n_tokens, K] F32 row-major
|
||||
// ids: [n_tokens, 6] int32 row-major
|
||||
// weights: [n_tokens, 6] F32 row-major
|
||||
// mid_f32: [n_tokens * 6, M] F32 row-major
|
||||
//
|
||||
// Constraints:
|
||||
// - n_expert_used must be 6
|
||||
// - K must be a multiple of 256
|
||||
//
|
||||
// Returns 0 on success, non-zero on validation or launch failure.
|
||||
|
||||
int ds4_mmq_iq2_xxs_moe_gate_up_mid_vec(
|
||||
const void * W_gate,
|
||||
const void * W_up,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
const float * weights,
|
||||
float * mid_f32,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
float clamp,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_q4_K_moe_gate_up_mid_vec(
|
||||
const void * W_gate,
|
||||
const void * W_up,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
const float * weights,
|
||||
float * mid_f32,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
float clamp,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_mxfp4_moe_gate_up_mid_vec(
|
||||
const void * W_gate,
|
||||
const void * W_up,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
const float * weights,
|
||||
float * mid_f32,
|
||||
int M,
|
||||
int K,
|
||||
int n_tokens,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
float clamp,
|
||||
cudaStream_t stream);
|
||||
|
||||
// Pair-fused MoE vector matmul entries (Step 6). Computes
|
||||
//
|
||||
// out[col, row] = (W_a[ids, row, :] @ X[token, :])
|
||||
// * silu(W_b[ids, row, :] @ X[token, :])
|
||||
//
|
||||
// in a SINGLE mmvq launch via mmvq's built-in fusion (fusion.gate = W_b,
|
||||
// fusion.glu_op = GGML_GLU_OP_SWIGLU). The kernel applies silu to the
|
||||
// fusion.gate matmul and multiplies into the main matmul: pass the
|
||||
// SwiGLU "up" weights as W_a and the SwiGLU "gate" weights as W_b to
|
||||
// match ds4's expected silu(gate)*up semantics. The DeepSeek V4 clamp
|
||||
// and router-weight multiplication are NOT applied by the kernel - the
|
||||
// caller is expected to apply them as a small post-process (or to skip
|
||||
// clamp if clamp==0).
|
||||
//
|
||||
// Constraints:
|
||||
// - n_tokens = 1 ONLY. mmvq supports fusion only at ncols_dst = 1.
|
||||
// - K must be a multiple of 256.
|
||||
//
|
||||
// Returns 0 on success, non-zero on validation or launch failure.
|
||||
|
||||
int ds4_mmq_iq2_xxs_moe_pair_vec(
|
||||
const void * W_a,
|
||||
const void * W_b,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
float * out_silu,
|
||||
int M,
|
||||
int K,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_q4_K_moe_pair_vec(
|
||||
const void * W_a,
|
||||
const void * W_b,
|
||||
const float * X_f32,
|
||||
const int32_t * ids,
|
||||
float * out_silu,
|
||||
int M,
|
||||
int K,
|
||||
int n_experts,
|
||||
int n_expert_used,
|
||||
cudaStream_t stream);
|
||||
|
||||
// Raw pair MoE vector entries. They quantize X to canonical Q8_1 once, then
|
||||
// run the same mmvq matvec kernel twice to produce the unfused gate and up
|
||||
// outputs. This preserves the caller's clamp-aware SwiGLU epilogue while
|
||||
// avoiding the duplicate Q8_1 quantize/scratch setup of two separate
|
||||
// ds4_mmq_*_moe_vec calls.
|
||||
//
|
||||
// Layout and constraints match ds4_mmq_*_moe_vec:
|
||||
// out_[token * n_expert_used + slot, row]
|
||||
// K must be a multiple of 256.
|
||||
|
||||
int ds4_mmq_iq2_xxs_moe_pair_raw_vec(
|
||||
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);
|
||||
|
||||
int ds4_mmq_q4_K_moe_pair_raw_vec(
|
||||
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);
|
||||
|
||||
// Dense vector matmul entry (Step 6). Same shape semantics as
|
||||
// ds4_mmq_q8_0_dense but routed through mmvq for batch counts that
|
||||
// favour the vec path (n_tokens <= 8 on Blackwell).
|
||||
//
|
||||
// Returns 0 on success, non-zero on validation or launch failure.
|
||||
|
||||
int ds4_mmq_q8_0_dense_vec(
|
||||
const void * W_q8_0,
|
||||
const float * X_f32,
|
||||
float * out_f32,
|
||||
int M,
|
||||
int N,
|
||||
int K,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_q4_K_dense_pair_vec(
|
||||
const void * W0_q4_K,
|
||||
const void * W1_q4_K,
|
||||
const float * X_f32,
|
||||
float * out0_f32,
|
||||
float * out1_f32,
|
||||
int M,
|
||||
int K,
|
||||
cudaStream_t stream);
|
||||
|
||||
// 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 cudaStreamPerThread to restore the default; NULL selects the legacy
|
||||
// default stream.
|
||||
void ds4_pool_set_stream(cudaStream_t stream);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,95 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Internal launcher for the gated D2R Q2_K MoE down-GEMM path.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(GGML_USE_HIP)
|
||||
#include "vendors/hip.h"
|
||||
#else
|
||||
#include <cuda_runtime.h>
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
bool ds4_mmq_q2_K_moe_d2r_available(int cc);
|
||||
bool ds4_mmq_iq2_xxs_moe_d2r_available(int cc);
|
||||
|
||||
size_t ds4_mmq_q2_K_moe_d2r_scratch_bytes(int64_t ncols_max, int n_experts);
|
||||
size_t ds4_mmq_iq2_xxs_moe_d2r_pair_scratch_bytes(int64_t ncols_max, int n_experts);
|
||||
size_t ds4_mmq_iq2_xxs_moe_d2r_fused_scratch_bytes(
|
||||
int64_t ncols_max, int n_experts);
|
||||
|
||||
int ds4_mmq_q2_K_moe_d2r_launch(
|
||||
const void * W_soa,
|
||||
int64_t soa_blocks,
|
||||
const void * q8,
|
||||
const int32_t * ids_dst,
|
||||
const int32_t * expert_bounds,
|
||||
float * out,
|
||||
int M,
|
||||
int K,
|
||||
int64_t ne_get_rows,
|
||||
int n_experts,
|
||||
void * worklist_scratch,
|
||||
size_t worklist_scratch_bytes,
|
||||
cudaStream_t stream);
|
||||
|
||||
bool ds4_mmq_q8_0_dense_d2r_available(int cc);
|
||||
|
||||
// Dense Q8_0 D2R on the kind-5 aligned artifact (--repack-q8-aligned).
|
||||
// q8 = block_q8_1_mmq D4 activation buffer ([k128][col], stride N cols,
|
||||
// over-allocated >= 128 blocks past N*K/128 for the guarded last col tile).
|
||||
int ds4_mmq_q8_0_dense_d2r_launch(
|
||||
const void * W_aligned,
|
||||
const void * q8,
|
||||
float * out,
|
||||
int M,
|
||||
int N,
|
||||
int K,
|
||||
cudaStream_t stream);
|
||||
|
||||
int ds4_mmq_iq2_xxs_moe_d2r_pair_launch(
|
||||
const void * gate_soa,
|
||||
const void * up_soa,
|
||||
int64_t soa_blocks,
|
||||
const void * q8,
|
||||
const int32_t * ids_dst,
|
||||
const int32_t * expert_bounds,
|
||||
float * out_gate,
|
||||
float * out_up,
|
||||
int M,
|
||||
int K,
|
||||
int64_t ne_get_rows,
|
||||
int n_experts,
|
||||
void * worklist_scratch,
|
||||
size_t worklist_scratch_bytes,
|
||||
cudaStream_t stream);
|
||||
|
||||
// Complete target-prefill gate/up path: both IQ2_XXS projections share one
|
||||
// activation tile, then sanitize + clamp + SwiGLU + routing weight are folded
|
||||
// directly into the expert-major Q8_1 D2S6 input consumed by Q2_K down.
|
||||
//
|
||||
// flat-pool p5b: with ids_src != NULL, input_q8 is TOKEN-COMPACT (n_tokens
|
||||
// rows quantized once, no gather) and each assignment column stages its
|
||||
// token's row through ids_src[col]; with ids_src == NULL, input_q8 is the
|
||||
// classic slot-gathered buffer (ne_get_rows rows) and n_tokens is ignored.
|
||||
int ds4_mmq_iq2_xxs_moe_d2r_fused_launch(
|
||||
const void * gate_soa,
|
||||
const void * up_soa,
|
||||
int64_t soa_blocks,
|
||||
const void * input_q8,
|
||||
const int32_t * ids_src,
|
||||
int n_tokens,
|
||||
const int32_t * ids_dst,
|
||||
const int32_t * expert_bounds,
|
||||
const float * router_weights,
|
||||
void * down_q8,
|
||||
int M,
|
||||
int K,
|
||||
int64_t ne_get_rows,
|
||||
int n_experts,
|
||||
float clamp,
|
||||
void * worklist_scratch,
|
||||
size_t worklist_scratch_bytes,
|
||||
cudaStream_t stream);
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,133 @@
|
||||
/* ds4_repack: the aligned-SoA derived-weight layout library shared by the
|
||||
* weight server (tools/ds4_weight_server.cu) and the engine's in-process
|
||||
* self-load build (ds4_cuda.cu). The GGUF tensor catalog walk, the repack
|
||||
* candidate predicates, the S5 parallel repack driver, the FNV-1a artifact
|
||||
* hash, and the three aligned repack builds (IQ2_XXS gate/up, Q2_K down,
|
||||
* Q8_0 dense) live here so both producers emit bit-identical artifacts
|
||||
* (gate: --repack-hash / DS4_WS_REPACK_HASH FNV lines). Layout contracts
|
||||
* are shared with the consuming kernels in cuda/mmq/ds4_mmq.h. */
|
||||
#ifndef DS4_REPACK_H
|
||||
#define DS4_REPACK_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct ds4_repack_file {
|
||||
int fd = -1;
|
||||
int direct_fd = -1;
|
||||
const uint8_t *data = nullptr;
|
||||
uint64_t size = 0;
|
||||
uint64_t direct_align = 1;
|
||||
};
|
||||
|
||||
struct ds4_repack_span {
|
||||
uint64_t off = 0;
|
||||
uint64_t end = 0;
|
||||
};
|
||||
|
||||
struct ds4_repack_tensor {
|
||||
std::string name;
|
||||
uint32_t type = 0;
|
||||
uint32_t ndim = 0;
|
||||
uint64_t dims[8] = {};
|
||||
uint64_t elements = 0;
|
||||
uint64_t off = 0;
|
||||
uint64_t bytes = 0;
|
||||
};
|
||||
|
||||
/* Aligned-artifact kinds. Numbering is shared with the weight-server
|
||||
* manifest format and the engine's cuda_derived_kind (ds4_cuda.cu); the
|
||||
* layout comments live on the builders in ds4_repack.cu. */
|
||||
enum {
|
||||
DS4_REPACK_Q8_0_F16_COLMAJOR = 2,
|
||||
DS4_REPACK_IQ2_XXS_ALIGNED_MOE = 4,
|
||||
DS4_REPACK_Q8_0_ALIGNED_DENSE = 5,
|
||||
DS4_REPACK_Q2_K_ALIGNED_MOE = 6,
|
||||
};
|
||||
|
||||
struct ds4_repack_artifact {
|
||||
const ds4_repack_tensor *t = nullptr; /* borrowed from the caller's catalog */
|
||||
uint32_t kind = 0; /* DS4_REPACK_* */
|
||||
uint64_t bytes = 0; /* artifact byte size */
|
||||
uint64_t in_dim = 0; /* dims[0] */
|
||||
uint64_t out_dim = 0; /* dims[1] */
|
||||
uint32_t group_count = 0; /* dims[2] for MoE kinds, 1 for dense,
|
||||
0 for f16 colmajor (the engine's
|
||||
lazy-cache lookup passes 0) */
|
||||
void *dev = nullptr; /* device artifact buffer */
|
||||
};
|
||||
|
||||
/* Optional device-allocation hooks (weight server VMM backend). alloc must
|
||||
* fill art->dev with at least art->bytes of device memory and return true;
|
||||
* free releases an allocation made by alloc. Leave both NULL for plain
|
||||
* cudaMalloc/cudaFree. */
|
||||
typedef bool (*ds4_repack_alloc_fn)(void *ctx, ds4_repack_artifact *art);
|
||||
typedef void (*ds4_repack_free_fn)(void *ctx, ds4_repack_artifact *art);
|
||||
|
||||
struct ds4_repack_build_args {
|
||||
const char *log_prefix = "ds4"; /* stderr line prefix ("ds4_weight_server" | "ds4") */
|
||||
const char *model_id = "base"; /* log label */
|
||||
const char *path = nullptr; /* GGUF path (each builder re-maps it) */
|
||||
const std::vector<ds4_repack_tensor> *records = nullptr;
|
||||
int device = 0;
|
||||
uint64_t copy_chunk_bytes = 0; /* staged read chunk (weight server --copy-chunk-mb) */
|
||||
ds4_repack_alloc_fn alloc_fn = nullptr;
|
||||
ds4_repack_free_fn free_fn = nullptr;
|
||||
void *alloc_ctx = nullptr;
|
||||
};
|
||||
|
||||
bool ds4_repack_map_file(const char *log_prefix, const char *path, ds4_repack_file &m);
|
||||
void ds4_repack_unmap_file(ds4_repack_file &m);
|
||||
|
||||
/* Parse the GGUF header + tensor table into whole-tensor spans and records
|
||||
* with absolute file offsets. Either out pointer may be NULL. */
|
||||
bool ds4_repack_collect_catalog(const char *log_prefix,
|
||||
const ds4_repack_file &m,
|
||||
std::vector<ds4_repack_span> *spans,
|
||||
std::vector<ds4_repack_tensor> *records);
|
||||
|
||||
bool ds4_repack_pread_full(int fd, void *buf, uint64_t bytes, uint64_t offset);
|
||||
/* Read [file_off, file_off+bytes) into stage, preferring the O_DIRECT fd;
|
||||
* *payload points at the requested bytes inside stage. */
|
||||
bool ds4_repack_read_stage(const ds4_repack_file &m, void *stage, uint64_t stage_bytes,
|
||||
uint64_t file_off, uint64_t bytes, const char **payload);
|
||||
|
||||
/* Candidate predicates for the three aligned repacks. */
|
||||
bool ds4_repack_iq2_candidate(const ds4_repack_tensor &t);
|
||||
bool ds4_repack_q2k_candidate(const ds4_repack_tensor &t);
|
||||
bool ds4_repack_q8_candidate(const ds4_repack_tensor &t);
|
||||
bool ds4_repack_q8_f16_candidate(const ds4_repack_tensor &t);
|
||||
|
||||
/* S5 driver configuration. Unset (0 / false) defers to the env knobs
|
||||
* DS4_WS_REPACK_THREADS and DS4_WS_REPACK_HASH. */
|
||||
void ds4_repack_set_threads(int n);
|
||||
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
|
||||
* allocated and returns false without touching out. */
|
||||
bool ds4_repack_build_q8_aligned(const ds4_repack_build_args &a,
|
||||
std::vector<ds4_repack_artifact> &out,
|
||||
uint64_t *repacked_bytes_out);
|
||||
bool ds4_repack_build_iq2_aligned(const ds4_repack_build_args &a,
|
||||
std::vector<ds4_repack_artifact> &out,
|
||||
uint64_t *repacked_bytes_out);
|
||||
bool ds4_repack_build_q2k_aligned(const ds4_repack_build_args &a,
|
||||
std::vector<ds4_repack_artifact> &out,
|
||||
uint64_t *repacked_bytes_out);
|
||||
bool ds4_repack_build_q8_f16(const ds4_repack_build_args &a,
|
||||
std::vector<ds4_repack_artifact> &out,
|
||||
uint64_t *repacked_bytes_out);
|
||||
|
||||
#endif /* DS4_REPACK_H */
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,4 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Thin redirect - see ggml.h.
|
||||
#pragma once
|
||||
#include "ds4_ggml_stubs.h"
|
||||
@@ -0,0 +1,4 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Thin redirect - see ggml.h.
|
||||
#pragma once
|
||||
#include "ds4_ggml_stubs.h"
|
||||
@@ -0,0 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Thin redirect so the vendored common.cuh / vecdotq.cuh / mmid.cu / etc.
|
||||
// can write #include "ggml.h" unchanged.
|
||||
#pragma once
|
||||
#include "ds4_ggml_stubs.h"
|
||||
+1456
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,339 @@
|
||||
#include "common.cuh"
|
||||
#include "mmid.cuh"
|
||||
|
||||
// To reduce shared memory use, store "it" and "iex_used" with 22/10 bits each.
|
||||
struct mm_ids_helper_store {
|
||||
uint32_t data;
|
||||
|
||||
__device__ mm_ids_helper_store(const uint32_t it, const uint32_t iex_used) {
|
||||
data = (it & 0x003FFFFF) | (iex_used << 22);
|
||||
}
|
||||
|
||||
__device__ uint32_t it() const {
|
||||
return data & 0x003FFFFF;
|
||||
}
|
||||
|
||||
__device__ uint32_t iex_used() const {
|
||||
return data >> 22;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(mm_ids_helper_store) == 4, "unexpected size for mm_ids_helper_store");
|
||||
|
||||
// Helper function for mul_mat_id, converts ids to a more convenient format.
|
||||
// ids_src1 describes how to permute the flattened column indices of src1 in order to get a compact src1 tensor sorted by expert.
|
||||
// ids_dst describes the same mapping but for the dst tensor.
|
||||
// The upper and lower bounds for the ith expert in the compact src1 tensor are stored in expert_bounds[i:i+1].
|
||||
template <int n_expert_used_template>
|
||||
__launch_bounds__(ggml_cuda_get_physical_warp_size(), 1)
|
||||
static __global__ void mm_ids_helper(
|
||||
const int32_t * __restrict__ ids, int32_t * __restrict__ ids_src1, int32_t * __restrict__ ids_dst, int32_t * __restrict__ expert_bounds,
|
||||
const int n_tokens, const int n_expert_used_var, const int nchannels_y, const int si1, const int sis1) {
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
const int n_expert_used = n_expert_used_template == 0 ? n_expert_used_var : n_expert_used_template;
|
||||
const int expert = blockIdx.x;
|
||||
|
||||
extern __shared__ char data_mm_ids_helper[];
|
||||
mm_ids_helper_store * store = (mm_ids_helper_store *) data_mm_ids_helper;
|
||||
|
||||
int nex_prev = 0; // Number of columns for experts with a lower index.
|
||||
int it_compact = 0; // Running index for the compact slice of this expert.
|
||||
|
||||
if constexpr (n_expert_used_template == 0) {
|
||||
// Generic implementation:
|
||||
for (int it = 0; it < n_tokens; ++it) {
|
||||
int iex_used = -1; // The index at which the expert is used, if any.
|
||||
for (int iex = threadIdx.x; iex < n_expert_used; iex += warp_size) {
|
||||
const int expert_used = ids[it*si1 + iex];
|
||||
nex_prev += expert_used < expert;
|
||||
if (expert_used == expert) {
|
||||
iex_used = iex;
|
||||
}
|
||||
}
|
||||
|
||||
if (iex_used != -1) {
|
||||
store[it_compact] = mm_ids_helper_store(it, iex_used);
|
||||
}
|
||||
|
||||
if (warp_reduce_any<warp_size>(iex_used != -1)) {
|
||||
it_compact++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Implementation optimized for specific numbers of experts used:
|
||||
static_assert(n_expert_used == 6 || warp_size % n_expert_used == 0, "bad n_expert_used");
|
||||
const int neu_padded = n_expert_used == 6 ? 8 : n_expert_used; // Padded to next higher power of 2.
|
||||
for (int it0 = 0; it0 < n_tokens; it0 += warp_size/neu_padded) {
|
||||
const int it = it0 + threadIdx.x / neu_padded;
|
||||
|
||||
const int iex = threadIdx.x % neu_padded; // The index at which the expert is used, if any.
|
||||
const int expert_used = (neu_padded == n_expert_used || iex < n_expert_used) && it < n_tokens ?
|
||||
ids[it*si1 + iex] : INT_MAX;
|
||||
const int iex_used = expert_used == expert ? iex : -1;
|
||||
nex_prev += expert_used < expert;
|
||||
|
||||
// Whether the threads at this token position have used the expert:
|
||||
const int it_compact_add_self = warp_reduce_any<neu_padded>(iex_used != -1);
|
||||
|
||||
// Do a scan over threads at lower token positions in warp to get the correct index for writing data:
|
||||
int it_compact_add_lower = 0;
|
||||
#pragma unroll
|
||||
for (int offset = neu_padded; offset < warp_size; offset += neu_padded) {
|
||||
const int tmp = __shfl_up_sync(0xFFFFFFFF, it_compact_add_self, offset, warp_size);
|
||||
if (threadIdx.x >= static_cast<unsigned int>(offset)) {
|
||||
it_compact_add_lower += tmp;
|
||||
}
|
||||
}
|
||||
|
||||
if (iex_used != -1) {
|
||||
store[it_compact + it_compact_add_lower] = mm_ids_helper_store(it, iex_used);
|
||||
}
|
||||
|
||||
// The thread with the highest index in the warp always has the sum over the whole warp, use it to increment all threads:
|
||||
it_compact += __shfl_sync(0xFFFFFFFF, it_compact_add_lower + it_compact_add_self, warp_size - 1, warp_size);
|
||||
}
|
||||
}
|
||||
nex_prev = warp_reduce_sum<warp_size>(nex_prev);
|
||||
|
||||
// Post-Volta independent thread scheduling: the store[] writes above are done by
|
||||
// some warp lanes and read below by other lanes. Without an explicit warp barrier
|
||||
// those shared-memory writes are not guaranteed visible to the cross-lane reads,
|
||||
// so a lane could read a stale/uninitialized store[] slot -> wrong compacted
|
||||
// expert ids -> nondeterministic MoE routing. On GB10 (sm_121) this realized as
|
||||
// the cont-multiseq non-determinism + BOS-spam (compute-sanitizer racecheck:
|
||||
// "RAW hazard at __shared__" between the store writes and these reads).
|
||||
__syncwarp();
|
||||
|
||||
for (int itc = threadIdx.x; itc < it_compact; itc += warp_size) {
|
||||
const mm_ids_helper_store store_it = store[itc];
|
||||
const int it = store_it.it();
|
||||
const int iex_used = store_it.iex_used();
|
||||
ids_src1[nex_prev + itc] = it*sis1 + iex_used % nchannels_y;
|
||||
ids_dst [nex_prev + itc] = it*n_expert_used + iex_used;
|
||||
}
|
||||
|
||||
if (threadIdx.x != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
expert_bounds[expert] = nex_prev;
|
||||
|
||||
if (expert < static_cast<int>(gridDim.x) - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
expert_bounds[gridDim.x] = nex_prev + it_compact;
|
||||
}
|
||||
|
||||
// ds4 local (P5): large-n variant of mm_ids_helper with NO shared-memory
|
||||
// staging. The smem kernel above stages the compact (it, iex_used) list in
|
||||
// n_tokens*4 B of dynamic shared memory, capping n_tokens at smpbo/4 (~25k on
|
||||
// GB10); the routed-MoE down matmul passes n_tokens = assignment rows
|
||||
// (6x the forward width), so 8192-row prefill chunks hit 48384 "tokens" and
|
||||
// the whole MoE block used to fall back to the pre-mmq expert-tile kernels.
|
||||
// This variant runs the SAME per-expert scan twice: pass 0 only counts
|
||||
// (nex_prev, bucket size), pass 1 re-scans and writes ids_src1/ids_dst
|
||||
// directly at their final offsets. Bit-identical to the smem kernel by
|
||||
// construction: identical (it, iex_used) tuples in identical token order and
|
||||
// identical output expressions — the (lossless) 22/10-bit store round-trip is
|
||||
// simply removed. The rescan is cheap: the ids array at the target shape is
|
||||
// ~190 KB and L2-resident across all expert blocks. Like the smem kernel,
|
||||
// behavior is undefined if one token lists the same expert in multiple slots
|
||||
// (the router's top-k is without replacement, so this cannot occur).
|
||||
template <int n_expert_used_template>
|
||||
__launch_bounds__(ggml_cuda_get_physical_warp_size(), 1)
|
||||
static __global__ void mm_ids_helper_global(
|
||||
const int32_t * __restrict__ ids, int32_t * __restrict__ ids_src1, int32_t * __restrict__ ids_dst, int32_t * __restrict__ expert_bounds,
|
||||
const int n_tokens, const int n_expert_used_var, const int nchannels_y, const int si1, const int sis1) {
|
||||
constexpr int warp_size = ggml_cuda_get_physical_warp_size();
|
||||
const int n_expert_used = n_expert_used_template == 0 ? n_expert_used_var : n_expert_used_template;
|
||||
const int expert = blockIdx.x;
|
||||
|
||||
int nex_prev = 0; // Number of columns for experts with a lower index.
|
||||
int it_compact_count = 0; // Bucket size for this expert (pass-0 result).
|
||||
|
||||
#pragma unroll 1
|
||||
for (int pass = 0; pass < 2; ++pass) {
|
||||
int it_compact = 0; // Running index for the compact slice of this expert.
|
||||
|
||||
if constexpr (n_expert_used_template == 0) {
|
||||
// Generic implementation:
|
||||
for (int it = 0; it < n_tokens; ++it) {
|
||||
int iex_used = -1; // The index at which the expert is used, if any.
|
||||
for (int iex = threadIdx.x; iex < n_expert_used; iex += warp_size) {
|
||||
const int expert_used = ids[it*si1 + iex];
|
||||
if (pass == 0) {
|
||||
nex_prev += expert_used < expert;
|
||||
}
|
||||
if (expert_used == expert) {
|
||||
iex_used = iex;
|
||||
}
|
||||
}
|
||||
|
||||
if (pass == 1 && iex_used != -1) {
|
||||
ids_src1[nex_prev + it_compact] = it*sis1 + iex_used % nchannels_y;
|
||||
ids_dst [nex_prev + it_compact] = it*n_expert_used + iex_used;
|
||||
}
|
||||
|
||||
if (warp_reduce_any<warp_size>(iex_used != -1)) {
|
||||
it_compact++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Implementation optimized for specific numbers of experts used:
|
||||
static_assert(n_expert_used == 6 || warp_size % n_expert_used == 0, "bad n_expert_used");
|
||||
const int neu_padded = n_expert_used == 6 ? 8 : n_expert_used; // Padded to next higher power of 2.
|
||||
for (int it0 = 0; it0 < n_tokens; it0 += warp_size/neu_padded) {
|
||||
const int it = it0 + threadIdx.x / neu_padded;
|
||||
|
||||
const int iex = threadIdx.x % neu_padded; // The index at which the expert is used, if any.
|
||||
const int expert_used = (neu_padded == n_expert_used || iex < n_expert_used) && it < n_tokens ?
|
||||
ids[it*si1 + iex] : INT_MAX;
|
||||
const int iex_used = expert_used == expert ? iex : -1;
|
||||
if (pass == 0) {
|
||||
nex_prev += expert_used < expert;
|
||||
}
|
||||
|
||||
// Whether the threads at this token position have used the expert:
|
||||
const int it_compact_add_self = warp_reduce_any<neu_padded>(iex_used != -1);
|
||||
|
||||
// Do a scan over threads at lower token positions in warp to get the correct index for writing data:
|
||||
int it_compact_add_lower = 0;
|
||||
#pragma unroll
|
||||
for (int offset = neu_padded; offset < warp_size; offset += neu_padded) {
|
||||
const int tmp = __shfl_up_sync(0xFFFFFFFF, it_compact_add_self, offset, warp_size);
|
||||
if (threadIdx.x >= static_cast<unsigned int>(offset)) {
|
||||
it_compact_add_lower += tmp;
|
||||
}
|
||||
}
|
||||
|
||||
if (pass == 1 && iex_used != -1) {
|
||||
const int itc = it_compact + it_compact_add_lower;
|
||||
ids_src1[nex_prev + itc] = it*sis1 + iex_used % nchannels_y;
|
||||
ids_dst [nex_prev + itc] = it*n_expert_used + iex_used;
|
||||
}
|
||||
|
||||
// The thread with the highest index in the warp always has the sum over the whole warp, use it to increment all threads:
|
||||
it_compact += __shfl_sync(0xFFFFFFFF, it_compact_add_lower + it_compact_add_self, warp_size - 1, warp_size);
|
||||
}
|
||||
}
|
||||
|
||||
if (pass == 0) {
|
||||
it_compact_count = it_compact;
|
||||
nex_prev = warp_reduce_sum<warp_size>(nex_prev);
|
||||
}
|
||||
}
|
||||
|
||||
if (threadIdx.x != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
expert_bounds[expert] = nex_prev;
|
||||
|
||||
if (expert < static_cast<int>(gridDim.x) - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
expert_bounds[gridDim.x] = nex_prev + it_compact_count;
|
||||
}
|
||||
|
||||
// ds4 local: kill switch for the large-n global path (DS4_MMID_LARGE=0).
|
||||
// With the switch off the ds4_mmq.cu callers refuse past-cap shapes exactly
|
||||
// as before (whole-MoE fallback to the expert-tile kernels).
|
||||
bool ds4_mmid_large_enabled(void) {
|
||||
static int cached = -1;
|
||||
if (cached < 0) {
|
||||
const char * env = getenv("DS4_MMID_LARGE");
|
||||
cached = !(env && env[0] == '0');
|
||||
}
|
||||
return cached != 0;
|
||||
}
|
||||
|
||||
template <int n_expert_used_template>
|
||||
static void launch_mm_ids_helper(
|
||||
const int32_t * __restrict__ ids, int32_t * __restrict__ ids_src1, int32_t * __restrict__ ids_dst, int32_t * __restrict__ expert_bounds,
|
||||
const int n_experts, const int n_tokens, const int n_expert_used_var, const int nchannels_y, const int si1, const int sis1, cudaStream_t stream) {
|
||||
GGML_ASSERT(n_tokens < (1 << 22) && "too few bits in mm_ids_helper_store");
|
||||
GGML_ASSERT(n_expert_used_var < (1 << 10) && "too few bits in mm_ids_helper_store");
|
||||
|
||||
const int id = ggml_cuda_get_device();
|
||||
const int warp_size = ggml_cuda_info().devices[id].warp_size;
|
||||
const size_t smpbo = ggml_cuda_info().devices[id].smpbo;
|
||||
|
||||
const dim3 num_blocks(n_experts, 1, 1);
|
||||
const dim3 block_size(warp_size, 1, 1);
|
||||
const size_t nbytes_shared = n_tokens*sizeof(mm_ids_helper_store);
|
||||
|
||||
// ds4 local (P5): past the smem cap, take the two-pass global variant
|
||||
// (bit-identical outputs, see mm_ids_helper_global). One-shot stderr
|
||||
// line = path proof for the gate harnesses.
|
||||
if (nbytes_shared > smpbo) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
logged = true;
|
||||
fprintf(stderr, "ds4: mm_ids_helper large-n global path engaged (P5, n_tokens=%d > cap %zu)\n",
|
||||
n_tokens, smpbo / sizeof(mm_ids_helper_store));
|
||||
}
|
||||
mm_ids_helper_global<n_expert_used_template><<<num_blocks, block_size, 0, stream>>>
|
||||
(ids, ids_src1, ids_dst, expert_bounds, n_tokens, n_expert_used_var, nchannels_y, si1, sis1);
|
||||
return;
|
||||
}
|
||||
|
||||
CUDA_SET_SHARED_MEMORY_LIMIT(mm_ids_helper<n_expert_used_template>, smpbo);
|
||||
mm_ids_helper<n_expert_used_template><<<num_blocks, block_size, nbytes_shared, stream>>>
|
||||
(ids, ids_src1, ids_dst, expert_bounds, n_tokens, n_expert_used_var, nchannels_y, si1, sis1);
|
||||
}
|
||||
|
||||
// ds4 local: kill switch for the case-1 fast path below (DS4_MMID_CASE1=0).
|
||||
static bool ds4_mmid_case1_enabled() {
|
||||
static int cached = -1;
|
||||
if (cached < 0) {
|
||||
const char * env = getenv("DS4_MMID_CASE1");
|
||||
cached = !(env && env[0] == '0');
|
||||
}
|
||||
return cached != 0;
|
||||
}
|
||||
|
||||
void ggml_cuda_launch_mm_ids_helper(
|
||||
const int32_t * __restrict__ ids, int32_t * __restrict__ ids_src1, int32_t * __restrict__ ids_dst, int32_t * __restrict__ expert_bounds,
|
||||
const int n_experts, const int n_tokens, const int n_expert_used, const int nchannels_y, const int si1, const int sis1, cudaStream_t stream) {
|
||||
switch (n_expert_used) {
|
||||
case 1:
|
||||
// ds4 local: the routed-MoE down matmul reinterprets (token, slot)
|
||||
// assignment rows as single-expert "tokens" (ds4_mmq.cu
|
||||
// ds4_mmq_moe_impl, n_expert_used=1). Without this case it fell to
|
||||
// the generic <0> template: one warp per expert scanning all
|
||||
// assignment rows with a single active lane -> 22.5 ms/launch at
|
||||
// W4096 prefill (2.90 s of a 12k admission). The optimized template
|
||||
// at neu_padded=1 covers 32 rows/iteration and emits bit-identical
|
||||
// id maps (proto_mm_ids.cu: parity on uniform/skewed/2%-invalid/
|
||||
// decode shapes, 20.4x at the W4096 shape). DS4_MMID_CASE1=0
|
||||
// reverts to the generic path.
|
||||
if (ds4_mmid_case1_enabled()) {
|
||||
launch_mm_ids_helper< 1>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, stream);
|
||||
} else {
|
||||
launch_mm_ids_helper< 0>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, stream);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
launch_mm_ids_helper< 2>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, stream);
|
||||
break;
|
||||
case 4:
|
||||
launch_mm_ids_helper< 4>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, stream);
|
||||
break;
|
||||
case 6:
|
||||
launch_mm_ids_helper< 6>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, stream);
|
||||
break;
|
||||
case 8:
|
||||
launch_mm_ids_helper< 8>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, stream);
|
||||
break;
|
||||
case 16:
|
||||
launch_mm_ids_helper<16>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, stream);
|
||||
break;
|
||||
case 32:
|
||||
launch_mm_ids_helper<32>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, stream);
|
||||
break;
|
||||
default:
|
||||
launch_mm_ids_helper< 0>(ids, ids_src1, ids_dst, expert_bounds, n_experts, n_tokens, n_expert_used, nchannels_y, si1, sis1, stream);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
void ggml_cuda_launch_mm_ids_helper(
|
||||
const int32_t * ids, int32_t * ids_src1, int32_t * ids_dst, int32_t * expert_bounds,
|
||||
int n_experts, int n_tokens, int n_expert_used, int nchannels_y, int si1, int sis1, cudaStream_t stream);
|
||||
|
||||
// ds4 local (P5): whether the large-n global-memory mm_ids path is enabled
|
||||
// (default on; DS4_MMID_LARGE=0 reverts callers to the past-cap refusal).
|
||||
bool ds4_mmid_large_enabled(void);
|
||||
+4500
File diff suppressed because it is too large.
Load diff
+1186
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include "common.cuh"
|
||||
|
||||
#define MMVQ_MAX_BATCH_SIZE 8 // Max. batch size for which to use MMVQ kernels.
|
||||
|
||||
// Returns the maximum batch size for which MMVQ should be used for MUL_MAT_ID,
|
||||
// based on the quantization type and GPU architecture (compute capability).
|
||||
int get_mmvq_mmid_max_batch(ggml_type type, int cc);
|
||||
|
||||
// ds4: direct switch over the per-type templated mmvq dispatch. Was
|
||||
// `static` upstream; we promote it so ds4_mmq.cu can call it without
|
||||
// constructing ggml_tensor structs. Args match the original signature
|
||||
// exactly.
|
||||
void mul_mat_vec_q_switch_type(
|
||||
const void * vx, const ggml_type type_x, const void * vy, const int32_t * ids, const ggml_cuda_mm_fusion_args_device fusion, float * dst,
|
||||
const int ncols_x, const int nrows_x, const int ncols_dst,
|
||||
const int stride_row_x, const int stride_col_y, const int stride_col_dst,
|
||||
const int nchannels_x, const int nchannels_y, const int nchannels_dst,
|
||||
const int stride_channel_x, const int stride_channel_y, const int stride_channel_dst,
|
||||
const int nsamples_x, const int nsamples_dst, const int stride_sample_x, const int stride_sample_y, const int stride_sample_dst,
|
||||
const int ids_stride, cudaStream_t stream);
|
||||
|
||||
#ifdef DS4_MMVQ_INCLUDE_GGML_ENTRIES
|
||||
// Original upstream ggml-graph entries. Disabled by default in the ds4
|
||||
// vendor; re-enable for parity comparisons against llama.cpp.
|
||||
void ggml_cuda_mul_mat_vec_q(ggml_backend_cuda_context & ctx,
|
||||
const ggml_tensor * src0, const ggml_tensor * src1, const ggml_tensor * ids, ggml_tensor * dst, const ggml_cuda_mm_fusion_args_host * fusion = nullptr);
|
||||
|
||||
void ggml_cuda_op_mul_mat_vec_q(
|
||||
ggml_backend_cuda_context & ctx,
|
||||
const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst, const char * src0_dd_i, const float * src1_ddf_i,
|
||||
const char * src1_ddq_i, float * dst_dd_i, const int64_t row_low, const int64_t row_high, const int64_t src1_ncols,
|
||||
const int64_t src1_padded_row_size, cudaStream_t stream);
|
||||
#endif
|
||||
@@ -0,0 +1,443 @@
|
||||
#include "quantize.cuh"
|
||||
#include <cstdint>
|
||||
|
||||
__launch_bounds__(CUDA_QUANTIZE_BLOCK_SIZE, 1)
|
||||
static __global__ void quantize_q8_1(
|
||||
const float * __restrict__ x, void * __restrict__ vy,
|
||||
const int64_t ne00, const int64_t s01, const int64_t s02, const int64_t s03,
|
||||
const int64_t ne0, const uint32_t ne1, const uint3 ne2) {
|
||||
const int64_t i0 = (int64_t)blockDim.x*blockIdx.x + threadIdx.x;
|
||||
|
||||
if (i0 >= ne0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int64_t i3 = fastdiv(blockIdx.z, ne2);
|
||||
const int64_t i2 = blockIdx.z - i3*ne2.z;
|
||||
const int64_t i1 = blockIdx.y;
|
||||
|
||||
const int64_t & i00 = i0;
|
||||
const int64_t & i01 = i1;
|
||||
const int64_t & i02 = i2;
|
||||
const int64_t & i03 = i3;
|
||||
|
||||
const int64_t i_cont = ((i3*ne2.z + i2) * ne1 + i1) * ne0 + i0;
|
||||
|
||||
block_q8_1 * y = (block_q8_1 *) vy;
|
||||
|
||||
const int64_t ib = i_cont / QK8_1; // block index
|
||||
const int64_t iqs = i_cont % QK8_1; // quant index
|
||||
|
||||
const float xi = i0 < ne00 ? x[i03*s03 + i02*s02 + i01*s01 + i00] : 0.0f;
|
||||
float amax = fabsf(xi);
|
||||
float sum = xi;
|
||||
|
||||
amax = warp_reduce_max<QK8_1>(amax);
|
||||
sum = warp_reduce_sum<QK8_1>(sum);
|
||||
|
||||
const float d = amax / 127.0f;
|
||||
const int8_t q = amax == 0.0f ? 0 : roundf(xi / d);
|
||||
|
||||
y[ib].qs[iqs] = q;
|
||||
|
||||
if (iqs > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
y[ib].ds = make_half2(d, sum);
|
||||
}
|
||||
|
||||
__device__ __forceinline__ uint8_t compute_e8m0_scale(float amax) {
|
||||
if (!(amax > 0.0f)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// FP4 E2M1: max exponent (unbiased) is 2.
|
||||
constexpr int FP4_E2M1_EMAX = 2;
|
||||
|
||||
const float e = log2f(amax);
|
||||
|
||||
// "even" -> round-to-nearest integer, ties-to-even
|
||||
const int e_int = __float2int_rn(e);
|
||||
|
||||
const int shared_exp = e_int - FP4_E2M1_EMAX;
|
||||
|
||||
int biased = shared_exp + 127;
|
||||
|
||||
biased = max(biased, 0);
|
||||
biased = min(biased, 254);
|
||||
|
||||
return static_cast<uint8_t>(biased);
|
||||
}
|
||||
|
||||
|
||||
static __global__ void quantize_mmq_nvfp4(
|
||||
const float * __restrict__ x, const int32_t * __restrict__ ids, void * __restrict__ vy,
|
||||
const int64_t ne00, const int64_t s01, const int64_t s02, const int64_t s03,
|
||||
const int64_t ne0, const int64_t ne1, const int64_t ne2) {
|
||||
#if defined(BLACKWELL_MMA_AVAILABLE)
|
||||
|
||||
const int64_t i0_base = ((int64_t) blockDim.x * blockIdx.y + threadIdx.x) * QK_NVFP4_SUB;
|
||||
if (i0_base >= ne0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int64_t i1 = blockIdx.x;
|
||||
const int64_t i2 = blockIdx.z % ne2;
|
||||
const int64_t i3 = blockIdx.z / ne2;
|
||||
const int64_t i01 = ids ? ids[i1] : i1;
|
||||
const int64_t k_block = i0_base / QK_K;
|
||||
const int64_t blocks_per_col = (ne0 + QK_K - 1) / QK_K;
|
||||
if (k_block >= blocks_per_col) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int64_t ib = blockIdx.z * ((int64_t) blocks_per_col * ne1) + k_block * ne1 + blockIdx.x;
|
||||
block_fp4_mmq * y = (block_fp4_mmq *) vy;
|
||||
block_fp4_mmq * yb = y + ib;
|
||||
|
||||
const int sub = (i0_base % QK_K) / QK_NVFP4_SUB;
|
||||
|
||||
float vals_raw[QK_NVFP4_SUB];
|
||||
float amax_raw = 0.0f;
|
||||
const int64_t base_idx = i3 * s03 + i2 * s02 + i01 * s01;
|
||||
#pragma unroll
|
||||
for (int k = 0; k < QK_NVFP4_SUB; k++) {
|
||||
const int64_t i00 = i0_base + k;
|
||||
if (i00 < ne00) {
|
||||
const float v = x[base_idx + i00];
|
||||
vals_raw[k] = v;
|
||||
amax_raw = fmaxf(amax_raw, fabsf(v));
|
||||
} else {
|
||||
vals_raw[k] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr int test_offsets[5] = { 0, -1, 1, -2, 2};
|
||||
const int first_fp8_code = (int) ggml_cuda_fp32_to_ue4m3(amax_raw / 6.0f);
|
||||
|
||||
float best_err = FLT_MAX;
|
||||
uint8_t fp8_code = 0;
|
||||
float subblock_scale = 0.0f;
|
||||
|
||||
#pragma unroll // Check +/- 2 to find best code to reduce NVFP4 activation loss. Negligible overhead on Blackwell.
|
||||
for (int i = 0; i < 5; i++) {
|
||||
const int test_code = first_fp8_code + test_offsets[i];
|
||||
if (test_code < 0 || test_code > 0x7e) {
|
||||
continue;
|
||||
}
|
||||
const uint8_t code = (uint8_t) test_code;
|
||||
const float test_scale = ggml_cuda_ue4m3_to_fp32(code);
|
||||
const float test_inv_scale = test_scale > 0.0f ? 0.5f / test_scale : 0.0f;
|
||||
float cur_err = 0.0f;
|
||||
#pragma unroll
|
||||
for (int k = 0; k < QK_NVFP4_SUB; ++k) {
|
||||
const float v = vals_raw[k];
|
||||
const uint8_t q = ggml_cuda_float_to_fp4_e2m1(v, test_inv_scale);
|
||||
const float err_diff = fabsf(v) - fabsf(kvalues_mxfp4[q & 0x7]) * test_scale;
|
||||
cur_err = fmaf(err_diff, err_diff, cur_err);
|
||||
}
|
||||
|
||||
if (cur_err < best_err) {
|
||||
best_err = cur_err;
|
||||
fp8_code = test_code;
|
||||
subblock_scale = test_scale;
|
||||
}
|
||||
}
|
||||
|
||||
const float inv_scale = subblock_scale > 0.0f ? 0.5f / subblock_scale : 0.0f;
|
||||
uint32_t q0 = 0;
|
||||
uint32_t q1 = 0;
|
||||
#pragma unroll // this is faster than the previous __nv_fp4x4_e2m1
|
||||
for (int k = 0; k < QK_NVFP4_SUB / 4; ++k) {
|
||||
q0 |= (uint32_t) ggml_cuda_float_to_fp4_e2m1(vals_raw[k + 0], inv_scale) << (8 * k);
|
||||
q0 |= (uint32_t) ggml_cuda_float_to_fp4_e2m1(vals_raw[k + 8], inv_scale) << (8 * k + 4);
|
||||
q1 |= (uint32_t) ggml_cuda_float_to_fp4_e2m1(vals_raw[k + 4], inv_scale) << (8 * k);
|
||||
q1 |= (uint32_t) ggml_cuda_float_to_fp4_e2m1(vals_raw[k + 12], inv_scale) << (8 * k + 4);
|
||||
}
|
||||
|
||||
uint32_t * yqs = reinterpret_cast<uint32_t *>(yb->qs);
|
||||
yqs[2 * sub + 0] = q0;
|
||||
yqs[2 * sub + 1] = q1;
|
||||
reinterpret_cast<uint8_t *>(yb->d4)[sub] = fp8_code;
|
||||
#else
|
||||
NO_DEVICE_CODE; // This is for Blackwell NVFP4 activations only.
|
||||
#endif // defined(BLACKWELL_MMA_AVAILABLE)
|
||||
|
||||
}
|
||||
|
||||
// quantize values in the format mxfp4 is stored which is interleaved nibbles
|
||||
// i.e. a block a0-a31 is represented as a0a16,a1a17 ...a15a31
|
||||
static __global__ void quantize_mmq_mxfp4(const float * __restrict__ x,
|
||||
const int32_t * __restrict__ ids,
|
||||
void * __restrict__ vy,
|
||||
const int64_t ne00,
|
||||
const int64_t s01,
|
||||
const int64_t s02,
|
||||
const int64_t s03,
|
||||
const int64_t ne0,
|
||||
const int ne1,
|
||||
const int ne2) {
|
||||
constexpr int vals_per_scale = 32;
|
||||
constexpr int vals_per_warp = 2 * vals_per_scale; // Each warp processes 2 blocks of 32 = 64 values
|
||||
|
||||
const int warp_id = threadIdx.y;
|
||||
const int lane_id_32 = threadIdx.x;
|
||||
|
||||
const int nwarps = blockDim.y;
|
||||
|
||||
const int64_t warp_start_offset = (blockIdx.y * nwarps + warp_id) * vals_per_warp;
|
||||
|
||||
if (warp_start_offset >= ne0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int64_t i1 = blockIdx.x;
|
||||
const int64_t i2 = blockIdx.z % ne2;
|
||||
const int64_t i3 = blockIdx.z / ne2;
|
||||
|
||||
const int64_t i01 = ids ? ids[i1] : i1;
|
||||
const int64_t i02 = i2;
|
||||
const int64_t i03 = i3;
|
||||
|
||||
block_fp4_mmq * y = (block_fp4_mmq *) vy;
|
||||
|
||||
const int64_t block_fp4_mmq_size = 8 * QK_MXFP4; // 256 values
|
||||
const int64_t ib0 = blockIdx.z * ((int64_t) ne1 * (ne0 / block_fp4_mmq_size));
|
||||
const int64_t ib = ib0 + (warp_start_offset / block_fp4_mmq_size) * ne1 + blockIdx.x;
|
||||
const int64_t quad_idx_in_block = (warp_start_offset % block_fp4_mmq_size) / vals_per_warp;
|
||||
|
||||
const int group_id = lane_id_32 / 4;
|
||||
const int lane_in_group = lane_id_32 % 4;
|
||||
const int base = group_id * 2;
|
||||
char2 * yqs2 = (char2 *) y[ib].qs;
|
||||
|
||||
const int64_t base_pos = i03 * s03 + i02 * s02 + i01 * s01;
|
||||
|
||||
uint8_t scales[2];
|
||||
|
||||
#pragma unroll
|
||||
for (int b = 0; b < 2; ++b) {
|
||||
const int64_t i0 = warp_start_offset + b * vals_per_scale + lane_id_32;
|
||||
const float xi = (i0 < ne00) ? x[base_pos + i0] : 0.0f;
|
||||
|
||||
float amax = fabsf(xi);
|
||||
#pragma unroll
|
||||
for (int mask = 16; mask > 0; mask >>= 1) {
|
||||
amax = fmaxf(amax, __shfl_xor_sync(0xFFFFFFFF, amax, mask, WARP_SIZE));
|
||||
}
|
||||
|
||||
const uint8_t e = compute_e8m0_scale(amax);
|
||||
scales[b] = e;
|
||||
const float inv_s = (amax == 0.0f) ? 0.0f : __frcp_rn(ggml_cuda_e8m0_to_fp32(e));
|
||||
|
||||
#if CUDART_VERSION >= 12080
|
||||
const float scaled_val = xi * inv_s;
|
||||
|
||||
const float val0 = __shfl_sync(0xFFFFFFFF, scaled_val, base, WARP_SIZE);
|
||||
const float val1 = __shfl_sync(0xFFFFFFFF, scaled_val, base + 16, WARP_SIZE);
|
||||
const float val2 = __shfl_sync(0xFFFFFFFF, scaled_val, base + 1, WARP_SIZE);
|
||||
const float val3 = __shfl_sync(0xFFFFFFFF, scaled_val, base + 17, WARP_SIZE);
|
||||
|
||||
if (lane_in_group == 0) {
|
||||
__nv_fp4x4_e2m1 fp4_packed(make_float4(val0, val1, val2, val3));
|
||||
|
||||
yqs2[quad_idx_in_block * 16 + b * 8 + group_id] = *(char2 *) &fp4_packed;
|
||||
}
|
||||
#else
|
||||
// Fallback: manual FP4 conversion using LUT
|
||||
const uint8_t q_val = ggml_cuda_float_to_fp4_e2m1(xi, inv_s);
|
||||
|
||||
const uint8_t q_lo_0 = __shfl_sync(0xFFFFFFFF, q_val, base, WARP_SIZE);
|
||||
const uint8_t q_lo_1 = __shfl_sync(0xFFFFFFFF, q_val, base + 1, WARP_SIZE);
|
||||
const uint8_t q_hi_0 = __shfl_sync(0xFFFFFFFF, q_val, base + 16, WARP_SIZE);
|
||||
const uint8_t q_hi_1 = __shfl_sync(0xFFFFFFFF, q_val, base + 17, WARP_SIZE);
|
||||
|
||||
if (lane_in_group == 0) {
|
||||
char2 q;
|
||||
q.x = (q_hi_0 << 4) | q_lo_0;
|
||||
q.y = (q_hi_1 << 4) | q_lo_1;
|
||||
yqs2[quad_idx_in_block * 16 + b * 8 + group_id] = q;
|
||||
}
|
||||
#endif // CUDART_VERSION >= 12080
|
||||
}
|
||||
|
||||
if (lane_id_32 == 0) {
|
||||
// Store 2 scales packed into 1 uint32
|
||||
y[ib].d4[quad_idx_in_block] = (scales[1] << 8) | scales[0];
|
||||
}
|
||||
}
|
||||
|
||||
template <mmq_q8_1_ds_layout ds_layout>
|
||||
static __global__ void quantize_mmq_q8_1(
|
||||
const float * __restrict__ x, const int32_t * __restrict__ ids, void * __restrict__ vy,
|
||||
const int64_t ne00, const int64_t s01, const int64_t s02, const int64_t s03,
|
||||
const int64_t ne0, const int ne1, const int ne2) {
|
||||
|
||||
constexpr int vals_per_scale = ds_layout == MMQ_Q8_1_DS_LAYOUT_D2S6 ? 64 : 32;
|
||||
constexpr int vals_per_sum = ds_layout == MMQ_Q8_1_DS_LAYOUT_D2S6 ? 16 : 32;
|
||||
|
||||
const int64_t i0 = ((int64_t)blockDim.x*blockIdx.y + threadIdx.x)*4;
|
||||
|
||||
if (i0 >= ne0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int64_t i1 = blockIdx.x;
|
||||
const int64_t i2 = blockIdx.z % ne2;
|
||||
const int64_t i3 = blockIdx.z / ne2;
|
||||
|
||||
const int64_t i00 = i0;
|
||||
const int64_t i01 = ids ? ids[i1] : i1;
|
||||
const int64_t i02 = i2;
|
||||
const int64_t i03 = i3;
|
||||
|
||||
const float4 * x4 = (const float4 *) x;
|
||||
|
||||
block_q8_1_mmq * y = (block_q8_1_mmq *) vy;
|
||||
|
||||
const int64_t ib0 = blockIdx.z*((int64_t)gridDim.x*gridDim.y*blockDim.x/QK8_1); // first block of channel
|
||||
const int64_t ib = ib0 + (i0 / (4*QK8_1))*ne1 + blockIdx.x; // block index in channel
|
||||
const int64_t iqs = i0 % (4*QK8_1); // quant index in block
|
||||
|
||||
// Load 4 floats per thread and calculate max. abs. value between them:
|
||||
const float4 xi = i0 < ne00 ? x4[(i03*s03 + i02*s02 + i01*s01 + i00)/4] : make_float4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
float amax = fabsf(xi.x);
|
||||
amax = fmaxf(amax, fabsf(xi.y));
|
||||
amax = fmaxf(amax, fabsf(xi.z));
|
||||
amax = fmaxf(amax, fabsf(xi.w));
|
||||
|
||||
// Exchange max. abs. value between vals_per_scale/4 threads.
|
||||
#pragma unroll
|
||||
for (int offset = vals_per_scale/8; offset > 0; offset >>= 1) {
|
||||
amax = fmaxf(amax, __shfl_xor_sync(0xFFFFFFFF, amax, offset, WARP_SIZE));
|
||||
}
|
||||
|
||||
float sum;
|
||||
if (ds_layout != MMQ_Q8_1_DS_LAYOUT_D4) {
|
||||
sum = xi.x + xi.y + xi.z + xi.w;
|
||||
|
||||
// Calculate sums across vals_per_sum/4 threads.
|
||||
#pragma unroll
|
||||
for (int offset = vals_per_sum/8; offset > 0; offset >>= 1) {
|
||||
sum += __shfl_xor_sync(0xFFFFFFFF, sum, offset, WARP_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
const float d_inv = 127.0f / amax;
|
||||
char4 q;
|
||||
q.x = roundf(xi.x*d_inv);
|
||||
q.y = roundf(xi.y*d_inv);
|
||||
q.z = roundf(xi.z*d_inv);
|
||||
q.w = roundf(xi.w*d_inv);
|
||||
|
||||
// Write back 4 int8 values as a single 32 bit value for better memory bandwidth:
|
||||
char4 * yqs4 = (char4 *) y[ib].qs;
|
||||
yqs4[iqs/4] = q;
|
||||
|
||||
if (ds_layout == MMQ_Q8_1_DS_LAYOUT_D2S6) {
|
||||
if (iqs % 16 != 0 || iqs >= 96) {
|
||||
return;
|
||||
}
|
||||
|
||||
y[ib].d2s6[2 + iqs/16] = sum;
|
||||
|
||||
if (iqs % 64 != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float d = 1.0f / d_inv;
|
||||
|
||||
y[ib].d2s6[iqs/64] = d;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (iqs % 32 != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float d = 1.0f / d_inv;
|
||||
|
||||
if (ds_layout == MMQ_Q8_1_DS_LAYOUT_DS4) {
|
||||
y[ib].ds4[iqs/32] = make_half2(d, sum);
|
||||
} else {
|
||||
y[ib].d4[iqs/32] = d;
|
||||
}
|
||||
}
|
||||
|
||||
void quantize_row_q8_1_cuda(
|
||||
const float * x, const int32_t * ids, void * vy, const ggml_type type_src0,
|
||||
const int64_t ne00, const int64_t s01, const int64_t s02, const int64_t s03,
|
||||
const int64_t ne0, const int64_t ne1, const int64_t ne2, const int64_t ne3, cudaStream_t stream) {
|
||||
GGML_ASSERT(!ids);
|
||||
GGML_ASSERT(ne0 % QK8_1 == 0);
|
||||
|
||||
const uint3 ne2_fastdiv = init_fastdiv_values(ne2);
|
||||
|
||||
const int64_t block_num_x = (ne0 + CUDA_QUANTIZE_BLOCK_SIZE - 1) / CUDA_QUANTIZE_BLOCK_SIZE;
|
||||
const dim3 num_blocks(block_num_x, ne1, ne2*ne3);
|
||||
const dim3 block_size(CUDA_QUANTIZE_BLOCK_SIZE, 1, 1);
|
||||
quantize_q8_1<<<num_blocks, block_size, 0, stream>>>(x, vy, ne00, s01, s02, s03, ne0, ne1, ne2_fastdiv);
|
||||
GGML_UNUSED(type_src0);
|
||||
}
|
||||
|
||||
void quantize_mmq_q8_1_cuda(
|
||||
const float * x, const int32_t * ids, void * vy, const ggml_type type_src0,
|
||||
const int64_t ne00, const int64_t s01, const int64_t s02, const int64_t s03,
|
||||
const int64_t ne0, const int64_t ne1, const int64_t ne2, const int64_t ne3, cudaStream_t stream) {
|
||||
GGML_ASSERT(ne00 % 4 == 0);
|
||||
GGML_ASSERT(ne0 % (4*QK8_1) == 0);
|
||||
|
||||
// ne1 tends to assume the highest values, therefore use it as the "x" dimension of the CUDA grid:
|
||||
const int64_t block_num_y = (ne0 + 4*CUDA_QUANTIZE_BLOCK_SIZE_MMQ - 1) / (4*CUDA_QUANTIZE_BLOCK_SIZE_MMQ);
|
||||
const dim3 num_blocks(ne1, block_num_y, ne2*ne3);
|
||||
const dim3 block_size(CUDA_QUANTIZE_BLOCK_SIZE_MMQ, 1, 1);
|
||||
switch (mmq_get_q8_1_ds_layout(type_src0)) {
|
||||
case MMQ_Q8_1_DS_LAYOUT_D4:
|
||||
quantize_mmq_q8_1<MMQ_Q8_1_DS_LAYOUT_D4>
|
||||
<<<num_blocks, block_size, 0, stream>>>(x, ids, vy, ne00, s01, s02, s03, ne0, ne1, ne2);
|
||||
break;
|
||||
case MMQ_Q8_1_DS_LAYOUT_DS4:
|
||||
quantize_mmq_q8_1<MMQ_Q8_1_DS_LAYOUT_DS4>
|
||||
<<<num_blocks, block_size, 0, stream>>>(x, ids, vy, ne00, s01, s02, s03, ne0, ne1, ne2);
|
||||
break;
|
||||
case MMQ_Q8_1_DS_LAYOUT_D2S6:
|
||||
quantize_mmq_q8_1<MMQ_Q8_1_DS_LAYOUT_D2S6>
|
||||
<<<num_blocks, block_size, 0, stream>>>(x, ids, vy, ne00, s01, s02, s03, ne0, ne1, ne2);
|
||||
break;
|
||||
default:
|
||||
GGML_ABORT("fatal error");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void quantize_mmq_fp4_cuda(
|
||||
const float * x, const int32_t * ids, void * vy, const ggml_type type_src0,
|
||||
const int64_t ne00, const int64_t s01, const int64_t s02, const int64_t s03,
|
||||
const int64_t ne0, const int64_t ne1, const int64_t ne2, const int64_t ne3, cudaStream_t stream) {
|
||||
GGML_ASSERT(type_src0 == GGML_TYPE_MXFP4 || type_src0 == GGML_TYPE_NVFP4);
|
||||
GGML_ASSERT(ne0 > 0);
|
||||
|
||||
if (type_src0 == GGML_TYPE_NVFP4) {
|
||||
GGML_ASSERT(ne00 % QK_NVFP4 == 0);
|
||||
constexpr int nvfp4_block_size = 128;
|
||||
const int64_t block_num_y = (ne0 + QK_NVFP4_SUB * nvfp4_block_size - 1) / (QK_NVFP4_SUB * nvfp4_block_size);
|
||||
const dim3 block_size(nvfp4_block_size, 1, 1);
|
||||
const dim3 num_blocks(ne1, block_num_y, ne2 * ne3);
|
||||
quantize_mmq_nvfp4<<<num_blocks, block_size, 0, stream>>>(
|
||||
x, ids, vy, ne00, s01, s02, s03, ne0, ne1, ne2);
|
||||
} else {
|
||||
GGML_ASSERT(ne0 % (2 * QK_MXFP4) == 0);
|
||||
|
||||
constexpr int nwarps = 8;
|
||||
constexpr int vals_per_warp = 2 * QK_MXFP4;
|
||||
constexpr int vals_per_block = nwarps * vals_per_warp;
|
||||
|
||||
const int64_t block_num_y = (ne0 + vals_per_block - 1) / vals_per_block;
|
||||
const dim3 num_blocks(ne1, block_num_y, ne2 * ne3);
|
||||
const dim3 block_size(WARP_SIZE, nwarps, 1);
|
||||
|
||||
quantize_mmq_mxfp4<<<num_blocks, block_size, 0, stream>>>(x, ids, vy, ne00, s01, s02, s03, ne0, ne1, ne2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.cuh"
|
||||
#include "mmq.cuh"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#define CUDA_QUANTIZE_BLOCK_SIZE 256
|
||||
#define CUDA_QUANTIZE_BLOCK_SIZE_MMQ 128
|
||||
|
||||
static_assert(MATRIX_ROW_PADDING % CUDA_QUANTIZE_BLOCK_SIZE == 0, "Risk of out-of-bounds access.");
|
||||
static_assert(MATRIX_ROW_PADDING % (4*CUDA_QUANTIZE_BLOCK_SIZE_MMQ) == 0, "Risk of out-of-bounds access.");
|
||||
|
||||
typedef void (*quantize_cuda_t)(
|
||||
const float * x, const int32_t * ids, void * vy,
|
||||
ggml_type type_src0, int64_t ne00, int64_t s01, int64_t s02, int64_t s03,
|
||||
int64_t ne0, int64_t ne1, int64_t ne2, int64_t ne3, cudaStream_t stream);
|
||||
|
||||
void quantize_row_q8_1_cuda(
|
||||
const float * x, const int32_t * ids, void * vy,
|
||||
ggml_type type_src0, int64_t ne00, int64_t s01, int64_t s02, int64_t s03,
|
||||
int64_t ne0, int64_t ne1, int64_t ne2, int64_t ne3, cudaStream_t stream);
|
||||
|
||||
void quantize_mmq_q8_1_cuda(
|
||||
const float * x, const int32_t * ids, void * vy,
|
||||
ggml_type type_src0, int64_t ne00, int64_t s01, int64_t s02, int64_t s03,
|
||||
int64_t ne0, int64_t ne1, int64_t ne2, int64_t ne3, cudaStream_t stream);
|
||||
|
||||
void quantize_mmq_fp4_cuda(const float * x,
|
||||
const int32_t * ids,
|
||||
void * vy,
|
||||
ggml_type type_src0,
|
||||
int64_t ne00,
|
||||
int64_t s01,
|
||||
int64_t s02,
|
||||
int64_t s03,
|
||||
int64_t ne0,
|
||||
int64_t ne1,
|
||||
int64_t ne2,
|
||||
int64_t ne3,
|
||||
cudaStream_t 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; }
|
||||
@@ -0,0 +1,105 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// iq2_host_tables.h - host-accessible copies of the IQ2 lookup tables.
|
||||
// Regenerate from cuda/mmq/ggml-common.h whenever upstream changes them:
|
||||
//
|
||||
// awk '/GGML_TABLE_BEGIN.*iq2xxs_grid, 256/,/GGML_TABLE_END/' \
|
||||
// cuda/mmq/ggml-common.h | grep -v GGML_TABLE > iq2xxs_grid.lines
|
||||
//
|
||||
// In the vendored ggml-common.h the IQ2 tables are gated on
|
||||
// GGML_COMMON_IMPL_CUDA and emitted as static const __device__, so they
|
||||
// are not host-accessible. Reproducing them here as plain const arrays
|
||||
// gives the CPU parity reference something to look up against.
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace iq2_host {
|
||||
|
||||
// Bit positions for sign extraction (1, 2, 4, ..., 128).
|
||||
static const uint8_t kmask_iq2xs[8] = {1, 2, 4, 8, 16, 32, 64, 128};
|
||||
|
||||
// 128-entry sign lookup. Index is a 7-bit pattern of which positions
|
||||
// are negative; value is the 8-bit pattern with the 8th bit set to
|
||||
// maintain even parity (matches ggml-common.h:503).
|
||||
static const uint8_t ksigns_iq2xs[128] = {
|
||||
0, 129, 130, 3, 132, 5, 6, 135, 136, 9, 10, 139, 12, 141, 142, 15,
|
||||
144, 17, 18, 147, 20, 149, 150, 23, 24, 153, 154, 27, 156, 29, 30, 159,
|
||||
160, 33, 34, 163, 36, 165, 166, 39, 40, 169, 170, 43, 172, 45, 46, 175,
|
||||
48, 177, 178, 51, 180, 53, 54, 183, 184, 57, 58, 187, 60, 189, 190, 63,
|
||||
192, 65, 66, 195, 68, 197, 198, 71, 72, 201, 202, 75, 204, 77, 78, 207,
|
||||
80, 209, 210, 83, 212, 85, 86, 215, 216, 89, 90, 219, 92, 221, 222, 95,
|
||||
96, 225, 226, 99, 228, 101, 102, 231, 232, 105, 106, 235, 108, 237, 238, 111,
|
||||
240, 113, 114, 243, 116, 245, 246, 119, 120, 249, 250, 123, 252, 125, 126, 255,
|
||||
};
|
||||
|
||||
// 256-entry IQ2_XXS grid: each uint64 holds 8 uint8 quantized values.
|
||||
// Indexed by the 8-bit aux8[l] from the encoded super-block.
|
||||
// (matches ggml-common.h:550).
|
||||
static const uint64_t iq2xxs_grid[256] = {
|
||||
0x0808080808080808, 0x080808080808082b, 0x0808080808081919, 0x0808080808082b08,
|
||||
0x0808080808082b2b, 0x0808080808190819, 0x0808080808191908, 0x08080808082b0808,
|
||||
0x08080808082b082b, 0x08080808082b2b08, 0x08080808082b2b2b, 0x0808080819080819,
|
||||
0x0808080819081908, 0x0808080819190808, 0x0808080819192b08, 0x08080808192b0819,
|
||||
0x08080808192b1908, 0x080808082b080808, 0x080808082b08082b, 0x080808082b082b2b,
|
||||
0x080808082b2b082b, 0x0808081908080819, 0x0808081908081908, 0x0808081908190808,
|
||||
0x0808081908191919, 0x0808081919080808, 0x080808192b081908, 0x080808192b192b08,
|
||||
0x0808082b08080808, 0x0808082b0808082b, 0x0808082b082b082b, 0x0808082b2b08082b,
|
||||
0x0808190808080819, 0x0808190808081908, 0x0808190808190808, 0x08081908082b0819,
|
||||
0x08081908082b1908, 0x0808190819080808, 0x080819081908082b, 0x0808190819082b08,
|
||||
0x08081908192b0808, 0x080819082b080819, 0x080819082b081908, 0x080819082b190808,
|
||||
0x080819082b2b1908, 0x0808191908080808, 0x080819190808082b, 0x0808191908082b08,
|
||||
0x08081919082b0808, 0x080819191908192b, 0x08081919192b2b19, 0x080819192b080808,
|
||||
0x080819192b190819, 0x0808192b08082b19, 0x0808192b08190808, 0x0808192b19080808,
|
||||
0x0808192b2b081908, 0x0808192b2b2b1908, 0x08082b0808080808, 0x08082b0808081919,
|
||||
0x08082b0808082b08, 0x08082b0808191908, 0x08082b08082b2b08, 0x08082b0819080819,
|
||||
0x08082b0819081908, 0x08082b0819190808, 0x08082b081919082b, 0x08082b082b082b08,
|
||||
0x08082b1908081908, 0x08082b1919080808, 0x08082b2b0808082b, 0x08082b2b08191908,
|
||||
0x0819080808080819, 0x0819080808081908, 0x0819080808190808, 0x08190808082b0819,
|
||||
0x0819080819080808, 0x08190808192b0808, 0x081908082b081908, 0x081908082b190808,
|
||||
0x081908082b191919, 0x0819081908080808, 0x0819081908082b08, 0x08190819082b0808,
|
||||
0x0819081919190808, 0x0819081919192b2b, 0x081908192b080808, 0x0819082b082b1908,
|
||||
0x0819082b19081919, 0x0819190808080808, 0x0819190808082b08, 0x08191908082b0808,
|
||||
0x08191908082b1919, 0x0819190819082b19, 0x081919082b080808, 0x0819191908192b08,
|
||||
0x08191919192b082b, 0x0819192b08080808, 0x0819192b0819192b, 0x08192b0808080819,
|
||||
0x08192b0808081908, 0x08192b0808190808, 0x08192b0819080808, 0x08192b082b080819,
|
||||
0x08192b1908080808, 0x08192b1908081919, 0x08192b192b2b0808, 0x08192b2b19190819,
|
||||
0x082b080808080808, 0x082b08080808082b, 0x082b080808082b2b, 0x082b080819081908,
|
||||
0x082b0808192b0819, 0x082b08082b080808, 0x082b08082b08082b, 0x082b0819082b2b19,
|
||||
0x082b081919082b08, 0x082b082b08080808, 0x082b082b0808082b, 0x082b190808080819,
|
||||
0x082b190808081908, 0x082b190808190808, 0x082b190819080808, 0x082b19081919192b,
|
||||
0x082b191908080808, 0x082b191919080819, 0x082b1919192b1908, 0x082b192b2b190808,
|
||||
0x082b2b0808082b08, 0x082b2b08082b0808, 0x082b2b082b191908, 0x082b2b2b19081908,
|
||||
0x1908080808080819, 0x1908080808081908, 0x1908080808190808, 0x1908080808192b08,
|
||||
0x19080808082b0819, 0x19080808082b1908, 0x1908080819080808, 0x1908080819082b08,
|
||||
0x190808081919192b, 0x19080808192b0808, 0x190808082b080819, 0x190808082b081908,
|
||||
0x190808082b190808, 0x1908081908080808, 0x19080819082b0808, 0x19080819192b0819,
|
||||
0x190808192b080808, 0x190808192b081919, 0x1908082b08080819, 0x1908082b08190808,
|
||||
0x1908082b19082b08, 0x1908082b1919192b, 0x1908082b192b2b08, 0x1908190808080808,
|
||||
0x1908190808082b08, 0x19081908082b0808, 0x190819082b080808, 0x190819082b192b19,
|
||||
0x190819190819082b, 0x19081919082b1908, 0x1908192b08080808, 0x19082b0808080819,
|
||||
0x19082b0808081908, 0x19082b0808190808, 0x19082b0819080808, 0x19082b0819081919,
|
||||
0x19082b1908080808, 0x19082b1919192b08, 0x19082b19192b0819, 0x19082b192b08082b,
|
||||
0x19082b2b19081919, 0x19082b2b2b190808, 0x1919080808080808, 0x1919080808082b08,
|
||||
0x1919080808190819, 0x1919080808192b19, 0x19190808082b0808, 0x191908082b080808,
|
||||
0x191908082b082b08, 0x1919081908081908, 0x191908191908082b, 0x191908192b2b1908,
|
||||
0x1919082b2b190819, 0x191919082b190808, 0x191919082b19082b, 0x1919191908082b2b,
|
||||
0x1919192b08080819, 0x1919192b19191908, 0x19192b0808080808, 0x19192b0808190819,
|
||||
0x19192b0808192b19, 0x19192b08192b1908, 0x19192b1919080808, 0x19192b2b08082b08,
|
||||
0x192b080808081908, 0x192b080808190808, 0x192b080819080808, 0x192b0808192b2b08,
|
||||
0x192b081908080808, 0x192b081919191919, 0x192b082b08192b08, 0x192b082b192b0808,
|
||||
0x192b190808080808, 0x192b190808081919, 0x192b191908190808, 0x192b19190819082b,
|
||||
0x192b19192b081908, 0x192b2b081908082b, 0x2b08080808080808, 0x2b0808080808082b,
|
||||
0x2b08080808082b2b, 0x2b08080819080819, 0x2b0808082b08082b, 0x2b08081908081908,
|
||||
0x2b08081908192b08, 0x2b08081919080808, 0x2b08082b08190819, 0x2b08190808080819,
|
||||
0x2b08190808081908, 0x2b08190808190808, 0x2b08190808191919, 0x2b08190819080808,
|
||||
0x2b081908192b0808, 0x2b08191908080808, 0x2b0819191908192b, 0x2b0819192b191908,
|
||||
0x2b08192b08082b19, 0x2b08192b19080808, 0x2b08192b192b0808, 0x2b082b080808082b,
|
||||
0x2b082b1908081908, 0x2b082b2b08190819, 0x2b19080808081908, 0x2b19080808190808,
|
||||
0x2b190808082b1908, 0x2b19080819080808, 0x2b1908082b2b0819, 0x2b1908190819192b,
|
||||
0x2b1908192b080808, 0x2b19082b19081919, 0x2b19190808080808, 0x2b191908082b082b,
|
||||
0x2b19190819081908, 0x2b19191919190819, 0x2b192b082b080819, 0x2b192b19082b0808,
|
||||
0x2b2b08080808082b, 0x2b2b080819190808, 0x2b2b08082b081919, 0x2b2b081908082b19,
|
||||
0x2b2b082b08080808, 0x2b2b190808192b08, 0x2b2b2b0819190808, 0x2b2b2b1908081908,
|
||||
};
|
||||
|
||||
} // namespace iq2_host
|
||||
@@ -0,0 +1,773 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// proto_gemm_dense_q8_d2r.cu - dense Q8_0 decode-to-registers prototype.
|
||||
//
|
||||
// Targets the dense-q8 census shapes (cmdq8shape15 2026-07-09, N = chunk
|
||||
// tokens 4096) where production mul_mat_q<8,128,0> runs far under the
|
||||
// cuBLASLt-int8 ceiling at the same shapes:
|
||||
// sexp gate/up M= 2048 K=4096 mmq 65 TF vs Lt-int8 156
|
||||
// sexp down M= 4096 K=2048 mmq 24 TF vs Lt-int8 111
|
||||
// attn q_b M=32768 K=1024 mmq 56 TF vs Lt-int8 79-85
|
||||
// o_proj M= 4096 K=8192 mmq 96 TF vs Lt-int8 145
|
||||
//
|
||||
// Weight layout: byte-neutral SoA repack of raw Q8_0 (34 B/block of 32):
|
||||
// [half d[M*nb]] [int8 qs[M*K]] (d row-major [M][nb], qs row-major [M][K])
|
||||
// Total bytes == raw bytes. NOTE a "nicer" fully-tiled contiguous-stage
|
||||
// layout ([row_tile][k64][h][row][16B]) measured 27% SLOWER at identical
|
||||
// instruction counts: every col-tile CTA of a row group reads the same 8 KiB
|
||||
// block concurrently and the contiguous span lives on a few L2 slices (slice
|
||||
// camping). Row-major keeps each stage's 128 rows K bytes apart -> spread
|
||||
// across slices; its 2-way ldgsts store conflicts are the cheaper poison.
|
||||
// This is the REPLACING-artifact candidate (IQ2/Q2K precedent); raw consumers
|
||||
// fall back to the host mmap.
|
||||
//
|
||||
// Kernel: m128n64 CTA (8 warps), q8 activations = production block_q8_1_mmq
|
||||
// D4 layout (f32 scale per 32) double-buffered at k128; weights double-
|
||||
// buffered at k64 (128 rows x 64 B + 16 B row pad for ldmatrix bank rotation);
|
||||
// mma.m16n8k32.s8 with per-k32 float fold acc += (s32)C * d_w(row) * d_a(col).
|
||||
// Q8_0 has no mins: no bias terms, no fix staging (d4 read straight from the
|
||||
// q8 block header). Grid is 1-D with triton-style grouped supertiling
|
||||
// (group_m row-tiles hot in L2 while col tiles stream) - group_m is a runtime
|
||||
// arg, swept by the harness.
|
||||
//
|
||||
// Build on the GB10/Blackwell box after `make -j6 cuda-spark`:
|
||||
// ar rcs libds4mmq.a cuda/mmq/ds4_ggml_stubs.o cuda/mmq/ds4_mmq.o \
|
||||
// cuda/mmq/quantize.o cuda/mmq/mmid.o cuda/mmq/mmvq.o
|
||||
// /usr/local/cuda/bin/nvcc -O3 --use_fast_math -std=c++17 -arch=sm_121 \
|
||||
// -Icuda/mmq cuda/mmq/test/proto_gemm_dense_q8_d2r.cu libds4mmq.a \
|
||||
// -lcudart -lcublas -lcuda -o proto_gemm_dense_q8_d2r
|
||||
// argv: [reps=20] [warmup=5] [N=4096]
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "ds4_mmq.h"
|
||||
#include "quantize.cuh"
|
||||
|
||||
extern "C" int ds4_cuda_q8_fold_take_q81(const void *src, uint64_t in_dim, void *out) {
|
||||
(void)src;
|
||||
(void)in_dim;
|
||||
(void)out;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#include <cuda_fp16.h>
|
||||
#include <cuda_runtime.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#define CHECK(call) do { \
|
||||
cudaError_t err__ = (call); \
|
||||
if (err__ != cudaSuccess) { \
|
||||
fprintf(stderr, "CUDA error %s at %s:%d: %s\n", #call, __FILE__, __LINE__, \
|
||||
cudaGetErrorString(err__)); \
|
||||
exit(1); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kMTile = 128;
|
||||
#ifndef PROTO_Q8_NTILE
|
||||
#define PROTO_Q8_NTILE 128
|
||||
#endif
|
||||
constexpr int kNTile = PROTO_Q8_NTILE;
|
||||
// 16-warp CTA, one per SM: 8 row-warps x 2 col-warps. Same 16 warps/SM
|
||||
// residency as 2x8, but per-thread acc halves to 8 fragments (32 regs) --
|
||||
// the 2x8-warp NT128 shape spilled ~76 B and the spill reloads were the top
|
||||
// stall (long_sb 3.02, local_ld 2.65M).
|
||||
constexpr int kRowWarps = 8;
|
||||
constexpr int kColWarps = 2;
|
||||
constexpr int kWarps = kRowWarps * kColWarps;
|
||||
constexpr int kThreads = 32 * kWarps;
|
||||
// 3-stage rings: issues happen at the TOP of each iter for stage i+2 (a
|
||||
// buffer no in-flight reader touches), so the post-compute __syncthreads of
|
||||
// the 2-stage schedule disappears -- one barrier per k64. 69 KiB total needs
|
||||
// the dynamic-smem opt-in (1 CTA/SM is already forced by the 512-thread
|
||||
// register budget, so no occupancy cost).
|
||||
constexpr int kStages = 3; // q8 qs ring (k64 halves) + header ring (k128)
|
||||
constexpr int kWStages = 3; // weight ring, k64 granularity
|
||||
constexpr int kNFrag = kNTile / 8;
|
||||
constexpr int kWRowPad = 80; // 64 B payload + 16 B pad: 20-int ldmatrix stride rotates banks
|
||||
|
||||
// q8 staging is SPLIT so NT=128 fits under 48 KiB static smem: the 16 B
|
||||
// header (d4 scales) double-buffered at k128 cadence, the 128 B qs payload
|
||||
// staged as k64 halves (64 B rows, same padded geometry as the weight ring).
|
||||
constexpr int kQ8QsChunks = kNTile * 4; // cols x 4 16B-chunks per k64 half
|
||||
constexpr int kQ8QsTrips = (kQ8QsChunks + kThreads - 1) / kThreads;
|
||||
constexpr int kWQChunks = kMTile * 4; // 128 rows x 4 16B-chunks (64 B payload)
|
||||
constexpr int kWQTrips = kWQChunks / kThreads;
|
||||
|
||||
static_assert(kQ8QsTrips == 1, "unexpected q8 qs issue trip count");
|
||||
static_assert(kWQTrips == 1, "unexpected weight issue trip count");
|
||||
static_assert(kNTile == 128, "16-warp decomposition assumes NT=128");
|
||||
static_assert(kNTile <= kThreads, "q8 header prefetch assumes one trip");
|
||||
constexpr int kNFragPerWarp = kNFrag / kColWarps;
|
||||
|
||||
// Loop invariants live in plain (warp-uniform) registers: everything here
|
||||
// derives arithmetically from blockIdx + kernel args, so ptxas can promote
|
||||
// the math to the uniform register file. (The Q2K proto's shared invariant
|
||||
// table exists for its memory-sourced expert-worklist lookups - dense has
|
||||
// none, and routing these through volatile smem forced them into regular
|
||||
// registers, feeding the 128-reg wall.)
|
||||
struct DenseQ8Params {
|
||||
const char *wd_row0; // d plane at cta_row0 (row stride nb*2 = K/16)
|
||||
const char *wq_row0; // qs plane at cta_row0 (row stride K)
|
||||
const char *q8_tile; // q8 blocks at col_lo
|
||||
uint32_t q8_k128_stride_bytes;
|
||||
uint32_t wq_row_stride; // K bytes
|
||||
int k64_iters;
|
||||
int k128_iters;
|
||||
};
|
||||
|
||||
uint16_t sane_half(std::mt19937 &rng) {
|
||||
const uint16_t mant = (uint16_t)(rng() & 0x03FFu);
|
||||
const uint16_t sign = (uint16_t)((rng() & 1u) << 15);
|
||||
return (uint16_t)(sign | (14u << 10) | mant);
|
||||
}
|
||||
|
||||
float *device_random_floats(std::mt19937 &rng, size_t n) {
|
||||
std::vector<float> h(n);
|
||||
std::uniform_real_distribution<float> dist(-1.0f, 1.0f);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
h[i] = dist(rng);
|
||||
}
|
||||
float *d = nullptr;
|
||||
CHECK(cudaMalloc(&d, n * sizeof(float)));
|
||||
CHECK(cudaMemcpy(d, h.data(), n * sizeof(float), cudaMemcpyHostToDevice));
|
||||
return d;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
float best_time_ms(F &&fn, int warmup, int reps) {
|
||||
cudaEvent_t a, b;
|
||||
CHECK(cudaEventCreate(&a));
|
||||
CHECK(cudaEventCreate(&b));
|
||||
for (int i = 0; i < warmup; ++i) {
|
||||
fn();
|
||||
}
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
float best = std::numeric_limits<float>::infinity();
|
||||
for (int r = 0; r < reps; ++r) {
|
||||
CHECK(cudaEventRecord(a));
|
||||
fn();
|
||||
CHECK(cudaEventRecord(b));
|
||||
CHECK(cudaEventSynchronize(b));
|
||||
float ms = 0.0f;
|
||||
CHECK(cudaEventElapsedTime(&ms, a, b));
|
||||
best = std::min(best, ms);
|
||||
}
|
||||
CHECK(cudaEventDestroy(a));
|
||||
CHECK(cudaEventDestroy(b));
|
||||
return best;
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void zero_16B(void *dst) {
|
||||
*reinterpret_cast<int4 *>(dst) = make_int4(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void cp_async_16B(void *dst, const void *src, bool pred) {
|
||||
#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 800
|
||||
if (pred) {
|
||||
const unsigned smem = static_cast<unsigned>(__cvta_generic_to_shared(dst));
|
||||
asm volatile("cp.async.cg.shared.global [%0], [%1], 16;"
|
||||
:: "r"(smem), "l"(src));
|
||||
} else {
|
||||
zero_16B(dst);
|
||||
}
|
||||
#else
|
||||
if (pred) {
|
||||
*reinterpret_cast<int4 *>(dst) = *reinterpret_cast<const int4 *>(src);
|
||||
} else {
|
||||
zero_16B(dst);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void cp_async_4B(void *dst, const void *src, bool pred) {
|
||||
#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 800
|
||||
if (pred) {
|
||||
const unsigned smem = static_cast<unsigned>(__cvta_generic_to_shared(dst));
|
||||
asm volatile("cp.async.ca.shared.global [%0], [%1], 4;"
|
||||
:: "r"(smem), "l"(src));
|
||||
} else {
|
||||
*reinterpret_cast<uint32_t *>(dst) = 0u;
|
||||
}
|
||||
#else
|
||||
if (pred) {
|
||||
*reinterpret_cast<uint32_t *>(dst) = *reinterpret_cast<const uint32_t *>(src);
|
||||
} else {
|
||||
*reinterpret_cast<uint32_t *>(dst) = 0u;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void cp_async_commit() {
|
||||
#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 800
|
||||
asm volatile("cp.async.commit_group;");
|
||||
#endif
|
||||
}
|
||||
|
||||
template <int KeepGroups>
|
||||
__device__ __forceinline__ void cp_async_wait_group() {
|
||||
static_assert(KeepGroups >= 0 && KeepGroups <= 7, "bad cp.async wait_group depth");
|
||||
#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 800
|
||||
asm volatile("cp.async.wait_group %0;" :: "n"(KeepGroups));
|
||||
#endif
|
||||
}
|
||||
|
||||
// CTA is launched as dim3(32, kWarps): the ggml mma primitives
|
||||
// (load_ldmatrix, tile get_i/get_j) use threadIdx.x as the LANE.
|
||||
__device__ __forceinline__ int d2r_lane() {
|
||||
#if defined(__CUDA_ARCH__)
|
||||
return (int)threadIdx.x;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
__device__ __forceinline__ int d2r_warp() {
|
||||
#if defined(__CUDA_ARCH__)
|
||||
return (int)threadIdx.y;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
__device__ __forceinline__ int d2r_tid() {
|
||||
#if defined(__CUDA_ARCH__)
|
||||
return (int)(threadIdx.y * 32 + threadIdx.x);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
constexpr size_t kSmemQ8HBytes = (size_t)kStages * kNTile * 16; // headers
|
||||
constexpr size_t kSmemQ8QBytes = (size_t)kStages * kNTile * kWRowPad; // qs k64 halves
|
||||
constexpr size_t kSmemWQBytes = (size_t)kWStages * kMTile * kWRowPad;
|
||||
constexpr size_t kSmemWDBytes = (size_t)kWStages * kMTile * sizeof(uint32_t);
|
||||
constexpr size_t kSmemWQOff = 0;
|
||||
constexpr size_t kSmemQ8QOff = kSmemWQOff + kSmemWQBytes;
|
||||
constexpr size_t kSmemQ8HOff = kSmemQ8QOff + kSmemQ8QBytes;
|
||||
constexpr size_t kSmemWDOff = kSmemQ8HOff + kSmemQ8HBytes;
|
||||
constexpr size_t kSmemTotalBytes = kSmemWDOff + kSmemWDBytes;
|
||||
static_assert(kSmemTotalBytes <= 99ull * 1024ull, "dynamic shared memory exceeds sm_121 limit");
|
||||
|
||||
// ---- q8 activation prefetch, split staging ----
|
||||
// qs k64 halves: 128 cols x 64 B (bytes 16 + (k64&1)*64 .. of each col's
|
||||
// 144 B block), padded 80 B rows — same geometry as the weight ring.
|
||||
// headers: 128 cols x 16 B (the d4 scale header), k128 cadence.
|
||||
|
||||
__device__ __forceinline__ void issue_q8_qs_prefetch(
|
||||
int8_t (&s_q8q)[kStages][kNTile][kWRowPad],
|
||||
const DenseQ8Params &p,
|
||||
int k64_iter) {
|
||||
const bool pred = k64_iter < p.k64_iters;
|
||||
const char *base = p.q8_tile +
|
||||
(uint64_t)(k64_iter >> 1) * p.q8_k128_stride_bytes +
|
||||
16u + (uint64_t)(k64_iter & 1) * 64u;
|
||||
const int buf = k64_iter % kStages;
|
||||
#pragma unroll
|
||||
for (int trip = 0; trip < kQ8QsTrips; ++trip) {
|
||||
const int chunk = d2r_tid() + trip * kThreads;
|
||||
if (kQ8QsChunks % kThreads != 0 && chunk >= kQ8QsChunks) {
|
||||
break;
|
||||
}
|
||||
const int col = chunk >> 2;
|
||||
const int h = chunk & 3;
|
||||
void *dst = &s_q8q[buf][col][h * 16];
|
||||
const void *src = base + (uint64_t)col * sizeof(block_q8_1_mmq) + (uint64_t)h * 16u;
|
||||
cp_async_16B(dst, src, pred);
|
||||
}
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void issue_q8_hdr_prefetch(
|
||||
float (&s_q8h)[kStages][kNTile][4],
|
||||
const DenseQ8Params &p,
|
||||
int k128_iter) {
|
||||
if (d2r_tid() >= kNTile) {
|
||||
return;
|
||||
}
|
||||
const bool pred = k128_iter < p.k128_iters;
|
||||
const char *base = p.q8_tile +
|
||||
(uint64_t)k128_iter * p.q8_k128_stride_bytes;
|
||||
const int col = d2r_tid();
|
||||
void *dst = &s_q8h[k128_iter % kStages][col][0];
|
||||
const void *src = base + (uint64_t)col * sizeof(block_q8_1_mmq);
|
||||
cp_async_16B(dst, src, pred);
|
||||
}
|
||||
|
||||
// ---- weight prefetch (one k64 stage = 128 rows x 64 B qs + 128 x 4 B d) ----
|
||||
|
||||
__device__ __forceinline__ void issue_w_prefetch(
|
||||
int8_t (&s_wq)[kWStages][kMTile][kWRowPad],
|
||||
uint32_t (&s_wd)[kWStages][kMTile],
|
||||
const DenseQ8Params &p,
|
||||
int k64_iter) {
|
||||
const bool pred = k64_iter < p.k64_iters;
|
||||
const int wst = k64_iter % kWStages;
|
||||
// Row-major planes (see layout note at top: contiguous-stage tiling was
|
||||
// 27% slower from L2 slice camping).
|
||||
const char *wq = p.wq_row0 + (uint64_t)k64_iter * 64u;
|
||||
const char *wd = p.wd_row0 + (uint64_t)k64_iter * 4u;
|
||||
const uint32_t wq_row_stride = p.wq_row_stride;
|
||||
const uint32_t wd_row_stride = p.wq_row_stride >> 4; // nb*2 = K/16
|
||||
#pragma unroll
|
||||
for (int trip = 0; trip < kWQTrips; ++trip) {
|
||||
const int chunk = d2r_tid() + trip * kThreads;
|
||||
const int row = chunk >> 2;
|
||||
const int h = chunk & 3;
|
||||
void *dst = &s_wq[wst][row][h * 16];
|
||||
const void *src = wq + (uint64_t)row * wq_row_stride + (uint64_t)h * 16u;
|
||||
cp_async_16B(dst, src, pred);
|
||||
}
|
||||
if (d2r_tid() < kMTile) {
|
||||
const int row = d2r_tid();
|
||||
void *dst = &s_wd[wst][row];
|
||||
const void *src = wd + (uint64_t)row * wd_row_stride;
|
||||
cp_async_4B(dst, src, pred);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- mainloop ----
|
||||
|
||||
template <typename TileA, typename TileB, typename TileC>
|
||||
__device__ __forceinline__ void dense_q8_d2r_mainloop(
|
||||
float (&acc)[kNFragPerWarp][4],
|
||||
float (&s_q8h)[kStages][kNTile][4],
|
||||
int8_t (&s_q8q)[kStages][kNTile][kWRowPad],
|
||||
int8_t (&s_wq)[kWStages][kMTile][kWRowPad],
|
||||
uint32_t (&s_wd)[kWStages][kMTile],
|
||||
const DenseQ8Params &p) {
|
||||
static_assert(TileC::ne == 4, "expected m16n8 s32 accumulator fragment");
|
||||
const int k64_iters = p.k64_iters;
|
||||
const int lane = d2r_lane();
|
||||
const int group = lane >> 2;
|
||||
const int wrow = (d2r_warp() >> 1) * 16; // row-warp
|
||||
const int nf0 = (d2r_warp() & 1) * kNFragPerWarp; // col-warp half
|
||||
const int c0 = TileC::get_j(0);
|
||||
const int c1 = TileC::get_j(1);
|
||||
|
||||
// Prologue: stages 0 and 1 in flight as two groups. Loop iters run
|
||||
// wait -> barrier -> issue(i+2) -> compute: with 3 buffers the issue
|
||||
// never aliases the two live stages, so the 2-stage schedule's
|
||||
// post-compute barrier disappears (one barrier per k64).
|
||||
issue_w_prefetch(s_wq, s_wd, p, 0);
|
||||
issue_q8_qs_prefetch(s_q8q, p, 0);
|
||||
issue_q8_hdr_prefetch(s_q8h, p, 0);
|
||||
cp_async_commit();
|
||||
issue_w_prefetch(s_wq, s_wd, p, 1);
|
||||
issue_q8_qs_prefetch(s_q8q, p, 1);
|
||||
issue_q8_hdr_prefetch(s_q8h, p, 1);
|
||||
cp_async_commit();
|
||||
|
||||
for (int i = 0; i < k64_iters; ++i) {
|
||||
// wait -> barrier -> issue: the barrier both publishes stage i's
|
||||
// copies CTA-wide and proves every warp finished reading stage i-1,
|
||||
// whose buffer ((i-1)%3 == (i+2)%3) the issues below overwrite.
|
||||
cp_async_wait_group<1>();
|
||||
__syncthreads();
|
||||
issue_w_prefetch(s_wq, s_wd, p, i + 2);
|
||||
issue_q8_qs_prefetch(s_q8q, p, i + 2);
|
||||
if ((i & 1) == 0) {
|
||||
issue_q8_hdr_prefetch(s_q8h, p, (i >> 1) + 2);
|
||||
}
|
||||
cp_async_commit();
|
||||
|
||||
const int wst = i % kWStages;
|
||||
const int qbuf = i % kStages; // qs halves ride the k64 cadence
|
||||
const int hbuf = (i >> 1) % kStages; // headers ride the k128 cadence
|
||||
const uint32_t dw0_bits = s_wd[wst][wrow + group];
|
||||
const uint32_t dw1_bits = s_wd[wst][wrow + group + 8];
|
||||
const float2 dw0 = __half22float2(*reinterpret_cast<const half2 *>(&dw0_bits));
|
||||
const float2 dw1 = __half22float2(*reinterpret_cast<const half2 *>(&dw1_bits));
|
||||
|
||||
// t-phased: each A load overlaps the previous phase's B/MMA/fold tail
|
||||
// (fusing both A loads up front measured -27%: hard A dependency at the
|
||||
// k64 head + burstier MIO).
|
||||
#pragma unroll
|
||||
for (int t = 0; t < 2; ++t) {
|
||||
TileA A;
|
||||
ggml_cuda_mma::load_ldmatrix(
|
||||
A, reinterpret_cast<const int *>(&s_wq[wst][wrow][t * 32]),
|
||||
kWRowPad / (int)sizeof(int));
|
||||
const int tq8 = (i & 1) * 2 + t; // k32 index within the q8 k128 block
|
||||
const float dwr0 = t ? dw0.y : dw0.x;
|
||||
const float dwr1 = t ? dw1.y : dw1.x;
|
||||
#pragma unroll
|
||||
for (int nf = 0; nf < kNFragPerWarp; ++nf) {
|
||||
TileB B;
|
||||
TileC C;
|
||||
ggml_cuda_mma::load_ldmatrix(
|
||||
B, reinterpret_cast<const int *>(&s_q8q[qbuf][(nf0 + nf) * 8][t * 32]),
|
||||
kWRowPad / (int)sizeof(int));
|
||||
ggml_cuda_mma::mma(C, A, B);
|
||||
const float da0 = s_q8h[hbuf][(nf0 + nf) * 8 + c0][tq8];
|
||||
const float da1 = s_q8h[hbuf][(nf0 + nf) * 8 + c1][tq8];
|
||||
acc[nf][0] = fmaf((float)C.x[0], dwr0 * da0, acc[nf][0]);
|
||||
acc[nf][1] = fmaf((float)C.x[1], dwr0 * da1, acc[nf][1]);
|
||||
acc[nf][2] = fmaf((float)C.x[2], dwr1 * da0, acc[nf][2]);
|
||||
acc[nf][3] = fmaf((float)C.x[3], dwr1 * da1, acc[nf][3]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
__global__ __launch_bounds__(kThreads, 1)
|
||||
void dense_q8_d2r_kernel(const char * __restrict__ wd_plane,
|
||||
const char * __restrict__ wq_plane,
|
||||
const block_q8_1_mmq * __restrict__ q8,
|
||||
float * __restrict__ outf,
|
||||
__half * __restrict__ outh,
|
||||
int M, int N, int K, int group_m) {
|
||||
#if defined(TURING_MMA_AVAILABLE)
|
||||
using tile_A = ggml_cuda_mma::tile<16, 8, int>;
|
||||
using tile_B = ggml_cuda_mma::tile<8, 8, int>;
|
||||
using tile_C = ggml_cuda_mma::tile<16, 8, int>;
|
||||
|
||||
extern __shared__ __align__(16) char smem_dyn[];
|
||||
auto &s_wq = *reinterpret_cast<int8_t (*)[kWStages][kMTile][kWRowPad]>(smem_dyn + kSmemWQOff);
|
||||
auto &s_q8q = *reinterpret_cast<int8_t (*)[kStages][kNTile][kWRowPad]>(smem_dyn + kSmemQ8QOff);
|
||||
auto &s_q8h = *reinterpret_cast<float (*)[kStages][kNTile][4]>(smem_dyn + kSmemQ8HOff);
|
||||
auto &s_wd = *reinterpret_cast<uint32_t (*)[kWStages][kMTile]>(smem_dyn + kSmemWDOff);
|
||||
|
||||
// Triton-style grouped supertile order: group_m row-tiles stay L2-hot
|
||||
// while the col tiles stream past them.
|
||||
const int num_m = M / kMTile;
|
||||
const int num_n = (N + kNTile - 1) / kNTile;
|
||||
const int width = group_m * num_n;
|
||||
const int g = (int)blockIdx.x / width;
|
||||
const int rem = (int)blockIdx.x - g * width;
|
||||
int gsize = num_m - g * group_m;
|
||||
if (gsize > group_m) {
|
||||
gsize = group_m;
|
||||
}
|
||||
const int pid_m = g * group_m + rem % gsize;
|
||||
const int pid_n = rem / gsize;
|
||||
|
||||
const int cta_row0 = pid_m * kMTile;
|
||||
const int col_lo = pid_n * kNTile;
|
||||
|
||||
DenseQ8Params p;
|
||||
p.wd_row0 = wd_plane + (uint64_t)cta_row0 * (uint64_t)(K >> 4);
|
||||
p.wq_row0 = wq_plane + (uint64_t)cta_row0 * (uint64_t)K;
|
||||
p.q8_tile = reinterpret_cast<const char *>(q8) +
|
||||
(uint64_t)col_lo * sizeof(block_q8_1_mmq);
|
||||
p.q8_k128_stride_bytes = (uint32_t)((uint64_t)N * sizeof(block_q8_1_mmq));
|
||||
p.wq_row_stride = (uint32_t)K;
|
||||
p.k64_iters = K >> 6;
|
||||
p.k128_iters = K >> 7;
|
||||
|
||||
float acc[kNFragPerWarp][tile_C::ne] = {};
|
||||
|
||||
dense_q8_d2r_mainloop<tile_A, tile_B, tile_C>(acc, s_q8h, s_q8q, s_wq, s_wd, p);
|
||||
|
||||
const int out_col_lo = col_lo + (d2r_warp() & 1) * (kNFragPerWarp * 8);
|
||||
const int out_N = N;
|
||||
const int out_M = M;
|
||||
const int out_row0 = cta_row0 + ((d2r_warp() >> 1) << 4);
|
||||
float *outf_base = outf;
|
||||
__half *outh_base = outh;
|
||||
#pragma unroll
|
||||
for (int nf = 0; nf < kNFragPerWarp; ++nf) {
|
||||
const int col_frag0 = out_col_lo + nf * 8;
|
||||
#pragma unroll
|
||||
for (int l = 0; l < tile_C::ne; ++l) {
|
||||
const int row = out_row0 + tile_C::get_i(l);
|
||||
const int col = col_frag0 + tile_C::get_j(l);
|
||||
if (col < out_N) {
|
||||
const float v = isfinite(acc[nf][l]) ? acc[nf][l] : 0.0f;
|
||||
if (outh_base) {
|
||||
outh_base[(uint64_t)col * (uint64_t)out_M + (uint64_t)row] = __float2half(v);
|
||||
} else {
|
||||
outf_base[(uint64_t)col * (uint64_t)out_M + (uint64_t)row] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)wd_plane;
|
||||
(void)wq_plane;
|
||||
(void)q8;
|
||||
(void)outf;
|
||||
(void)outh;
|
||||
(void)M;
|
||||
(void)N;
|
||||
(void)K;
|
||||
(void)group_m;
|
||||
#endif
|
||||
}
|
||||
|
||||
int launch_dense_q8_d2r(const void *wd_plane, const void *wq_plane,
|
||||
const block_q8_1_mmq *q8, float *outf, __half *outh,
|
||||
int M, int N, int K, int group_m, cudaStream_t stream) {
|
||||
if (!wd_plane || !wq_plane || !q8 || (!outf && !outh) ||
|
||||
M <= 0 || (M & (kMTile - 1)) != 0 || K % 256 != 0 || N <= 0 || group_m <= 0) {
|
||||
fprintf(stderr, "launch_dense_q8_d2r: bad args M=%d N=%d K=%d group_m=%d\n",
|
||||
M, N, K, group_m);
|
||||
return -1;
|
||||
}
|
||||
const int num_m = M / kMTile;
|
||||
const int num_n = (N + kNTile - 1) / kNTile;
|
||||
static int smem_opted = 0;
|
||||
if (!smem_opted) {
|
||||
cudaFuncSetAttribute(dense_q8_d2r_kernel,
|
||||
cudaFuncAttributeMaxDynamicSharedMemorySize,
|
||||
(int)kSmemTotalBytes);
|
||||
smem_opted = 1;
|
||||
}
|
||||
const dim3 grid((unsigned)(num_m * num_n), 1, 1);
|
||||
const dim3 block(32, kWarps, 1);
|
||||
dense_q8_d2r_kernel<<<grid, block, kSmemTotalBytes, stream>>>(
|
||||
(const char *)wd_plane, (const char *)wq_plane, q8, outf, outh, M, N, K, group_m);
|
||||
const cudaError_t err = cudaGetLastError();
|
||||
if (err != cudaSuccess) {
|
||||
fprintf(stderr, "dense_q8_d2r_kernel launch failed: %s\n", cudaGetErrorString(err));
|
||||
return -2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void quantize_q8_mmq_dense(const float *x, block_q8_1_mmq *q8, int K, int N,
|
||||
cudaStream_t stream) {
|
||||
quantize_mmq_q8_1_cuda(
|
||||
x, /*ids=*/nullptr, (void *)q8,
|
||||
GGML_TYPE_Q8_0, /*ne00=*/K, /*s01=*/(int64_t)K, /*s02=*/0, /*s03=*/0,
|
||||
/*ne0=*/K, /*ne1=*/N, /*ne2=*/1, /*ne3=*/1, stream);
|
||||
CHECK(cudaGetLastError());
|
||||
}
|
||||
|
||||
struct ParityStats {
|
||||
double max_abs = 0.0;
|
||||
double max_rel_rms = 0.0;
|
||||
int max_row = -1;
|
||||
int max_col = -1;
|
||||
};
|
||||
|
||||
ParityStats check_parity(const float *ref, const float *got, int M, int N) {
|
||||
std::vector<double> ss((size_t)M, 0.0);
|
||||
for (int c = 0; c < N; ++c) {
|
||||
const float *rp = ref + (size_t)c * M;
|
||||
for (int r = 0; r < M; ++r) {
|
||||
ss[(size_t)r] += (double)rp[r] * (double)rp[r];
|
||||
}
|
||||
}
|
||||
ParityStats st;
|
||||
for (int c = 0; c < N; ++c) {
|
||||
const float *rp = ref + (size_t)c * M;
|
||||
const float *gp = got + (size_t)c * M;
|
||||
for (int r = 0; r < M; ++r) {
|
||||
const double abs_err = std::abs((double)gp[r] - (double)rp[r]);
|
||||
const double rms = std::sqrt(ss[(size_t)r] / std::max(1, N));
|
||||
const double rel = abs_err / std::max(1.0e-6, rms);
|
||||
if (abs_err > st.max_abs) {
|
||||
st.max_abs = abs_err;
|
||||
st.max_row = r;
|
||||
st.max_col = c;
|
||||
}
|
||||
st.max_rel_rms = std::max(st.max_rel_rms, rel);
|
||||
}
|
||||
}
|
||||
return st;
|
||||
}
|
||||
|
||||
void run_shape(std::mt19937 &rng, const char *label, int M, int K, int N,
|
||||
int warmup, int reps, bool do_parity) {
|
||||
const int nb = K / 32;
|
||||
const uint64_t raw_bytes = (uint64_t)M * nb * 34u;
|
||||
const uint64_t d_bytes = (uint64_t)M * nb * 2u;
|
||||
|
||||
// Raw Q8_0 blocks (34 B: half d + 32 int8) for the production reference,
|
||||
// and the byte-neutral SoA planes for the D2R kernel, from the same data.
|
||||
std::vector<uint8_t> h_raw(raw_bytes);
|
||||
std::vector<uint8_t> h_soa(raw_bytes);
|
||||
{
|
||||
// Row-major SoA planes: d [M][nb] halves, qs [M][K] int8.
|
||||
uint8_t *raw = h_raw.data();
|
||||
uint16_t *dp = reinterpret_cast<uint16_t *>(h_soa.data());
|
||||
int8_t *qp = reinterpret_cast<int8_t *>(h_soa.data() + d_bytes);
|
||||
for (int r = 0; r < M; ++r) {
|
||||
for (int b = 0; b < nb; ++b) {
|
||||
uint8_t *blk = raw + ((uint64_t)r * nb + b) * 34u;
|
||||
const uint16_t d = sane_half(rng);
|
||||
memcpy(blk, &d, 2);
|
||||
for (int j = 0; j < 32; ++j) {
|
||||
blk[2 + j] = (uint8_t)(rng() & 0xFFu);
|
||||
}
|
||||
dp[(uint64_t)r * nb + b] = d;
|
||||
memcpy(qp + (uint64_t)r * K + (uint64_t)b * 32u, blk + 2, 32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void *d_raw = nullptr;
|
||||
void *d_soa = nullptr;
|
||||
CHECK(cudaMalloc(&d_raw, raw_bytes));
|
||||
CHECK(cudaMalloc(&d_soa, raw_bytes));
|
||||
CHECK(cudaMemcpy(d_raw, h_raw.data(), raw_bytes, cudaMemcpyHostToDevice));
|
||||
CHECK(cudaMemcpy(d_soa, h_soa.data(), raw_bytes, cudaMemcpyHostToDevice));
|
||||
const char *d_wd = (const char *)d_soa;
|
||||
const char *d_wq = (const char *)d_soa + d_bytes;
|
||||
|
||||
float *d_x = device_random_floats(rng, (size_t)N * (size_t)K);
|
||||
|
||||
// +kNTile block slack so a guarded last col tile can read past col N-1
|
||||
// without faulting (values discarded by the epilogue guard).
|
||||
const size_t q8_blocks = (size_t)N * (size_t)(K / 128) + kNTile;
|
||||
block_q8_1_mmq *d_q8 = nullptr;
|
||||
CHECK(cudaMalloc(&d_q8, q8_blocks * sizeof(block_q8_1_mmq)));
|
||||
CHECK(cudaMemset(d_q8, 0, q8_blocks * sizeof(block_q8_1_mmq)));
|
||||
quantize_q8_mmq_dense(d_x, d_q8, K, N, 0);
|
||||
|
||||
float *d_ref = nullptr;
|
||||
float *d_new = nullptr;
|
||||
__half *d_newh = nullptr;
|
||||
CHECK(cudaMalloc(&d_ref, (size_t)M * N * sizeof(float)));
|
||||
CHECK(cudaMalloc(&d_new, (size_t)M * N * sizeof(float)));
|
||||
CHECK(cudaMalloc(&d_newh, (size_t)M * N * sizeof(__half)));
|
||||
|
||||
if (ds4_mmq_q8_0_dense(d_raw, d_x, d_ref, M, N, K, 0) != 0) {
|
||||
fprintf(stderr, "reference ds4_mmq_q8_0_dense failed M=%d K=%d\n", M, K);
|
||||
exit(1);
|
||||
}
|
||||
if (launch_dense_q8_d2r(d_wd, d_wq, d_q8, d_new, nullptr, M, N, K, 8, 0) != 0) {
|
||||
exit(1);
|
||||
}
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
|
||||
if (do_parity) {
|
||||
std::vector<float> ref((size_t)M * N), got((size_t)M * N);
|
||||
CHECK(cudaMemcpy(ref.data(), d_ref, ref.size() * sizeof(float), cudaMemcpyDeviceToHost));
|
||||
CHECK(cudaMemcpy(got.data(), d_new, got.size() * sizeof(float), cudaMemcpyDeviceToHost));
|
||||
const ParityStats st = check_parity(ref.data(), got.data(), M, N);
|
||||
const bool pass = st.max_rel_rms <= 2.0e-3;
|
||||
printf(" parity %-8s M=%5d K=%4d N=%d: max_abs=%.6g at col=%d row=%d max_abs/rms(row)=%.6g %s\n",
|
||||
label, M, K, N, st.max_abs, st.max_col, st.max_row, st.max_rel_rms,
|
||||
pass ? "PASS" : "FAIL");
|
||||
if (!pass && getenv("PROTO_Q8_DEBUG")) {
|
||||
// Error structure: per (row-tile, col-tile) fraction of bad elements,
|
||||
// plus per row%16 / col%8 / warp-row histograms of bad positions.
|
||||
const int ntm = M / 128, ntn = (N + 63) / 64;
|
||||
size_t bad_total = 0;
|
||||
std::vector<size_t> bad_rowmod(16, 0), bad_colmod(8, 0), bad_warp(8, 0);
|
||||
std::vector<size_t> tile_bad((size_t)ntm * ntn, 0);
|
||||
for (int c = 0; c < N; ++c) {
|
||||
const float *rp = ref.data() + (size_t)c * M;
|
||||
const float *gp = got.data() + (size_t)c * M;
|
||||
for (int r = 0; r < M; ++r) {
|
||||
const double e = std::abs((double)gp[r] - (double)rp[r]);
|
||||
const double mag = std::max(1.0, std::abs((double)rp[r]));
|
||||
if (e / mag > 1.0e-2) {
|
||||
++bad_total;
|
||||
++bad_rowmod[r & 15];
|
||||
++bad_colmod[c & 7];
|
||||
++bad_warp[(r >> 4) & 7];
|
||||
++tile_bad[(size_t)(r / 128) * ntn + (c / 64)];
|
||||
}
|
||||
}
|
||||
}
|
||||
printf(" bad(rel>1e-2) %zu / %zu\n", bad_total, (size_t)M * N);
|
||||
printf(" by row%%16:"); for (int i = 0; i < 16; ++i) printf(" %zu", bad_rowmod[i]); printf("\n");
|
||||
printf(" by col%%8: "); for (int i = 0; i < 8; ++i) printf(" %zu", bad_colmod[i]); printf("\n");
|
||||
printf(" by warp(row/16%%8):"); for (int i = 0; i < 8; ++i) printf(" %zu", bad_warp[i]); printf("\n");
|
||||
printf(" tile map (rows=row-tile, cols=col-tile, %% bad, first 8x16):\n");
|
||||
for (int tm = 0; tm < std::min(ntm, 8); ++tm) {
|
||||
printf(" ");
|
||||
for (int tn = 0; tn < std::min(ntn, 16); ++tn) {
|
||||
printf("%3zu", tile_bad[(size_t)tm * ntn + tn] * 100 / (128 * 64));
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
// A few sample mismatches with ratios (scale bugs show as clean ratios).
|
||||
int shown = 0;
|
||||
for (int c = 0; c < N && shown < 6; ++c) {
|
||||
for (int r = 0; r < M && shown < 6; ++r) {
|
||||
const float rv = ref[(size_t)c * M + r], gv = got[(size_t)c * M + r];
|
||||
if (std::abs(gv - rv) / std::max(1.0f, std::abs(rv)) > 1.0e-2) {
|
||||
printf(" sample r=%d c=%d ref=%.6g got=%.6g got/ref=%.4f\n",
|
||||
r, c, rv, gv, rv != 0.0f ? gv / rv : 0.0f);
|
||||
++shown;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!pass) {
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
const double flop = 2.0 * (double)M * (double)K * (double)N;
|
||||
const float ref_ms = best_time_ms([&] {
|
||||
ds4_mmq_q8_0_dense(d_raw, d_x, d_ref, M, N, K, 0);
|
||||
}, warmup, reps);
|
||||
|
||||
printf(" %-8s M=%5d K=%4d N=%d mmq-ref %7.3f ms %5.1f TF\n",
|
||||
label, M, K, N, ref_ms, flop / (ref_ms * 1.0e-3) / 1.0e12);
|
||||
|
||||
float best_kernel_ms = std::numeric_limits<float>::infinity();
|
||||
int best_g = 0;
|
||||
const int groups[] = {1, 4, 8, 16};
|
||||
for (int gi = 0; gi < 4; ++gi) {
|
||||
const int g = groups[gi];
|
||||
const float ms = best_time_ms([&] {
|
||||
launch_dense_q8_d2r(d_wd, d_wq, d_q8, d_new, nullptr, M, N, K, g, 0);
|
||||
}, warmup, reps);
|
||||
printf(" d2r f32-out group_m=%2d %7.3f ms %5.1f TF\n",
|
||||
g, ms, flop / (ms * 1.0e-3) / 1.0e12);
|
||||
if (ms < best_kernel_ms) {
|
||||
best_kernel_ms = ms;
|
||||
best_g = g;
|
||||
}
|
||||
}
|
||||
|
||||
const float f16_ms = best_time_ms([&] {
|
||||
launch_dense_q8_d2r(d_wd, d_wq, d_q8, nullptr, d_newh, M, N, K, best_g, 0);
|
||||
}, warmup, reps);
|
||||
const float full_ms = best_time_ms([&] {
|
||||
quantize_q8_mmq_dense(d_x, d_q8, K, N, 0);
|
||||
launch_dense_q8_d2r(d_wd, d_wq, d_q8, d_new, nullptr, M, N, K, best_g, 0);
|
||||
}, warmup, reps);
|
||||
|
||||
printf(" d2r BEST group_m=%d: f32-out %7.3f ms %5.1f TF | f16-out %7.3f ms %5.1f TF | +q8 %7.3f ms %5.1f TF | vs mmq %.2fx\n",
|
||||
best_g,
|
||||
best_kernel_ms, flop / (best_kernel_ms * 1.0e-3) / 1.0e12,
|
||||
f16_ms, flop / (f16_ms * 1.0e-3) / 1.0e12,
|
||||
full_ms, flop / (full_ms * 1.0e-3) / 1.0e12,
|
||||
ref_ms / best_kernel_ms);
|
||||
|
||||
CHECK(cudaFree(d_raw));
|
||||
CHECK(cudaFree(d_soa));
|
||||
CHECK(cudaFree(d_x));
|
||||
CHECK(cudaFree(d_q8));
|
||||
CHECK(cudaFree(d_ref));
|
||||
CHECK(cudaFree(d_new));
|
||||
CHECK(cudaFree(d_newh));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const int reps = (argc > 1) ? atoi(argv[1]) : 20;
|
||||
const int warmup = (argc > 2) ? atoi(argv[2]) : 5;
|
||||
const int N = (argc > 3) ? atoi(argv[3]) : 4096;
|
||||
|
||||
printf("### proto_gemm_dense_q8_d2r reps=%d warmup=%d N=%d ###\n", reps, warmup, N);
|
||||
printf("### CTA m%dn%d, warps=%d, q8 x%d stages, weights x%d stages, dyn smem %zu B ###\n",
|
||||
kMTile, kNTile, kWarps, kStages, kWStages, kSmemTotalBytes);
|
||||
printf("### Lt-int8 ceilings at these shapes: gate/up 156, down 111, q_b 79-85, o_proj 145 TF ###\n");
|
||||
|
||||
std::mt19937 rng(0xD84D8021u + 13u * (unsigned)N);
|
||||
|
||||
run_shape(rng, "gate/up", 2048, 4096, N, warmup, reps, true);
|
||||
run_shape(rng, "down", 4096, 2048, N, warmup, reps, true);
|
||||
run_shape(rng, "q_b", 32768, 1024, N, warmup, reps, false);
|
||||
run_shape(rng, "o_proj", 4096, 8192, N, warmup, reps, false);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// proto_gemm_lib.cu - NVIDIA-library dense GEMM ceiling at the exact MoE-leg
|
||||
// shapes (cuBLASLt heuristics, up to 16 algos, best-of; TN col-major):
|
||||
// down-shape: m=4096 n=24576 k=2048 (== one W4096 down launch's total FLOP)
|
||||
// gateup-shape: m=2048 n=24576 k=4096 (== one gate or up GEMM of the pair)
|
||||
// Kinds: int8->int32 (COMPUTE_32I) | f16->f16 @ f32-acc (COMPUTE_32F) |
|
||||
// f16->f16 @ f16-acc (COMPUTE_16F).
|
||||
//
|
||||
// This is "what NVIDIA's best dense kernel does on this silicon at our shape" —
|
||||
// the practical ceiling any MoE grouped-GEMM rewrite chases (grouping + quant
|
||||
// decode overhead comes on top of it).
|
||||
//
|
||||
// Build: /usr/local/cuda/bin/nvcc -O3 -std=c++17 -arch=sm_121 \
|
||||
// cuda/mmq/test/proto_gemm_lib.cu -o proto_gemm_lib -lcublasLt
|
||||
// argv: [reps=10]
|
||||
|
||||
#include <cublasLt.h>
|
||||
#include <cuda_fp16.h>
|
||||
#include <cuda_runtime.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#define CHECK(call) do { cudaError_t e_=(call); if(e_!=cudaSuccess){ \
|
||||
fprintf(stderr,"CUDA %s @%d: %s\n",#call,__LINE__,cudaGetErrorString(e_)); exit(1);} } while(0)
|
||||
#define LT(call) do { cublasStatus_t s_=(call); if(s_!=CUBLAS_STATUS_SUCCESS){ \
|
||||
fprintf(stderr,"cublasLt %s @%d: status %d\n",#call,__LINE__,(int)s_); goto done; } } while(0)
|
||||
|
||||
namespace {
|
||||
|
||||
__global__ void fillk(unsigned *p, size_t n, unsigned seed) {
|
||||
for (size_t i = blockIdx.x * (size_t)blockDim.x + threadIdx.x; i < n;
|
||||
i += (size_t)gridDim.x * blockDim.x)
|
||||
p[i] = ((unsigned)i * 2654435761u + seed) & 0x3BFF3BFFu; // finite small f16 pairs; valid int8 bytes
|
||||
}
|
||||
|
||||
void bench_case(cublasLtHandle_t lt, void *dA, void *dB, void *dC, void *ws,
|
||||
size_t wsSize, int m, int n, int k, int kind, int reps,
|
||||
const char *label) {
|
||||
const cudaDataType ab = (kind == 0) ? CUDA_R_8I : CUDA_R_16F;
|
||||
const cudaDataType cd = (kind == 0) ? CUDA_R_32I : CUDA_R_16F;
|
||||
const cublasComputeType_t ct = (kind == 0) ? CUBLAS_COMPUTE_32I
|
||||
: (kind == 1) ? CUBLAS_COMPUTE_32F : CUBLAS_COMPUTE_16F;
|
||||
const cudaDataType st = (kind == 0) ? CUDA_R_32I
|
||||
: (kind == 1) ? CUDA_R_32F : CUDA_R_16F;
|
||||
cublasLtMatmulDesc_t op = nullptr;
|
||||
cublasLtMatrixLayout_t La = nullptr, Lb = nullptr, Lc = nullptr;
|
||||
cublasLtMatmulPreference_t pref = nullptr;
|
||||
cublasLtMatmulHeuristicResult_t res[16];
|
||||
int nres = 0;
|
||||
const cublasOperation_t ta = CUBLAS_OP_T, tb = CUBLAS_OP_N;
|
||||
const int32_t ai = 1, bi = 0; const float af = 1.f, bf = 0.f;
|
||||
const __half ah = __float2half(1.f), bh = __float2half(0.f);
|
||||
const void *alpha = (kind == 0) ? (const void *)&ai : (kind == 1) ? (const void *)&af : (const void *)&ah;
|
||||
const void *beta = (kind == 0) ? (const void *)&bi : (kind == 1) ? (const void *)&bf : (const void *)&bh;
|
||||
float best = 1e30f; int besti = -1;
|
||||
|
||||
LT(cublasLtMatmulDescCreate(&op, ct, st));
|
||||
LT(cublasLtMatmulDescSetAttribute(op, CUBLASLT_MATMUL_DESC_TRANSA, &ta, sizeof(ta)));
|
||||
LT(cublasLtMatmulDescSetAttribute(op, CUBLASLT_MATMUL_DESC_TRANSB, &tb, sizeof(tb)));
|
||||
LT(cublasLtMatrixLayoutCreate(&La, ab, k, m, k)); // stored kxm; opA=T -> mxk
|
||||
LT(cublasLtMatrixLayoutCreate(&Lb, ab, k, n, k));
|
||||
LT(cublasLtMatrixLayoutCreate(&Lc, cd, m, n, m));
|
||||
LT(cublasLtMatmulPreferenceCreate(&pref));
|
||||
LT(cublasLtMatmulPreferenceSetAttribute(pref, CUBLASLT_MATMUL_PREF_MAX_WORKSPACE_BYTES,
|
||||
&wsSize, sizeof(wsSize)));
|
||||
LT(cublasLtMatmulAlgoGetHeuristic(lt, op, La, Lb, Lc, Lc, pref, 16, res, &nres));
|
||||
if (!nres) { printf(" %-14s m=%5d n=%5d k=%5d: NOALGO\n", label, m, n, k); goto done; }
|
||||
|
||||
for (int i = 0; i < nres; ++i) {
|
||||
if (res[i].state != CUBLAS_STATUS_SUCCESS) continue;
|
||||
if (cublasLtMatmul(lt, op, alpha, dA, La, dB, Lb, beta, dC, Lc, dC, Lc,
|
||||
&res[i].algo, ws, wsSize, 0) != CUBLAS_STATUS_SUCCESS) continue;
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
cudaEvent_t e0, e1; CHECK(cudaEventCreate(&e0)); CHECK(cudaEventCreate(&e1));
|
||||
CHECK(cudaEventRecord(e0));
|
||||
for (int r = 0; r < reps; ++r)
|
||||
cublasLtMatmul(lt, op, alpha, dA, La, dB, Lb, beta, dC, Lc, dC, Lc,
|
||||
&res[i].algo, ws, wsSize, 0);
|
||||
CHECK(cudaEventRecord(e1)); CHECK(cudaEventSynchronize(e1));
|
||||
float ms = 0; CHECK(cudaEventElapsedTime(&ms, e0, e1)); ms /= (float)reps;
|
||||
if (ms < best) { best = ms; besti = i; }
|
||||
CHECK(cudaEventDestroy(e0)); CHECK(cudaEventDestroy(e1));
|
||||
}
|
||||
if (besti < 0) { printf(" %-14s m=%5d n=%5d k=%5d: ALL-ALGOS-FAILED (n=%d)\n", label, m, n, k, nres); goto done; }
|
||||
printf(" %-14s m=%5d n=%5d k=%5d: best %7.3f ms %6.1f TFLOPS (algo %d of %d)\n",
|
||||
label, m, n, k, best, 2.0 * m * (double)n * k / (best * 1e-3) / 1e12, besti, nres);
|
||||
done:
|
||||
if (pref) cublasLtMatmulPreferenceDestroy(pref);
|
||||
if (Lc) cublasLtMatrixLayoutDestroy(Lc);
|
||||
if (Lb) cublasLtMatrixLayoutDestroy(Lb);
|
||||
if (La) cublasLtMatrixLayoutDestroy(La);
|
||||
if (op) cublasLtMatmulDescDestroy(op);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const int reps = (argc > 1) ? atoi(argv[1]) : 10;
|
||||
printf("### proto_gemm_lib cublasLt %zu reps=%d ###\n", cublasLtGetVersion(), reps);
|
||||
cublasLtHandle_t lt; if (cublasLtCreate(<) != CUBLAS_STATUS_SUCCESS) { fprintf(stderr, "lt create fail\n"); return 1; }
|
||||
const size_t maxA = (size_t)32768 * 8192 * 2; // covers kxm all shapes, f16
|
||||
const size_t maxB = (size_t)8192 * 24576 * 2;
|
||||
const size_t maxC = (size_t)32768 * 24576 * 4;
|
||||
const size_t wsSize = 256u << 20;
|
||||
void *dA, *dB, *dC, *ws;
|
||||
CHECK(cudaMalloc(&dA, maxA)); CHECK(cudaMalloc(&dB, maxB));
|
||||
CHECK(cudaMalloc(&dC, maxC)); CHECK(cudaMalloc(&ws, wsSize));
|
||||
fillk<<<512, 256>>>((unsigned *)dA, maxA / 4, 0xA5A5u);
|
||||
fillk<<<512, 256>>>((unsigned *)dB, maxB / 4, 0x5A5Au);
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
|
||||
/* MoE-equiv shapes + the dense-q8 census shapes (cmdq8shape15 2026-07-09):
|
||||
* q_b [32768x1024], o_proj [4096x8192], sexp gate/up [2048x4096],
|
||||
* sexp down [4096x2048], all at N=4096 chunk tokens. */
|
||||
const int shapes[][3] = { {4096, 24576, 2048}, {2048, 24576, 4096},
|
||||
{32768, 4096, 1024}, {4096, 4096, 8192},
|
||||
{2048, 4096, 4096}, {4096, 4096, 2048} };
|
||||
const char *kn[] = { "int8.s32acc", "f16.f32acc", "f16.f16acc" };
|
||||
for (const auto &s : shapes) {
|
||||
printf("--- shape m=%d n=%d k=%d ---\n", s[0], s[1], s[2]);
|
||||
for (int kind = 0; kind < 3; ++kind)
|
||||
bench_case(lt, dA, dB, dC, ws, wsSize, s[0], s[1], s[2], kind, reps, kn[kind]);
|
||||
}
|
||||
cublasLtDestroy(lt);
|
||||
CHECK(cudaFree(dA)); CHECK(cudaFree(dB)); CHECK(cudaFree(dC)); CHECK(cudaFree(ws));
|
||||
return 0;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,262 @@
|
||||
// proto_iq2_aligned.cu — M1-MoE Inc1 prototype (megakernel program).
|
||||
//
|
||||
// HYPOTHESIS: the IQ2_XXS decode matvec runs at ~142 GB/s (vs the ~200
|
||||
// engine ceiling) because block_iq2_xxs is 66 bytes, so the code stream is
|
||||
// only 2-byte aligned and every 32-bit weight word costs two 16-bit loads
|
||||
// (get_int_b2). Repacking into an aligned SoA layout (d[] halves separate,
|
||||
// qs[] 64B-aligned per block) allows full-width aligned loads at identical
|
||||
// byte count.
|
||||
//
|
||||
// A/B at the exact production decode shape (per layer, per token):
|
||||
// gate/up experts: M=2048 rows, K=4096, top-6 of n_experts, n_tokens=1
|
||||
// baseline = ds4_mmq_iq2_xxs_moe_vec (the production vec-tier entry)
|
||||
// aligned = warp-per-row kernel, same dp4a/grid/sign math, SoA weights
|
||||
//
|
||||
// Correctness: aligned vs baseline output, tolerance (float accum order
|
||||
// differs across blocks; per-pair integer math is identical).
|
||||
//
|
||||
// Build (box):
|
||||
// nvcc -O3 --use_fast_math -std=c++17 -arch=sm_121 -I.. \
|
||||
// proto_iq2_aligned.cu ../ds4_mmq.o -lcudart -lcuda -o proto_iq2_aligned
|
||||
|
||||
#include "ds4_mmq.h"
|
||||
|
||||
#define GGML_COMMON_DECL_CUDA
|
||||
#define GGML_COMMON_IMPL_CUDA
|
||||
#include "../ggml-common.h"
|
||||
|
||||
#include <cuda_runtime.h>
|
||||
#include <cuda_fp16.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#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)
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int QKK = 256; // values per IQ2_XXS block
|
||||
constexpr int BLOCK_BYTES = 66; // sizeof(block_iq2_xxs) = 2 + 32*2
|
||||
constexpr int PAIRS_PER_BLOCK = 8; // 8 (q2,aux32) uint32 pairs, 32 values each
|
||||
|
||||
// ---------------------------------------------------------------- host q8_1
|
||||
struct q8_1_block { __half2 ds; int8_t qs[32]; }; // mirrors block_q8_1
|
||||
|
||||
static void quantize_q8_1_host(const float *x, q8_1_block *out, int k) {
|
||||
for (int b = 0; b < k / 32; b++) {
|
||||
float amax = 0.0f;
|
||||
for (int i = 0; i < 32; i++) amax = fmaxf(amax, fabsf(x[b * 32 + i]));
|
||||
const float d = amax / 127.0f;
|
||||
const float id = d != 0.0f ? 1.0f / d : 0.0f;
|
||||
int sum = 0;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
int q = (int)roundf(x[b * 32 + i] * id);
|
||||
if (q > 127) q = 127;
|
||||
if (q < -127) q = -127;
|
||||
out[b].qs[i] = (int8_t)q;
|
||||
sum += q;
|
||||
}
|
||||
out[b].ds = __floats2half2_rn(d, d * (float)sum);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ device kernel
|
||||
__device__ __forceinline__ uint32_t proto_unpack_ksigns(const uint8_t v) {
|
||||
const uint32_t p = __popc(v) & 1;
|
||||
const uint32_t s = v ^ p << 7;
|
||||
return s * 0x01010101;
|
||||
}
|
||||
|
||||
__device__ __forceinline__ int proto_dp4a(const int a, const int b, int c) {
|
||||
return __dp4a(a, b, c);
|
||||
}
|
||||
|
||||
// Aligned SoA weights: for (expert e, row r) with nb blocks per row:
|
||||
// dq[(e*M + r)*nb + b] : __half block scale
|
||||
// qs[((e*M + r)*nb + b) * 8 + p] : uint2 (q2, aux32) pair p
|
||||
// qs base is 16B-aligned; each block's 8 uint2 = 64B, 64B-aligned stride.
|
||||
__global__ void iq2_aligned_moe_vec_kernel(
|
||||
float *out, // [n_slots, M]
|
||||
const uint2 *qs, // aligned code pairs
|
||||
const __half *dq, // block scales
|
||||
const q8_1_block *x8, // [K/32] quantized activation (shared by slots)
|
||||
const int32_t *ids, // [n_slots] expert ids
|
||||
const float *slot_w, // [n_slots] router weights (1.0 in proto)
|
||||
int M,
|
||||
int nb) // blocks per row = K/256
|
||||
{
|
||||
const int row = blockIdx.x;
|
||||
const int slot = blockIdx.y;
|
||||
const int lane = threadIdx.x; // 32 lanes: lane covers (block b, pair p)
|
||||
const long long rbase = ((long long)ids[slot] * M + row) * nb;
|
||||
|
||||
float acc = 0.0f;
|
||||
// 32 lanes cover 4 blocks x 8 pairs per pass.
|
||||
for (int b0 = 0; b0 < nb; b0 += 4) {
|
||||
const int b = b0 + (lane >> 3);
|
||||
const int p = lane & 7;
|
||||
const uint2 cw = qs[(rbase + b) * PAIRS_PER_BLOCK + p]; // aligned 8B
|
||||
const uint32_t q2 = cw.x, aux32 = cw.y;
|
||||
const uint8_t *aux8 = (const uint8_t *)&q2;
|
||||
|
||||
int sumi = 0;
|
||||
const int q8i = (b * QKK + p * 32) / 32; // q8_1 block covering these 32 values
|
||||
const int *u = (const int *)x8[q8i].qs;
|
||||
#pragma unroll
|
||||
for (int k0 = 0; k0 < 8; k0 += 2) {
|
||||
const uint2 grid_pos = ((const uint2 *)iq2xxs_grid)[aux8[k0 / 2]];
|
||||
const uint32_t signs = proto_unpack_ksigns(aux32 >> (7 * k0 / 2));
|
||||
|
||||
const int signs0 = __vcmpne4(signs & 0x08040201, 0);
|
||||
const int grid0 = __vsub4(grid_pos.x ^ signs0, signs0);
|
||||
sumi = proto_dp4a(grid0, u[k0 + 0], sumi);
|
||||
|
||||
const int signs1 = __vcmpne4(signs & 0x80402010, 0);
|
||||
const int grid1 = __vsub4(grid_pos.y ^ signs1, signs1);
|
||||
sumi = proto_dp4a(grid1, u[k0 + 1], sumi);
|
||||
}
|
||||
const int ls = aux32 >> 27 | 1;
|
||||
sumi = sumi * ls / 8;
|
||||
const float d = __half2float(dq[rbase + b]) * __low2float(x8[q8i].ds);
|
||||
acc += d * (float)sumi;
|
||||
}
|
||||
#pragma unroll
|
||||
for (int off = 16; off > 0; off >>= 1)
|
||||
acc += __shfl_down_sync(0xffffffffu, acc, off);
|
||||
if (lane == 0) out[(long long)slot * M + row] = acc * slot_w[slot];
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
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 nb = K / QKK; // 16 blocks/row
|
||||
const long long row_bytes = (long long)nb * BLOCK_BYTES; // 1056
|
||||
const double slot_gb = (double)n_slots * M * row_bytes / 1e9;
|
||||
|
||||
std::mt19937 rng(argc > 1 ? (uint32_t)atoi(argv[1]) : 1234u);
|
||||
|
||||
// Random IQ2_XXS blocks (same trick as test_mmq_parity: random bytes are
|
||||
// valid IQ2_XXS content; both kernels see identical bits).
|
||||
std::vector<uint8_t> W((size_t)n_experts * M * row_bytes);
|
||||
for (auto &b : W) b = (uint8_t)(rng() & 0xff);
|
||||
// keep block d halves sane (avoid inf/nan): overwrite each block's d
|
||||
for (long long blk = 0; blk < (long long)n_experts * M * nb; blk++) {
|
||||
uint16_t h = (uint16_t)(0x2c00 | (rng() & 0xff)); // ~0.0625..0.1 range fp16
|
||||
memcpy(&W[blk * BLOCK_BYTES], &h, 2);
|
||||
}
|
||||
|
||||
std::vector<float> X(K);
|
||||
for (auto &v : X) v = ((float)(rng() % 2000) - 1000.0f) / 500.0f;
|
||||
std::vector<q8_1_block> X8(K / 32);
|
||||
quantize_q8_1_host(X.data(), X8.data(), K);
|
||||
|
||||
std::vector<int32_t> ids(n_idsets * n_slots);
|
||||
for (int s = 0; s < n_idsets; s++)
|
||||
for (int j = 0; j < n_slots; j++)
|
||||
ids[s * n_slots + j] = (s * n_slots + j) % n_experts;
|
||||
|
||||
// Aligned SoA repack
|
||||
const long long nblocks = (long long)n_experts * M * nb;
|
||||
std::vector<uint2> QS(nblocks * PAIRS_PER_BLOCK);
|
||||
std::vector<__half> DQ(nblocks);
|
||||
for (long long blk = 0; blk < nblocks; blk++) {
|
||||
const uint8_t *src = &W[blk * BLOCK_BYTES];
|
||||
uint16_t h; memcpy(&h, src, 2);
|
||||
DQ[blk] = __ushort_as_half(h);
|
||||
memcpy(&QS[blk * PAIRS_PER_BLOCK], src + 2, 64);
|
||||
}
|
||||
|
||||
// Device buffers
|
||||
uint8_t *dW; float *dX, *dOutBase, *dOutAl; int32_t *dIds; uint2 *dQS; __half *dDQ;
|
||||
q8_1_block *dX8; float *dSlotW;
|
||||
CK(cudaMalloc(&dW, W.size()));
|
||||
CK(cudaMalloc(&dX, sizeof(float) * K * n_slots));
|
||||
CK(cudaMalloc(&dOutBase, sizeof(float) * n_slots * M));
|
||||
CK(cudaMalloc(&dOutAl, sizeof(float) * n_slots * M));
|
||||
CK(cudaMalloc(&dIds, sizeof(int32_t) * ids.size()));
|
||||
CK(cudaMalloc(&dQS, QS.size() * sizeof(uint2)));
|
||||
CK(cudaMalloc(&dDQ, DQ.size() * sizeof(__half)));
|
||||
CK(cudaMalloc(&dX8, X8.size() * sizeof(q8_1_block)));
|
||||
CK(cudaMalloc(&dSlotW, sizeof(float) * n_slots));
|
||||
CK(cudaMemcpy(dW, W.data(), W.size(), cudaMemcpyHostToDevice));
|
||||
// moe_vec's X layout: [n_tokens*n_expert_used rows? or one row]; the vec
|
||||
// entry treats X as [n_tokens, K] with router weights applied later; we
|
||||
// pass the same activation replicated per slot to keep both paths equal.
|
||||
for (int s = 0; s < n_slots; s++)
|
||||
CK(cudaMemcpy(dX + (size_t)s * K, X.data(), sizeof(float) * K, cudaMemcpyHostToDevice));
|
||||
CK(cudaMemcpy(dIds, ids.data(), sizeof(int32_t) * ids.size(), cudaMemcpyHostToDevice));
|
||||
CK(cudaMemcpy(dQS, QS.data(), QS.size() * sizeof(uint2), cudaMemcpyHostToDevice));
|
||||
CK(cudaMemcpy(dDQ, DQ.data(), DQ.size() * sizeof(__half), cudaMemcpyHostToDevice));
|
||||
CK(cudaMemcpy(dX8, X8.data(), X8.size() * sizeof(q8_1_block), cudaMemcpyHostToDevice));
|
||||
std::vector<float> slotw(n_slots, 1.0f);
|
||||
CK(cudaMemcpy(dSlotW, slotw.data(), sizeof(float) * n_slots, cudaMemcpyHostToDevice));
|
||||
|
||||
cudaStream_t stream; CK(cudaStreamCreate(&stream));
|
||||
cudaEvent_t e0, e1; CK(cudaEventCreate(&e0)); CK(cudaEventCreate(&e1));
|
||||
|
||||
// ---- baseline: production vec entry -----------------------------------
|
||||
int rc = ds4_mmq_iq2_xxs_moe_vec(dW, dX, dIds, dOutBase, M, K, 1, n_experts, n_slots, stream);
|
||||
if (rc != 0) { printf("baseline moe_vec rc=%d\n", rc); return 1; }
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
CK(cudaEventRecord(e0, stream));
|
||||
for (int i = 0; i < iters; i++)
|
||||
(void)ds4_mmq_iq2_xxs_moe_vec(dW, dX, dIds + (i % n_idsets) * n_slots, dOutBase,
|
||||
M, K, 1, n_experts, n_slots, stream);
|
||||
CK(cudaEventRecord(e1, stream));
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
float ms_base = 0.0f; CK(cudaEventElapsedTime(&ms_base, e0, e1));
|
||||
ms_base /= iters;
|
||||
|
||||
// ---- aligned prototype -------------------------------------------------
|
||||
dim3 grid(M, n_slots, 1);
|
||||
iq2_aligned_moe_vec_kernel<<<grid, 32, 0, stream>>>(dOutAl, dQS, dDQ, dX8, dIds, dSlotW, M, nb);
|
||||
CK(cudaGetLastError());
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
CK(cudaEventRecord(e0, stream));
|
||||
for (int i = 0; i < iters; i++)
|
||||
iq2_aligned_moe_vec_kernel<<<grid, 32, 0, stream>>>(dOutAl, dQS, dDQ, dX8,
|
||||
dIds + (i % n_idsets) * n_slots,
|
||||
dSlotW, M, nb);
|
||||
CK(cudaEventRecord(e1, stream));
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
float ms_al = 0.0f; CK(cudaEventElapsedTime(&ms_al, e0, e1));
|
||||
ms_al /= iters;
|
||||
|
||||
// ---- correctness (vs baseline, last idset used by both loops) ----------
|
||||
// rerun both once on idset 0 for a clean compare
|
||||
(void)ds4_mmq_iq2_xxs_moe_vec(dW, dX, dIds, dOutBase, M, K, 1, n_experts, n_slots, stream);
|
||||
iq2_aligned_moe_vec_kernel<<<grid, 32, 0, stream>>>(dOutAl, dQS, dDQ, dX8, dIds, dSlotW, M, nb);
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
std::vector<float> ob(n_slots * M), oa(n_slots * M);
|
||||
CK(cudaMemcpy(ob.data(), dOutBase, sizeof(float) * ob.size(), cudaMemcpyDeviceToHost));
|
||||
CK(cudaMemcpy(oa.data(), dOutAl, sizeof(float) * oa.size(), cudaMemcpyDeviceToHost));
|
||||
double max_rel = 0.0, max_abs = 0.0; int bad = 0;
|
||||
for (size_t i = 0; i < ob.size(); i++) {
|
||||
const double a = ob[i], b = oa[i];
|
||||
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;
|
||||
if (rd > max_rel) max_rel = rd;
|
||||
if (rd > 2e-2 && ad > 1e-2) bad++;
|
||||
}
|
||||
|
||||
printf("PROTO_IQ2_ALIGNED 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);
|
||||
printf(" baseline moe_vec : %.4f ms -> %6.1f GB/s\n", ms_base, slot_gb / (ms_base / 1e3));
|
||||
printf(" aligned proto : %.4f ms -> %6.1f GB/s (%+.1f%%)\n",
|
||||
ms_al, slot_gb / (ms_al / 1e3), 100.0 * (ms_base / ms_al - 1.0));
|
||||
printf(" parity: max_rel=%.3e max_abs=%.3e bad=%d -> %s\n",
|
||||
max_rel, max_abs, bad, bad == 0 ? "PASS" : "FAIL");
|
||||
return bad == 0 ? 0 : 2;
|
||||
}
|
||||
@@ -0,0 +1,858 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// proto_kv_reencode_idem.cu - packed-KV restore re-encode idempotence probe.
|
||||
//
|
||||
// v0.2.4 ships REFUSED packed disk-KV restores: the restore path reads the
|
||||
// F32 committed rows from the file and regenerates the fp8/fp4 mirrors by
|
||||
// re-encoding, and the kv_crossmode gate observed the resulting packed-server
|
||||
// continuations DRIFTING (fp8-only and fp4-only independently) despite the
|
||||
// pow2-scale idempotence argument saying re-encode of committed values is
|
||||
// value-exact. This proto isolates the kernel math from the pipeline:
|
||||
// fp8 : quantize(x) -> C1,codes1,scales1 ; dequant==C1 ;
|
||||
// quantize(C1) -> C2,codes2,scales2 ; C2 ?= C1 (bitwise) ; dequant==C1
|
||||
// fp4 : hadamard_qat(x) -> C1 ; fp4_dequant==C1 ;
|
||||
// noHad_encode(C1) -> C2 ; C2 ?= C1 ; dequant==C1
|
||||
// plus a third-iteration fixed-point check and a fast-math boundary scan of
|
||||
// scale = exp2f(ceilf(log2f(amax/448 or /6))) at exact power-of-two inputs
|
||||
// (the one corner where fast-math approximation could flip ceilf and shift
|
||||
// the whole block one exponent).
|
||||
// Code/scale mismatches with VALUE identity are tallied separately: a block
|
||||
// whose committed max sits exactly on a half-boundary legitimately re-picks
|
||||
// scale/2 with all codes shifted one exponent, values bit-identical.
|
||||
//
|
||||
// The first battery runs verbatim copies of the v0.2.4 (PRE-fix) ds4_cuda.cu
|
||||
// bodies and is EXPECTED to fail -- kept as the regression repro. The _fx
|
||||
// battery is the fix as shipped in v0.3 (dsv4_pow2_ceil_scale + signbit) and
|
||||
// is the gate (exit status). Build MUST mirror the engine's fast-math flags:
|
||||
// /usr/local/cuda/bin/nvcc -O3 -std=c++17 --use_fast_math -arch=sm_121 \
|
||||
// cuda/mmq/test/proto_kv_reencode_idem.cu -o proto_kv_reencode_idem
|
||||
// argv: [n_rows_fp8=131072] [n_rows_fp4=131072]
|
||||
|
||||
#include <cuda_runtime.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
|
||||
#define CHECK(call) do { cudaError_t e_=(call); if(e_!=cudaSuccess){ \
|
||||
fprintf(stderr,"CUDA %s @%d: %s\n",#call,__LINE__,cudaGetErrorString(e_)); exit(1);} } while(0)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Verbatim from ds4_cuda.cu (v0.2.4 tag content).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define DS4_OPP_C_FP8_NOPE_DEV 448u /* 512 - 64 */
|
||||
#define DS4_OPP_C_FP8_BLOCKS_DEV 7u /* 448 / 64 */
|
||||
#define DS4_OPP_C_FP8_ROW_BYTES_DEV 704u /* 448 codes + 64 FP32 tail */
|
||||
|
||||
__device__ static float dsv4_e4m3fn_value_dev(int i) {
|
||||
int exp = (i >> 3) & 15;
|
||||
int mant = i & 7;
|
||||
if (exp == 0) return (float)mant * 0.001953125f;
|
||||
return (1.0f + (float)mant * 0.125f) * exp2f((float)exp - 7.0f);
|
||||
}
|
||||
|
||||
__device__ static float dsv4_e4m3fn_dequant_dev(float x) {
|
||||
float sign = x < 0.0f ? -1.0f : 1.0f;
|
||||
float ax = fminf(fabsf(x), 448.0f);
|
||||
int lo = 0, hi = 126;
|
||||
while (lo < hi) {
|
||||
int mid = (lo + hi + 1) >> 1;
|
||||
if (dsv4_e4m3fn_value_dev(mid) <= ax) lo = mid;
|
||||
else hi = mid - 1;
|
||||
}
|
||||
int best = lo;
|
||||
if (best < 126) {
|
||||
float bd = fabsf(ax - dsv4_e4m3fn_value_dev(best));
|
||||
float nd = fabsf(ax - dsv4_e4m3fn_value_dev(best + 1));
|
||||
if (nd < bd || (nd == bd && (((best + 1) & 1) == 0) && ((best & 1) != 0))) best++;
|
||||
}
|
||||
return sign * dsv4_e4m3fn_value_dev(best);
|
||||
}
|
||||
|
||||
__device__ static unsigned char dsv4_e4m3fn_encode_dev(float x) {
|
||||
const unsigned int sign_bit = (x < 0.0f) ? 0x80u : 0x00u;
|
||||
const float ax = fminf(fabsf(x), 448.0f);
|
||||
int lo = 0, hi = 126;
|
||||
while (lo < hi) {
|
||||
const int mid = (lo + hi + 1) >> 1;
|
||||
if (dsv4_e4m3fn_value_dev(mid) <= ax) lo = mid;
|
||||
else hi = mid - 1;
|
||||
}
|
||||
int best = lo;
|
||||
if (best < 126) {
|
||||
const float bd = fabsf(ax - dsv4_e4m3fn_value_dev(best));
|
||||
const float nd = fabsf(ax - dsv4_e4m3fn_value_dev(best + 1));
|
||||
if (nd < bd || (nd == bd && (((best + 1) & 1) == 0) && ((best & 1) != 0))) best++;
|
||||
}
|
||||
return (unsigned char)(sign_bit | (unsigned int)best);
|
||||
}
|
||||
|
||||
__device__ float dsv4_e4m3fn_decode_table[128];
|
||||
|
||||
static int fp8_decode_table_init(void) {
|
||||
float table[128];
|
||||
for (int i = 0; i < 128; i++) {
|
||||
const int exp = (i >> 3) & 15;
|
||||
const int mant = i & 7;
|
||||
table[i] = (exp == 0)
|
||||
? (float)mant * 0.001953125f
|
||||
: (1.0f + (float)mant * 0.125f) * ldexpf(1.0f, exp - 7);
|
||||
}
|
||||
return cudaMemcpyToSymbol(dsv4_e4m3fn_decode_table, table,
|
||||
sizeof(table), 0, cudaMemcpyHostToDevice) == cudaSuccess;
|
||||
}
|
||||
|
||||
__device__ static float fp8_kv_read(
|
||||
const unsigned char * __restrict__ codes_base,
|
||||
const float * __restrict__ scale_base,
|
||||
uint32_t c,
|
||||
uint32_t d) {
|
||||
const uint64_t row_off = (uint64_t)c * DS4_OPP_C_FP8_ROW_BYTES_DEV;
|
||||
if (d < DS4_OPP_C_FP8_NOPE_DEV) {
|
||||
const unsigned char code = codes_base[row_off + d];
|
||||
const float scale = scale_base[(uint64_t)c * DS4_OPP_C_FP8_BLOCKS_DEV + (d >> 6)];
|
||||
const float mag = dsv4_e4m3fn_decode_table[code & 0x7fu];
|
||||
return ((code & 0x80u) ? -mag : mag) * scale;
|
||||
}
|
||||
const float *tail = (const float *)(codes_base + row_off + DS4_OPP_C_FP8_NOPE_DEV);
|
||||
return tail[d - DS4_OPP_C_FP8_NOPE_DEV];
|
||||
}
|
||||
|
||||
__global__ static void fp8_kv_dequant_rows_kernel(
|
||||
float * __restrict__ dst,
|
||||
const unsigned char * __restrict__ codes_base,
|
||||
const float * __restrict__ scale_base,
|
||||
uint32_t n_rows,
|
||||
uint32_t head_dim) {
|
||||
const uint32_t r = blockIdx.x;
|
||||
if (r >= n_rows) return;
|
||||
float *out = dst + (uint64_t)r * head_dim;
|
||||
for (uint32_t d = threadIdx.x; d < head_dim; d += blockDim.x) {
|
||||
out[d] = fp8_kv_read(codes_base, scale_base, r, d);
|
||||
}
|
||||
}
|
||||
|
||||
__global__ static void fp8_kv_quantize_kernel(
|
||||
float *x, uint32_t n_tok, uint32_t head_dim, uint32_t n_rot,
|
||||
unsigned char * __restrict__ codes_base,
|
||||
float * __restrict__ scale_base) {
|
||||
uint32_t row = blockIdx.x;
|
||||
uint32_t tid = threadIdx.x;
|
||||
uint32_t n_nope = head_dim - n_rot;
|
||||
const uint64_t row_stride = (uint64_t)n_nope + (uint64_t)n_rot * sizeof(float);
|
||||
const uint64_t scale_stride = (uint64_t)(n_nope / 64u);
|
||||
float *xr = x + (uint64_t)row * head_dim;
|
||||
unsigned char *codes_row = codes_base
|
||||
? codes_base + (uint64_t)row * row_stride
|
||||
: (unsigned char *)0;
|
||||
float *scale_row = scale_base
|
||||
? scale_base + (uint64_t)row * scale_stride
|
||||
: (float *)0;
|
||||
__shared__ float scratch[64];
|
||||
for (uint32_t off = 0; off < n_nope; off += 64) {
|
||||
float v = 0.0f;
|
||||
if (off + tid < n_nope) v = xr[off + tid];
|
||||
scratch[tid] = off + tid < n_nope ? fabsf(v) : 0.0f;
|
||||
__syncthreads();
|
||||
for (uint32_t stride = 32; stride > 0; stride >>= 1) {
|
||||
if (tid < stride) scratch[tid] = fmaxf(scratch[tid], scratch[tid + stride]);
|
||||
__syncthreads();
|
||||
}
|
||||
float scale = exp2f(ceilf(log2f(fmaxf(scratch[0], 1.0e-4f) / 448.0f)));
|
||||
if (off + tid < n_nope) {
|
||||
float clamp = fminf(448.0f, fmaxf(-448.0f, v / scale));
|
||||
xr[off + tid] = dsv4_e4m3fn_dequant_dev(clamp) * scale;
|
||||
if (codes_row) codes_row[off + tid] = dsv4_e4m3fn_encode_dev(clamp);
|
||||
}
|
||||
if (scale_row && tid == 0) scale_row[off / 64u] = scale;
|
||||
__syncthreads();
|
||||
}
|
||||
if (codes_row && tid < n_rot) {
|
||||
float *tail = (float *)(codes_row + (uint64_t)n_nope);
|
||||
tail[tid] = xr[(uint64_t)n_nope + tid];
|
||||
}
|
||||
}
|
||||
|
||||
__device__ static float dsv4_e2m1fn_value_dev(int i) {
|
||||
switch (i & 7) {
|
||||
case 0: return 0.0f;
|
||||
case 1: return 0.5f;
|
||||
case 2: return 1.0f;
|
||||
case 3: return 1.5f;
|
||||
case 4: return 2.0f;
|
||||
case 5: return 3.0f;
|
||||
case 6: return 4.0f;
|
||||
default: return 6.0f;
|
||||
}
|
||||
}
|
||||
|
||||
__device__ static int dsv4_e2m1fn_level_dev(float ax) {
|
||||
ax = fminf(ax, 6.0f);
|
||||
int best = 0;
|
||||
float best_diff = fabsf(ax - dsv4_e2m1fn_value_dev(0));
|
||||
for (int i = 1; i < 8; i++) {
|
||||
float diff = fabsf(ax - dsv4_e2m1fn_value_dev(i));
|
||||
if (diff < best_diff || (diff == best_diff && ((i & 1) == 0) && ((best & 1) != 0))) {
|
||||
best = i;
|
||||
best_diff = diff;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
__device__ static float indexer_fp4_read(
|
||||
const unsigned char * __restrict__ codes_base,
|
||||
const float * __restrict__ scale_base,
|
||||
uint32_t row, uint32_t d) {
|
||||
const unsigned char byte = codes_base[(uint64_t)row * 64u + (d >> 1u)];
|
||||
const uint32_t nib = ((uint32_t)byte >> (4u * (d & 1u))) & 0xFu;
|
||||
const float sign = (nib & 8u) ? -1.0f : 1.0f;
|
||||
const float scale = scale_base[(uint64_t)row * 4u + (d >> 5u)];
|
||||
return sign * dsv4_e2m1fn_value_dev((int)(nib & 7u)) * scale;
|
||||
}
|
||||
|
||||
__global__ static void indexer_fp4_dequant_rows_kernel(
|
||||
float * __restrict__ dst,
|
||||
const unsigned char * __restrict__ codes_base,
|
||||
const float * __restrict__ scale_base,
|
||||
uint32_t n_rows, uint32_t head_dim) {
|
||||
const uint32_t r = blockIdx.x;
|
||||
if (r >= n_rows) return;
|
||||
float *out = dst + (uint64_t)r * head_dim;
|
||||
for (uint32_t d = threadIdx.x; d < head_dim; d += blockDim.x) {
|
||||
out[d] = indexer_fp4_read(codes_base, scale_base, r, d);
|
||||
}
|
||||
}
|
||||
|
||||
__global__ static void indexer_fp4_encode_rows_kernel(
|
||||
float * __restrict__ x, uint32_t n_rows, uint32_t head_dim,
|
||||
unsigned char * __restrict__ codes_base, float * __restrict__ scale_base) {
|
||||
uint32_t row = blockIdx.x;
|
||||
uint32_t tid = threadIdx.x;
|
||||
if (row >= n_rows || head_dim != 128u || tid >= 128u) return;
|
||||
__shared__ float absbuf[128];
|
||||
float *xr = x + (uint64_t)row * head_dim;
|
||||
float v = xr[tid]; /* committed value -- NO Hadamard */
|
||||
uint32_t fp4_block = tid >> 5u;
|
||||
uint32_t lane = tid & 31u;
|
||||
uint32_t block_base = fp4_block * 32u;
|
||||
absbuf[tid] = fabsf(v);
|
||||
__syncthreads();
|
||||
for (uint32_t stride = 16u; stride > 0u; stride >>= 1u) {
|
||||
if (lane < stride)
|
||||
absbuf[block_base + lane] = fmaxf(absbuf[block_base + lane],
|
||||
absbuf[block_base + lane + stride]);
|
||||
__syncthreads();
|
||||
}
|
||||
float amax = fmaxf(absbuf[block_base], 7.052966104933725e-38f);
|
||||
float scale = exp2f(ceilf(log2f(amax / 6.0f)));
|
||||
float clamp = fminf(6.0f, fmaxf(-6.0f, v / scale));
|
||||
int level = dsv4_e2m1fn_level_dev(fabsf(clamp));
|
||||
xr[tid] = ((clamp < 0.0f ? -1.0f : 1.0f) * dsv4_e2m1fn_value_dev(level)) * scale;
|
||||
uint32_t nib = (clamp < 0.0f ? 8u : 0u) | (uint32_t)level;
|
||||
uint32_t hi = __shfl_down_sync(0xffffffffu, nib, 1u);
|
||||
if ((tid & 1u) == 0u) codes_base[(uint64_t)row * 64u + (tid >> 1u)] = (unsigned char)(nib | (hi << 4u));
|
||||
if ((tid & 31u) == 0u) scale_base[(uint64_t)row * 4u + (tid >> 5u)] = scale;
|
||||
}
|
||||
|
||||
__global__ static void indexer_hadamard_fp4_kernel(float *x, uint32_t n_rows, uint32_t head_dim,
|
||||
unsigned char * __restrict__ codes_base,
|
||||
float * __restrict__ scale_base) {
|
||||
uint32_t row = blockIdx.x;
|
||||
uint32_t tid = threadIdx.x;
|
||||
if (row >= n_rows || head_dim != 128u || tid >= 128u) return;
|
||||
|
||||
__shared__ float vals[128];
|
||||
__shared__ float absbuf[128];
|
||||
float *xr = x + (uint64_t)row * head_dim;
|
||||
vals[tid] = xr[tid];
|
||||
__syncthreads();
|
||||
|
||||
for (uint32_t stride = 1u; stride < 128u; stride <<= 1u) {
|
||||
if ((tid & stride) == 0u) {
|
||||
uint32_t base = (tid & ~(2u * stride - 1u)) + (tid & (stride - 1u));
|
||||
float a = vals[base];
|
||||
float b = vals[base + stride];
|
||||
vals[base] = a + b;
|
||||
vals[base + stride] = a - b;
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
|
||||
float v = vals[tid] * 0.08838834764831845f;
|
||||
uint32_t fp4_block = tid >> 5u;
|
||||
uint32_t lane = tid & 31u;
|
||||
uint32_t block_base = fp4_block * 32u;
|
||||
absbuf[tid] = fabsf(v);
|
||||
__syncthreads();
|
||||
|
||||
for (uint32_t stride = 16u; stride > 0u; stride >>= 1u) {
|
||||
if (lane < stride) {
|
||||
absbuf[block_base + lane] = fmaxf(absbuf[block_base + lane],
|
||||
absbuf[block_base + lane + stride]);
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
|
||||
float amax = fmaxf(absbuf[block_base], 7.052966104933725e-38f);
|
||||
float scale = exp2f(ceilf(log2f(amax / 6.0f)));
|
||||
float clamp = fminf(6.0f, fmaxf(-6.0f, v / scale));
|
||||
int level = dsv4_e2m1fn_level_dev(fabsf(clamp));
|
||||
xr[tid] = ((clamp < 0.0f ? -1.0f : 1.0f) * dsv4_e2m1fn_value_dev(level)) * scale;
|
||||
if (codes_base) {
|
||||
uint32_t nib = (clamp < 0.0f ? 8u : 0u) | (uint32_t)level;
|
||||
uint32_t hi = __shfl_down_sync(0xffffffffu, nib, 1u);
|
||||
if ((tid & 1u) == 0u) codes_base[(uint64_t)row * 64u + (tid >> 1u)] = (unsigned char)(nib | (hi << 4u));
|
||||
if ((tid & 31u) == 0u) scale_base[(uint64_t)row * 4u + (tid >> 5u)] = scale;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FIXED variants (candidate engine change): exact pow2-ceil scale via frexpf
|
||||
// (bit ops, immune to fast-math lg2.approx one-sided error at 2^-n) and
|
||||
// signbit-based sign handling (preserves -0.0 across the round trip).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
__device__ static float dsv4_pow2_ceil_scale(float r) {
|
||||
int e; float m = frexpf(r, &e); /* r = m * 2^e, m in [0.5, 1) */
|
||||
return ldexpf(1.0f, m == 0.5f ? e - 1 : e);
|
||||
}
|
||||
|
||||
__device__ static float dsv4_e4m3fn_dequant_fx(float x) {
|
||||
float sign = signbit(x) ? -1.0f : 1.0f;
|
||||
float ax = fminf(fabsf(x), 448.0f);
|
||||
int lo = 0, hi = 126;
|
||||
while (lo < hi) {
|
||||
int mid = (lo + hi + 1) >> 1;
|
||||
if (dsv4_e4m3fn_value_dev(mid) <= ax) lo = mid;
|
||||
else hi = mid - 1;
|
||||
}
|
||||
int best = lo;
|
||||
if (best < 126) {
|
||||
float bd = fabsf(ax - dsv4_e4m3fn_value_dev(best));
|
||||
float nd = fabsf(ax - dsv4_e4m3fn_value_dev(best + 1));
|
||||
if (nd < bd || (nd == bd && (((best + 1) & 1) == 0) && ((best & 1) != 0))) best++;
|
||||
}
|
||||
return sign * dsv4_e4m3fn_value_dev(best);
|
||||
}
|
||||
|
||||
__device__ static unsigned char dsv4_e4m3fn_encode_fx(float x) {
|
||||
const unsigned int sign_bit = signbit(x) ? 0x80u : 0x00u;
|
||||
const float ax = fminf(fabsf(x), 448.0f);
|
||||
int lo = 0, hi = 126;
|
||||
while (lo < hi) {
|
||||
const int mid = (lo + hi + 1) >> 1;
|
||||
if (dsv4_e4m3fn_value_dev(mid) <= ax) lo = mid;
|
||||
else hi = mid - 1;
|
||||
}
|
||||
int best = lo;
|
||||
if (best < 126) {
|
||||
const float bd = fabsf(ax - dsv4_e4m3fn_value_dev(best));
|
||||
const float nd = fabsf(ax - dsv4_e4m3fn_value_dev(best + 1));
|
||||
if (nd < bd || (nd == bd && (((best + 1) & 1) == 0) && ((best & 1) != 0))) best++;
|
||||
}
|
||||
return (unsigned char)(sign_bit | (unsigned int)best);
|
||||
}
|
||||
|
||||
__global__ static void fp8_kv_quantize_kernel_fx(
|
||||
float *x, uint32_t n_tok, uint32_t head_dim, uint32_t n_rot,
|
||||
unsigned char * __restrict__ codes_base,
|
||||
float * __restrict__ scale_base) {
|
||||
uint32_t row = blockIdx.x;
|
||||
uint32_t tid = threadIdx.x;
|
||||
uint32_t n_nope = head_dim - n_rot;
|
||||
const uint64_t row_stride = (uint64_t)n_nope + (uint64_t)n_rot * sizeof(float);
|
||||
const uint64_t scale_stride = (uint64_t)(n_nope / 64u);
|
||||
float *xr = x + (uint64_t)row * head_dim;
|
||||
unsigned char *codes_row = codes_base
|
||||
? codes_base + (uint64_t)row * row_stride
|
||||
: (unsigned char *)0;
|
||||
float *scale_row = scale_base
|
||||
? scale_base + (uint64_t)row * scale_stride
|
||||
: (float *)0;
|
||||
__shared__ float scratch[64];
|
||||
for (uint32_t off = 0; off < n_nope; off += 64) {
|
||||
float v = 0.0f;
|
||||
if (off + tid < n_nope) v = xr[off + tid];
|
||||
scratch[tid] = off + tid < n_nope ? fabsf(v) : 0.0f;
|
||||
__syncthreads();
|
||||
for (uint32_t stride = 32; stride > 0; stride >>= 1) {
|
||||
if (tid < stride) scratch[tid] = fmaxf(scratch[tid], scratch[tid + stride]);
|
||||
__syncthreads();
|
||||
}
|
||||
float scale = dsv4_pow2_ceil_scale(fmaxf(scratch[0], 1.0e-4f) / 448.0f);
|
||||
if (off + tid < n_nope) {
|
||||
float clamp = fminf(448.0f, fmaxf(-448.0f, v / scale));
|
||||
xr[off + tid] = dsv4_e4m3fn_dequant_fx(clamp) * scale;
|
||||
if (codes_row) codes_row[off + tid] = dsv4_e4m3fn_encode_fx(clamp);
|
||||
}
|
||||
if (scale_row && tid == 0) scale_row[off / 64u] = scale;
|
||||
__syncthreads();
|
||||
}
|
||||
if (codes_row && tid < n_rot) {
|
||||
float *tail = (float *)(codes_row + (uint64_t)n_nope);
|
||||
tail[tid] = xr[(uint64_t)n_nope + tid];
|
||||
}
|
||||
}
|
||||
|
||||
__global__ static void indexer_fp4_encode_rows_kernel_fx(
|
||||
float * __restrict__ x, uint32_t n_rows, uint32_t head_dim,
|
||||
unsigned char * __restrict__ codes_base, float * __restrict__ scale_base) {
|
||||
uint32_t row = blockIdx.x;
|
||||
uint32_t tid = threadIdx.x;
|
||||
if (row >= n_rows || head_dim != 128u || tid >= 128u) return;
|
||||
__shared__ float absbuf[128];
|
||||
float *xr = x + (uint64_t)row * head_dim;
|
||||
float v = xr[tid];
|
||||
uint32_t fp4_block = tid >> 5u;
|
||||
uint32_t lane = tid & 31u;
|
||||
uint32_t block_base = fp4_block * 32u;
|
||||
absbuf[tid] = fabsf(v);
|
||||
__syncthreads();
|
||||
for (uint32_t stride = 16u; stride > 0u; stride >>= 1u) {
|
||||
if (lane < stride)
|
||||
absbuf[block_base + lane] = fmaxf(absbuf[block_base + lane],
|
||||
absbuf[block_base + lane + stride]);
|
||||
__syncthreads();
|
||||
}
|
||||
float amax = fmaxf(absbuf[block_base], 7.052966104933725e-38f);
|
||||
float scale = dsv4_pow2_ceil_scale(amax / 6.0f);
|
||||
float clamp = fminf(6.0f, fmaxf(-6.0f, v / scale));
|
||||
int level = dsv4_e2m1fn_level_dev(fabsf(clamp));
|
||||
xr[tid] = ((signbit(clamp) ? -1.0f : 1.0f) * dsv4_e2m1fn_value_dev(level)) * scale;
|
||||
uint32_t nib = (signbit(clamp) ? 8u : 0u) | (uint32_t)level;
|
||||
uint32_t hi = __shfl_down_sync(0xffffffffu, nib, 1u);
|
||||
if ((tid & 1u) == 0u) codes_base[(uint64_t)row * 64u + (tid >> 1u)] = (unsigned char)(nib | (hi << 4u));
|
||||
if ((tid & 31u) == 0u) scale_base[(uint64_t)row * 4u + (tid >> 5u)] = scale;
|
||||
}
|
||||
|
||||
__global__ static void indexer_hadamard_fp4_kernel_fx(float *x, uint32_t n_rows, uint32_t head_dim,
|
||||
unsigned char * __restrict__ codes_base,
|
||||
float * __restrict__ scale_base) {
|
||||
uint32_t row = blockIdx.x;
|
||||
uint32_t tid = threadIdx.x;
|
||||
if (row >= n_rows || head_dim != 128u || tid >= 128u) return;
|
||||
|
||||
__shared__ float vals[128];
|
||||
__shared__ float absbuf[128];
|
||||
float *xr = x + (uint64_t)row * head_dim;
|
||||
vals[tid] = xr[tid];
|
||||
__syncthreads();
|
||||
|
||||
for (uint32_t stride = 1u; stride < 128u; stride <<= 1u) {
|
||||
if ((tid & stride) == 0u) {
|
||||
uint32_t base = (tid & ~(2u * stride - 1u)) + (tid & (stride - 1u));
|
||||
float a = vals[base];
|
||||
float b = vals[base + stride];
|
||||
vals[base] = a + b;
|
||||
vals[base + stride] = a - b;
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
|
||||
float v = vals[tid] * 0.08838834764831845f;
|
||||
uint32_t fp4_block = tid >> 5u;
|
||||
uint32_t lane = tid & 31u;
|
||||
uint32_t block_base = fp4_block * 32u;
|
||||
absbuf[tid] = fabsf(v);
|
||||
__syncthreads();
|
||||
|
||||
for (uint32_t stride = 16u; stride > 0u; stride >>= 1u) {
|
||||
if (lane < stride) {
|
||||
absbuf[block_base + lane] = fmaxf(absbuf[block_base + lane],
|
||||
absbuf[block_base + lane + stride]);
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
|
||||
float amax = fmaxf(absbuf[block_base], 7.052966104933725e-38f);
|
||||
float scale = dsv4_pow2_ceil_scale(amax / 6.0f);
|
||||
float clamp = fminf(6.0f, fmaxf(-6.0f, v / scale));
|
||||
int level = dsv4_e2m1fn_level_dev(fabsf(clamp));
|
||||
xr[tid] = ((signbit(clamp) ? -1.0f : 1.0f) * dsv4_e2m1fn_value_dev(level)) * scale;
|
||||
if (codes_base) {
|
||||
uint32_t nib = (signbit(clamp) ? 8u : 0u) | (uint32_t)level;
|
||||
uint32_t hi = __shfl_down_sync(0xffffffffu, nib, 1u);
|
||||
if ((tid & 1u) == 0u) codes_base[(uint64_t)row * 64u + (tid >> 1u)] = (unsigned char)(nib | (hi << 4u));
|
||||
if ((tid & 31u) == 0u) scale_base[(uint64_t)row * 4u + (tid >> 5u)] = scale;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Proto scaffolding (not engine code).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Deterministic per-element fill: xorshift hash of (row,lane) -> [-1,1),
|
||||
// scaled per row-band regime. Bands (of 8) for the fp8 input:
|
||||
// 0 uniform | 1 x1e-5 | 2 x1e4 | 3 pow2 magnitude ladder 2^((row%40)-20)
|
||||
// 4 block amax forced EXACTLY 448*2^k (k=(row%40)-20; lane0 of block)
|
||||
// 5 block amax forced EXACTLY 224*2^k (post-encode halving boundary)
|
||||
// 6 block amax 433*2^k (e4m3 rounds it UP to 448*2^k -> boundary amax')
|
||||
// 7 tiny/zero: x1e-6, every 3rd row all-zero (sub-1e-4-floor blocks)
|
||||
// Exact 2^k as a float, immune to any exp2f approximation question.
|
||||
__device__ static float exact_pow2(int k) {
|
||||
return __uint_as_float((uint32_t)(127 + k) << 23);
|
||||
}
|
||||
|
||||
__device__ static float hash_unit(uint32_t row, uint32_t lane, uint32_t salt) {
|
||||
uint32_t h = row * 2654435761u ^ (lane + 1u) * 2246822519u ^ salt * 3266489917u;
|
||||
h ^= h >> 15; h *= 2246822519u; h ^= h >> 13; h *= 3266489917u; h ^= h >> 16;
|
||||
return ((float)(h & 0xFFFFFFu) / 8388608.0f) - 1.0f; // [-1,1)
|
||||
}
|
||||
|
||||
__global__ static void fill_fp8_input_kernel(float *x, uint32_t n_rows, uint32_t salt) {
|
||||
uint32_t row = blockIdx.x;
|
||||
uint32_t tid = threadIdx.x; // 512 threads
|
||||
if (row >= n_rows || tid >= 512u) return;
|
||||
uint32_t band = row / (n_rows / 8u + 1u);
|
||||
float u = hash_unit(row, tid, salt);
|
||||
float v = u;
|
||||
int k = (int)(row % 40u) - 20;
|
||||
switch (band) {
|
||||
case 0: break;
|
||||
case 1: v = u * 1e-5f; break;
|
||||
case 2: v = u * 1e4f; break;
|
||||
case 3: v = u * exact_pow2(k); break;
|
||||
case 4: v = (tid < 448u && (tid & 63u) == 0u) ? 448.0f * exact_pow2(k)
|
||||
: u * 100.0f * exact_pow2(k); break;
|
||||
case 5: v = (tid < 448u && (tid & 63u) == 0u) ? 224.0f * exact_pow2(k)
|
||||
: u * 100.0f * exact_pow2(k); break;
|
||||
case 6: v = (tid < 448u && (tid & 63u) == 0u) ? 433.0f * exact_pow2(k)
|
||||
: u * 100.0f * exact_pow2(k); break;
|
||||
default:
|
||||
v = (row % 3u == 0u) ? 0.0f : u * 1e-6f;
|
||||
break;
|
||||
}
|
||||
x[(uint64_t)row * 512u + tid] = v;
|
||||
}
|
||||
|
||||
__global__ static void fill_fp4_input_kernel(float *x, uint32_t n_rows, uint32_t salt) {
|
||||
uint32_t row = blockIdx.x;
|
||||
uint32_t tid = threadIdx.x; // 128 threads
|
||||
if (row >= n_rows || tid >= 128u) return;
|
||||
uint32_t band = row / (n_rows / 6u + 1u);
|
||||
float u = hash_unit(row, tid, salt ^ 0x9e3779b9u);
|
||||
float v = u;
|
||||
int k = (int)(row % 60u) - 30;
|
||||
switch (band) {
|
||||
case 0: break;
|
||||
case 1: v = u * 1e-6f; break;
|
||||
case 2: v = u * 1e5f; break;
|
||||
case 3: v = u * exact_pow2(k); break;
|
||||
case 4: v = (u > 0.0f ? 1.0f : -1.0f) * exact_pow2(k); break; // pure pow2 lanes
|
||||
default:
|
||||
v = (row % 3u == 0u) ? 0.0f : u * 1e-30f;
|
||||
break;
|
||||
}
|
||||
x[(uint64_t)row * 128u + tid] = v;
|
||||
}
|
||||
|
||||
struct mismatch_rec { uint32_t idx_hi, idx_lo, a_bits, b_bits; };
|
||||
#define MAX_RECS 8
|
||||
|
||||
__global__ static void compare_f32_kernel(const float *a, const float *b, uint64_t n,
|
||||
unsigned long long *count,
|
||||
mismatch_rec *recs, int *nrec) {
|
||||
uint64_t i = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i >= n) return;
|
||||
uint32_t ab = __float_as_uint(a[i]);
|
||||
uint32_t bb = __float_as_uint(b[i]);
|
||||
if (ab != bb) {
|
||||
atomicAdd(count, 1ull);
|
||||
int slot = atomicAdd(nrec, 1);
|
||||
if (slot < MAX_RECS) {
|
||||
recs[slot].idx_hi = (uint32_t)(i >> 32);
|
||||
recs[slot].idx_lo = (uint32_t)(i & 0xFFFFFFFFu);
|
||||
recs[slot].a_bits = ab;
|
||||
recs[slot].b_bits = bb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__global__ static void compare_u8_kernel(const unsigned char *a, const unsigned char *b,
|
||||
uint64_t n, unsigned long long *count) {
|
||||
uint64_t i = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i >= n) return;
|
||||
if (a[i] != b[i]) atomicAdd(count, 1ull);
|
||||
}
|
||||
|
||||
// Fast-math boundary scan: the exact-pow2 inputs the re-encode presents when
|
||||
// a block's committed max lands on 448*2^k (fp8) or {6,3,1.5}*2^k (fp4).
|
||||
// out[j] = scale computed by the shipped formula; host prints deviations.
|
||||
__global__ static void boundary_scan_kernel(float *out) {
|
||||
int idx = threadIdx.x; // 61 k values: -30..30
|
||||
if (idx >= 61) return;
|
||||
int k = idx - 30;
|
||||
float p2k = exact_pow2(k);
|
||||
// fp8 formula at amax = 448*2^k, 224*2^k, and one-ulp neighbours of 448*2^k
|
||||
float a448 = 448.0f * p2k;
|
||||
float a224 = 224.0f * p2k;
|
||||
float a448u = __uint_as_float(__float_as_uint(a448) + 1u);
|
||||
float a448d = __uint_as_float(__float_as_uint(a448) - 1u);
|
||||
out[idx*8 + 0] = exp2f(ceilf(log2f(fmaxf(a448, 1.0e-4f) / 448.0f)));
|
||||
out[idx*8 + 1] = exp2f(ceilf(log2f(fmaxf(a224, 1.0e-4f) / 448.0f)));
|
||||
out[idx*8 + 2] = exp2f(ceilf(log2f(fmaxf(a448u, 1.0e-4f) / 448.0f)));
|
||||
out[idx*8 + 3] = exp2f(ceilf(log2f(fmaxf(a448d, 1.0e-4f) / 448.0f)));
|
||||
// fp4 formula at amax = 6*2^k, 3*2^k, 1.5*2^k, one-ulp above 6*2^k
|
||||
float a6 = 6.0f * p2k;
|
||||
float a3 = 3.0f * p2k;
|
||||
float a15 = 1.5f * p2k;
|
||||
float a6u = __uint_as_float(__float_as_uint(a6) + 1u);
|
||||
out[idx*8 + 4] = exp2f(ceilf(log2f(fmaxf(a6, 7.052966104933725e-38f) / 6.0f)));
|
||||
out[idx*8 + 5] = exp2f(ceilf(log2f(fmaxf(a3, 7.052966104933725e-38f) / 6.0f)));
|
||||
out[idx*8 + 6] = exp2f(ceilf(log2f(fmaxf(a15, 7.052966104933725e-38f) / 6.0f)));
|
||||
out[idx*8 + 7] = exp2f(ceilf(log2f(fmaxf(a6u, 7.052966104933725e-38f) / 6.0f)));
|
||||
}
|
||||
|
||||
static unsigned long long report_f32(const char *tag, const float *a, const float *b,
|
||||
uint64_t n, uint32_t row_dim) {
|
||||
unsigned long long *d_count; mismatch_rec *d_recs; int *d_nrec;
|
||||
CHECK(cudaMalloc(&d_count, sizeof(*d_count)));
|
||||
CHECK(cudaMalloc(&d_recs, MAX_RECS * sizeof(mismatch_rec)));
|
||||
CHECK(cudaMalloc(&d_nrec, sizeof(int)));
|
||||
CHECK(cudaMemset(d_count, 0, sizeof(*d_count)));
|
||||
CHECK(cudaMemset(d_nrec, 0, sizeof(int)));
|
||||
uint64_t blocks = (n + 255) / 256;
|
||||
compare_f32_kernel<<<(uint32_t)blocks, 256>>>(a, b, n, d_count, d_recs, d_nrec);
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
unsigned long long count; mismatch_rec recs[MAX_RECS]; int nrec;
|
||||
CHECK(cudaMemcpy(&count, d_count, sizeof(count), cudaMemcpyDeviceToHost));
|
||||
CHECK(cudaMemcpy(recs, d_recs, sizeof(recs), cudaMemcpyDeviceToHost));
|
||||
CHECK(cudaMemcpy(&nrec, d_nrec, sizeof(nrec), cudaMemcpyDeviceToHost));
|
||||
printf(" %-28s mismatches=%llu / %llu\n", tag, count, (unsigned long long)n);
|
||||
for (int i = 0; i < nrec && i < MAX_RECS; i++) {
|
||||
uint64_t idx = ((uint64_t)recs[i].idx_hi << 32) | recs[i].idx_lo;
|
||||
float fa, fb;
|
||||
memcpy(&fa, &recs[i].a_bits, 4); memcpy(&fb, &recs[i].b_bits, 4);
|
||||
printf(" [%d] row=%llu lane=%u a=%08x(%.9g) b=%08x(%.9g)\n",
|
||||
i, (unsigned long long)(idx / row_dim), (uint32_t)(idx % row_dim),
|
||||
recs[i].a_bits, fa, recs[i].b_bits, fb);
|
||||
}
|
||||
cudaFree(d_count); cudaFree(d_recs); cudaFree(d_nrec);
|
||||
return count;
|
||||
}
|
||||
|
||||
static unsigned long long report_u8(const char *tag, const unsigned char *a,
|
||||
const unsigned char *b, uint64_t n) {
|
||||
unsigned long long *d_count;
|
||||
CHECK(cudaMalloc(&d_count, sizeof(*d_count)));
|
||||
CHECK(cudaMemset(d_count, 0, sizeof(*d_count)));
|
||||
uint64_t blocks = (n + 255) / 256;
|
||||
compare_u8_kernel<<<(uint32_t)blocks, 256>>>(a, b, n, d_count);
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
unsigned long long count;
|
||||
CHECK(cudaMemcpy(&count, d_count, sizeof(count), cudaMemcpyDeviceToHost));
|
||||
printf(" %-28s byte-mismatches=%llu / %llu (level-shift benign iff values match)\n",
|
||||
tag, count, (unsigned long long)n);
|
||||
cudaFree(d_count);
|
||||
return count;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
uint32_t n_fp8 = argc > 1 ? (uint32_t)atoi(argv[1]) : 131072u;
|
||||
uint32_t n_fp4 = argc > 2 ? (uint32_t)atoi(argv[2]) : 131072u;
|
||||
if (!fp8_decode_table_init()) { fprintf(stderr, "decode table init failed\n"); return 1; }
|
||||
|
||||
int fails = 0; /* FIXED-variant failures: the actual gate */
|
||||
int orig_fail = 0; /* shipped-kernel failures: expected repro of the bug */
|
||||
|
||||
// ---------------- fp8 comp path ----------------
|
||||
{
|
||||
const uint32_t HD = 512u, NROT = 64u;
|
||||
uint64_t xn = (uint64_t)n_fp8 * HD;
|
||||
uint64_t cb = (uint64_t)n_fp8 * DS4_OPP_C_FP8_ROW_BYTES_DEV;
|
||||
uint64_t sn = (uint64_t)n_fp8 * DS4_OPP_C_FP8_BLOCKS_DEV;
|
||||
float *x, *d1, *x2, *d2, *s1, *s2;
|
||||
unsigned char *c1, *c2;
|
||||
CHECK(cudaMalloc(&x, xn * 4)); CHECK(cudaMalloc(&d1, xn * 4));
|
||||
CHECK(cudaMalloc(&x2, xn * 4)); CHECK(cudaMalloc(&d2, xn * 4));
|
||||
CHECK(cudaMalloc(&c1, cb)); CHECK(cudaMalloc(&c2, cb));
|
||||
CHECK(cudaMalloc(&s1, sn * 4)); CHECK(cudaMalloc(&s2, sn * 4));
|
||||
|
||||
fill_fp8_input_kernel<<<n_fp8, 512>>>(x, n_fp8, 7u);
|
||||
fp8_kv_quantize_kernel<<<n_fp8, 64>>>(x, n_fp8, HD, NROT, c1, s1); // -> C1
|
||||
fp8_kv_dequant_rows_kernel<<<n_fp8, 128>>>(d1, c1, s1, n_fp8, HD); // D1
|
||||
CHECK(cudaMemcpy(x2, x, xn * 4, cudaMemcpyDeviceToDevice));
|
||||
fp8_kv_quantize_kernel<<<n_fp8, 64>>>(x2, n_fp8, HD, NROT, c2, s2); // -> C2
|
||||
fp8_kv_dequant_rows_kernel<<<n_fp8, 128>>>(d2, c2, s2, n_fp8, HD); // D2
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
|
||||
printf("FP8 comp path (%u rows, head_dim %u):\n", n_fp8, HD);
|
||||
unsigned long long m_read = report_f32("read-identity D1 vs C1", d1, x, xn, HD);
|
||||
unsigned long long m_idem = report_f32("VALUE idempotence C2 vs C1", x2, x, xn, HD);
|
||||
unsigned long long m_read2 = report_f32("read-identity D2 vs C1", d2, x, xn, HD);
|
||||
report_u8("codes2 vs codes1", c1, c2, cb);
|
||||
report_f32("scales2 vs scales1", s1, s2, sn, DS4_OPP_C_FP8_BLOCKS_DEV);
|
||||
// third iteration: fixed point
|
||||
fp8_kv_quantize_kernel<<<n_fp8, 64>>>(x2, n_fp8, HD, NROT, c2, s2);
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
unsigned long long m_fix = report_f32("fixed-point C3 vs C2(=x2)", x2, x, xn, HD);
|
||||
int ok = (m_read == 0 && m_idem == 0 && m_read2 == 0 && m_fix == 0);
|
||||
printf("REENC-IDEM FP8: %s\n\n", ok ? "PASS" : "FAIL (repro)");
|
||||
if (!ok) orig_fail++;
|
||||
cudaFree(x); cudaFree(d1); cudaFree(x2); cudaFree(d2);
|
||||
cudaFree(c1); cudaFree(c2); cudaFree(s1); cudaFree(s2);
|
||||
}
|
||||
|
||||
// ---------------- fp4 indexer path ----------------
|
||||
{
|
||||
const uint32_t HD = 128u;
|
||||
uint64_t xn = (uint64_t)n_fp4 * HD;
|
||||
uint64_t cb = (uint64_t)n_fp4 * 64u;
|
||||
uint64_t sn = (uint64_t)n_fp4 * 4u;
|
||||
float *x, *d1, *x2, *d2, *s1, *s2;
|
||||
unsigned char *c1, *c2;
|
||||
CHECK(cudaMalloc(&x, xn * 4)); CHECK(cudaMalloc(&d1, xn * 4));
|
||||
CHECK(cudaMalloc(&x2, xn * 4)); CHECK(cudaMalloc(&d2, xn * 4));
|
||||
CHECK(cudaMalloc(&c1, cb)); CHECK(cudaMalloc(&c2, cb));
|
||||
CHECK(cudaMalloc(&s1, sn * 4)); CHECK(cudaMalloc(&s2, sn * 4));
|
||||
|
||||
fill_fp4_input_kernel<<<n_fp4, 128>>>(x, n_fp4, 7u);
|
||||
indexer_hadamard_fp4_kernel<<<n_fp4, 128>>>(x, n_fp4, HD, c1, s1); // QAT -> C1
|
||||
indexer_fp4_dequant_rows_kernel<<<n_fp4, 128>>>(d1, c1, s1, n_fp4, HD); // D1
|
||||
CHECK(cudaMemcpy(x2, x, xn * 4, cudaMemcpyDeviceToDevice));
|
||||
indexer_fp4_encode_rows_kernel<<<n_fp4, 128>>>(x2, n_fp4, HD, c2, s2); // load-path re-encode -> C2
|
||||
indexer_fp4_dequant_rows_kernel<<<n_fp4, 128>>>(d2, c2, s2, n_fp4, HD); // D2
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
|
||||
printf("FP4 indexer path (%u rows, head_dim %u):\n", n_fp4, HD);
|
||||
unsigned long long m_read = report_f32("read-identity D1 vs C1", d1, x, xn, HD);
|
||||
unsigned long long m_idem = report_f32("VALUE idempotence C2 vs C1", x2, x, xn, HD);
|
||||
unsigned long long m_read2 = report_f32("read-identity D2 vs C1", d2, x, xn, HD);
|
||||
report_u8("codes2 vs codes1", c1, c2, cb);
|
||||
report_f32("scales2 vs scales1", s1, s2, sn, 4u);
|
||||
indexer_fp4_encode_rows_kernel<<<n_fp4, 128>>>(x2, n_fp4, HD, c2, s2);
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
unsigned long long m_fix = report_f32("fixed-point C3 vs C2(=x2)", x2, x, xn, HD);
|
||||
int ok = (m_read == 0 && m_idem == 0 && m_read2 == 0 && m_fix == 0);
|
||||
printf("REENC-IDEM FP4: %s\n\n", ok ? "PASS" : "FAIL (repro)");
|
||||
if (!ok) orig_fail++;
|
||||
cudaFree(x); cudaFree(d1); cudaFree(x2); cudaFree(d2);
|
||||
cudaFree(c1); cudaFree(c2); cudaFree(s1); cudaFree(s2);
|
||||
}
|
||||
|
||||
// ---------------- FIXED fp8 comp path ----------------
|
||||
{
|
||||
const uint32_t HD = 512u, NROT = 64u;
|
||||
uint64_t xn = (uint64_t)n_fp8 * HD;
|
||||
uint64_t cb = (uint64_t)n_fp8 * DS4_OPP_C_FP8_ROW_BYTES_DEV;
|
||||
uint64_t sn = (uint64_t)n_fp8 * DS4_OPP_C_FP8_BLOCKS_DEV;
|
||||
float *x, *d1, *x2, *d2, *s1, *s2;
|
||||
unsigned char *c1, *c2;
|
||||
CHECK(cudaMalloc(&x, xn * 4)); CHECK(cudaMalloc(&d1, xn * 4));
|
||||
CHECK(cudaMalloc(&x2, xn * 4)); CHECK(cudaMalloc(&d2, xn * 4));
|
||||
CHECK(cudaMalloc(&c1, cb)); CHECK(cudaMalloc(&c2, cb));
|
||||
CHECK(cudaMalloc(&s1, sn * 4)); CHECK(cudaMalloc(&s2, sn * 4));
|
||||
|
||||
fill_fp8_input_kernel<<<n_fp8, 512>>>(x, n_fp8, 7u);
|
||||
fp8_kv_quantize_kernel_fx<<<n_fp8, 64>>>(x, n_fp8, HD, NROT, c1, s1);
|
||||
fp8_kv_dequant_rows_kernel<<<n_fp8, 128>>>(d1, c1, s1, n_fp8, HD);
|
||||
CHECK(cudaMemcpy(x2, x, xn * 4, cudaMemcpyDeviceToDevice));
|
||||
fp8_kv_quantize_kernel_fx<<<n_fp8, 64>>>(x2, n_fp8, HD, NROT, c2, s2);
|
||||
fp8_kv_dequant_rows_kernel<<<n_fp8, 128>>>(d2, c2, s2, n_fp8, HD);
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
|
||||
printf("FIXED FP8 comp path (frexpf scale + signbit; %u rows):\n", n_fp8);
|
||||
unsigned long long m_read = report_f32("read-identity D1 vs C1", d1, x, xn, HD);
|
||||
unsigned long long m_idem = report_f32("VALUE idempotence C2 vs C1", x2, x, xn, HD);
|
||||
unsigned long long m_read2 = report_f32("read-identity D2 vs C1", d2, x, xn, HD);
|
||||
unsigned long long m_code = report_u8("codes2 vs codes1", c1, c2, cb);
|
||||
unsigned long long m_scale = report_f32("scales2 vs scales1", s1, s2, sn, DS4_OPP_C_FP8_BLOCKS_DEV);
|
||||
fp8_kv_quantize_kernel_fx<<<n_fp8, 64>>>(x2, n_fp8, HD, NROT, c2, s2);
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
unsigned long long m_fix = report_f32("fixed-point C3 vs C2(=x2)", x2, x, xn, HD);
|
||||
int ok = (m_read == 0 && m_idem == 0 && m_read2 == 0 && m_fix == 0);
|
||||
printf("REENC-IDEM FP8-FIXED: %s%s\n\n", ok ? "PASS" : "FAIL",
|
||||
(ok && (m_code || m_scale)) ? " (codes level-shift, values exact)" : "");
|
||||
if (!ok) fails++;
|
||||
cudaFree(x); cudaFree(d1); cudaFree(x2); cudaFree(d2);
|
||||
cudaFree(c1); cudaFree(c2); cudaFree(s1); cudaFree(s2);
|
||||
}
|
||||
|
||||
// ---------------- FIXED fp4 indexer path ----------------
|
||||
{
|
||||
const uint32_t HD = 128u;
|
||||
uint64_t xn = (uint64_t)n_fp4 * HD;
|
||||
uint64_t cb = (uint64_t)n_fp4 * 64u;
|
||||
uint64_t sn = (uint64_t)n_fp4 * 4u;
|
||||
float *x, *d1, *x2, *d2, *s1, *s2;
|
||||
unsigned char *c1, *c2;
|
||||
CHECK(cudaMalloc(&x, xn * 4)); CHECK(cudaMalloc(&d1, xn * 4));
|
||||
CHECK(cudaMalloc(&x2, xn * 4)); CHECK(cudaMalloc(&d2, xn * 4));
|
||||
CHECK(cudaMalloc(&c1, cb)); CHECK(cudaMalloc(&c2, cb));
|
||||
CHECK(cudaMalloc(&s1, sn * 4)); CHECK(cudaMalloc(&s2, sn * 4));
|
||||
|
||||
fill_fp4_input_kernel<<<n_fp4, 128>>>(x, n_fp4, 7u);
|
||||
indexer_hadamard_fp4_kernel_fx<<<n_fp4, 128>>>(x, n_fp4, HD, c1, s1);
|
||||
indexer_fp4_dequant_rows_kernel<<<n_fp4, 128>>>(d1, c1, s1, n_fp4, HD);
|
||||
CHECK(cudaMemcpy(x2, x, xn * 4, cudaMemcpyDeviceToDevice));
|
||||
indexer_fp4_encode_rows_kernel_fx<<<n_fp4, 128>>>(x2, n_fp4, HD, c2, s2);
|
||||
indexer_fp4_dequant_rows_kernel<<<n_fp4, 128>>>(d2, c2, s2, n_fp4, HD);
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
|
||||
printf("FIXED FP4 indexer path (frexpf scale + signbit; %u rows):\n", n_fp4);
|
||||
unsigned long long m_read = report_f32("read-identity D1 vs C1", d1, x, xn, HD);
|
||||
unsigned long long m_idem = report_f32("VALUE idempotence C2 vs C1", x2, x, xn, HD);
|
||||
unsigned long long m_read2 = report_f32("read-identity D2 vs C1", d2, x, xn, HD);
|
||||
unsigned long long m_code = report_u8("codes2 vs codes1", c1, c2, cb);
|
||||
unsigned long long m_scale = report_f32("scales2 vs scales1", s1, s2, sn, 4u);
|
||||
indexer_fp4_encode_rows_kernel_fx<<<n_fp4, 128>>>(x2, n_fp4, HD, c2, s2);
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
unsigned long long m_fix = report_f32("fixed-point C3 vs C2(=x2)", x2, x, xn, HD);
|
||||
int ok = (m_read == 0 && m_idem == 0 && m_read2 == 0 && m_fix == 0);
|
||||
printf("REENC-IDEM FP4-FIXED: %s%s\n\n", ok ? "PASS" : "FAIL",
|
||||
(ok && (m_code || m_scale)) ? " (codes level-shift, values exact)" : "");
|
||||
if (!ok) fails++;
|
||||
cudaFree(x); cudaFree(d1); cudaFree(x2); cudaFree(d2);
|
||||
cudaFree(c1); cudaFree(c2); cudaFree(s1); cudaFree(s2);
|
||||
}
|
||||
|
||||
// ---------------- fast-math boundary scan ----------------
|
||||
{
|
||||
float *d_out;
|
||||
CHECK(cudaMalloc(&d_out, 61 * 8 * sizeof(float)));
|
||||
boundary_scan_kernel<<<1, 64>>>(d_out);
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
float out[61 * 8];
|
||||
CHECK(cudaMemcpy(out, d_out, sizeof(out), cudaMemcpyDeviceToHost));
|
||||
cudaFree(d_out);
|
||||
int devs = 0, ulps = 0;
|
||||
printf("Fast-math boundary scan (exact-pow2 amax inputs, k=-30..30):\n");
|
||||
for (int idx = 0; idx < 61; idx++) {
|
||||
int k = idx - 30;
|
||||
float p2k = ldexpf(1.0f, k);
|
||||
float p2k1 = ldexpf(1.0f, k - 1);
|
||||
// expectations from exact real arithmetic; skip cases where the
|
||||
// 1e-4 amax floor binds instead. `ulp` cases (one-ulp-off amax)
|
||||
// probe approximation sensitivity, not a re-encode reachable
|
||||
// state -- tallied separately as INFO.
|
||||
struct { const char *name; float got, want; int skip, ulp; } cases[8] = {
|
||||
{"fp8 448*2^k ", out[idx*8+0], p2k, 448.0f*p2k < 1.0e-4f, 0},
|
||||
{"fp8 224*2^k ", out[idx*8+1], p2k1, 224.0f*p2k < 1.0e-4f, 0},
|
||||
{"fp8 448u ", out[idx*8+2], 2.0f*p2k, 448.0f*p2k < 1.0e-4f, 1},
|
||||
{"fp8 448d ", out[idx*8+3], p2k, 448.0f*p2k < 1.0e-4f, 1},
|
||||
{"fp4 6*2^k ", out[idx*8+4], p2k, 0, 0},
|
||||
{"fp4 3*2^k ", out[idx*8+5], p2k1, 0, 0},
|
||||
{"fp4 1.5*2^k ", out[idx*8+6], ldexpf(1.0f, k-2), 0, 0},
|
||||
{"fp4 6u ", out[idx*8+7], 2.0f*p2k, 0, 1},
|
||||
};
|
||||
for (int c = 0; c < 8; c++) {
|
||||
if (cases[c].skip) continue;
|
||||
if (cases[c].got != cases[c].want) {
|
||||
printf(" %s k=%3d %s got=%.9g want=%.9g\n",
|
||||
cases[c].ulp ? "ulp-info " : "DEVIATION",
|
||||
k, cases[c].name, cases[c].got, cases[c].want);
|
||||
if (cases[c].ulp) ulps++; else devs++;
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("boundary scan: exact-case deviations=%d%s, ulp-info=%d\n\n", devs,
|
||||
devs ? " (MECHANISM CANDIDATE -- fast-math log2f/ceilf at pow2)" : "", ulps);
|
||||
}
|
||||
|
||||
printf("PROTO-KV-REENC-IDEM: shipped kernels %s, fixed variants %s\n",
|
||||
orig_fail ? "REPRODUCE the bug" : "PASS (bug not reproduced?!)",
|
||||
fails == 0 ? "PASS" : "FAIL");
|
||||
return fails != 0;
|
||||
}
|
||||
@@ -0,0 +1,537 @@
|
||||
/* proto_m2_comp.cu — M2-Inc5 prototype: fused compressor pair+store.
|
||||
* Replaces the per-compressor-event 5-launch chain
|
||||
* matmul_f16_splitk(kv) + combine + matmul_f16_splitk(sc) + combine
|
||||
* + compressor_store
|
||||
* with ONE cooperative kernel, at the three decode widths
|
||||
* out 1024 (primary ratio-4), out 512 (primary ratio-2),
|
||||
* out 256 (indexer ratio-4, head_dim 128); in_dim 4096, ksplit 2/4/8.
|
||||
*
|
||||
* Gate: kv_cur/sc_cur/state_kv/state_score all BIT-IDENTICAL to the ref
|
||||
* chain across widths x ape f16/f32 x pos0 sweep; capture==eager; timing.
|
||||
*
|
||||
* Bit-exactness: phase 1 replicates the ref split-K tile exactly like
|
||||
* M2-Inc3 (proto_m2_router.cu), generalized to seg in {2048,1024,512}:
|
||||
* a lane's A = seg/256 accumulators hold chunks lane+32a (identical values
|
||||
* at identical lanes to ref warps a), each reduced by the verbatim
|
||||
* warp_sum_f32; the ref's cross-warp shfl_down tree over [s0..s7,0-padded]
|
||||
* imposes the parenthesization
|
||||
* A=2: s0+s1
|
||||
* A=4: (s0+s2)+(s1+s3)
|
||||
* A=8: ((s0+s4)+(s2+s6))+((s1+s5)+(s3+s7))
|
||||
* (identical up to x+0.0f==x on exact zeros). The tail combines partials
|
||||
* in kseg order (verbatim combine kernel) and applies the verbatim store
|
||||
* body (state copy + ape add) to the just-combined values.
|
||||
*
|
||||
* Build (GB10):
|
||||
* nvcc -O3 --use_fast_math -std=c++17 -arch=sm_121 proto_m2_comp.cu -o proto_m2_comp
|
||||
*/
|
||||
#include <cooperative_groups.h>
|
||||
#include <cuda_fp16.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
|
||||
#define CHECK(x) do { cudaError_t e_ = (x); if (e_ != cudaSuccess) { \
|
||||
fprintf(stderr, "CUDA error %s at %s:%d\n", cudaGetErrorString(e_), __FILE__, __LINE__); exit(1); } } while (0)
|
||||
|
||||
#define N_IN 4096u
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Vendored production device functions + kernels (ds4_cuda.cu, verbatim) */
|
||||
|
||||
struct ds4_decode_scalars {
|
||||
uint32_t pos0;
|
||||
uint32_t raw_row;
|
||||
uint32_t raw_start;
|
||||
uint32_t n_raw;
|
||||
uint32_t n_comp;
|
||||
uint32_t emit_phase;
|
||||
uint32_t comp_row;
|
||||
uint32_t index_row;
|
||||
uint32_t flags;
|
||||
uint32_t token;
|
||||
};
|
||||
|
||||
__device__ static float warp_sum_f32(float v) {
|
||||
for (int offset = 16; offset > 0; offset >>= 1) {
|
||||
v += __shfl_down_sync(0xffffffffu, v, offset);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
__device__ static float block_reduce_sum_256(float v) {
|
||||
v = warp_sum_f32(v);
|
||||
__shared__ float s[8];
|
||||
const uint32_t lane = threadIdx.x & 31u;
|
||||
const uint32_t warp = threadIdx.x >> 5u;
|
||||
if (lane == 0u) s[warp] = v;
|
||||
__syncthreads();
|
||||
float r = 0.0f;
|
||||
if (warp == 0u) {
|
||||
r = (lane < 8u) ? s[lane] : 0.0f;
|
||||
r += __shfl_down_sync(0xffffffffu, r, 4);
|
||||
r += __shfl_down_sync(0xffffffffu, r, 2);
|
||||
r += __shfl_down_sync(0xffffffffu, r, 1);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
__global__ static void matmul_f16_splitk_kernel(
|
||||
float *partial,
|
||||
const __half *w,
|
||||
const float *x,
|
||||
uint64_t in_dim,
|
||||
uint64_t out_dim,
|
||||
uint32_t ksplit,
|
||||
int vec_ok) {
|
||||
const uint64_t row = (uint64_t)blockIdx.x;
|
||||
const uint32_t kseg = blockIdx.y;
|
||||
if (row >= out_dim) return;
|
||||
uint64_t seg = (in_dim + (uint64_t)ksplit - 1u) / (uint64_t)ksplit;
|
||||
seg = (seg + 7u) & ~(uint64_t)7u;
|
||||
const uint64_t k0 = (uint64_t)kseg * seg;
|
||||
if (k0 >= in_dim) {
|
||||
if (threadIdx.x == 0u) partial[row * (uint64_t)ksplit + (uint64_t)kseg] = 0.0f;
|
||||
return;
|
||||
}
|
||||
uint64_t k1 = k0 + seg;
|
||||
if (k1 > in_dim) k1 = in_dim;
|
||||
const __half *wr = w + row * in_dim;
|
||||
const uint32_t tid = threadIdx.x;
|
||||
const uint32_t nt = blockDim.x;
|
||||
float sum = 0.0f;
|
||||
if (vec_ok) {
|
||||
const uint64_t vend = k0 + ((k1 - k0) & ~(uint64_t)7u);
|
||||
for (uint64_t i = k0 + (uint64_t)tid * 8u; i < vend; i += (uint64_t)nt * 8u) {
|
||||
const uint4 wv = *reinterpret_cast<const uint4 *>(wr + i);
|
||||
const float4 xa = *reinterpret_cast<const float4 *>(x + i);
|
||||
const float4 xb = *reinterpret_cast<const float4 *>(x + i + 4);
|
||||
const __half2 *h = reinterpret_cast<const __half2 *>(&wv);
|
||||
const float2 w0 = __half22float2(h[0]);
|
||||
const float2 w1 = __half22float2(h[1]);
|
||||
const float2 w2 = __half22float2(h[2]);
|
||||
const float2 w3 = __half22float2(h[3]);
|
||||
sum += w0.x * xa.x + w0.y * xa.y + w1.x * xa.z + w1.y * xa.w
|
||||
+ w2.x * xb.x + w2.y * xb.y + w3.x * xb.z + w3.y * xb.w;
|
||||
}
|
||||
for (uint64_t i = vend + tid; i < k1; i += nt) sum += __half2float(wr[i]) * x[i];
|
||||
} else {
|
||||
for (uint64_t i = k0 + tid; i < k1; i += nt) sum += __half2float(wr[i]) * x[i];
|
||||
}
|
||||
sum = block_reduce_sum_256(sum);
|
||||
if (threadIdx.x == 0u) partial[row * (uint64_t)ksplit + (uint64_t)kseg] = sum;
|
||||
}
|
||||
|
||||
__global__ static void matmul_f16_splitk_combine_kernel(
|
||||
float *out,
|
||||
const float *partial,
|
||||
uint64_t out_dim,
|
||||
uint32_t ksplit) {
|
||||
const uint64_t row = (uint64_t)blockIdx.x * (uint64_t)blockDim.x + (uint64_t)threadIdx.x;
|
||||
if (row >= out_dim) return;
|
||||
const float *p = partial + row * (uint64_t)ksplit;
|
||||
float s = 0.0f;
|
||||
for (uint32_t k = 0u; k < ksplit; k++) s += p[k];
|
||||
out[row] = s;
|
||||
}
|
||||
|
||||
__device__ static float model_scalar_dev(const void *base, uint64_t offset, uint32_t type, uint64_t idx) {
|
||||
const char *p = (const char *)base + offset;
|
||||
if (type == 1u) return __half2float(((const __half *)p)[idx]);
|
||||
return ((const float *)p)[idx];
|
||||
}
|
||||
|
||||
__global__ static void compressor_store_kernel(
|
||||
const float *kv,
|
||||
const float *sc,
|
||||
float *state_kv,
|
||||
float *state_score,
|
||||
const void *model_map,
|
||||
uint64_t ape_offset,
|
||||
uint32_t ape_type,
|
||||
uint32_t head_dim,
|
||||
uint32_t ratio,
|
||||
uint32_t pos0,
|
||||
uint32_t n_tokens,
|
||||
const struct ds4_decode_scalars * __restrict__ s_override) {
|
||||
if (s_override) {
|
||||
pos0 = s_override->pos0;
|
||||
}
|
||||
uint32_t coff = ratio == 4u ? 2u : 1u;
|
||||
uint32_t width = coff * head_dim;
|
||||
uint64_t gid = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x;
|
||||
uint64_t n = (uint64_t)n_tokens * width;
|
||||
if (gid >= n) return;
|
||||
uint32_t t = gid / width;
|
||||
uint32_t j = gid - (uint64_t)t * width;
|
||||
uint32_t pos_mod = (pos0 + t) % ratio;
|
||||
uint32_t dst_row = ratio == 4u ? ratio + pos_mod : pos_mod;
|
||||
state_kv[(uint64_t)dst_row * width + j] = kv[(uint64_t)t * width + j];
|
||||
state_score[(uint64_t)dst_row * width + j] =
|
||||
sc[(uint64_t)t * width + j] + model_scalar_dev(model_map, ape_offset, ape_type, (uint64_t)pos_mod * width + j);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* FUSED: pair matmul + combine + store as one cooperative kernel.
|
||||
* Template KS = ksplit in {2,4,8}; SEG = 4096/KS; A = SEG/256 accumulators
|
||||
* per lane. */
|
||||
|
||||
template <uint32_t KS>
|
||||
__global__ static void comp_pair_store_fused_kernel(
|
||||
float *kv_cur, /* [width] */
|
||||
float *sc_cur, /* [width] */
|
||||
float *partials, /* [2*width*KS] dedicated scratch */
|
||||
float *state_kv,
|
||||
float *state_score,
|
||||
const __half *w_kv, /* [width][4096] */
|
||||
const __half *w_sc, /* [width][4096] */
|
||||
const float *x, /* [4096] */
|
||||
const void *ape_map,
|
||||
uint64_t ape_offset,
|
||||
uint32_t ape_type,
|
||||
uint32_t head_dim,
|
||||
uint32_t ratio,
|
||||
uint32_t width, /* == coff*head_dim == out_dim */
|
||||
uint32_t pos0,
|
||||
const struct ds4_decode_scalars * __restrict__ s_override) {
|
||||
constexpr uint32_t SEG = N_IN / KS;
|
||||
constexpr uint32_t A = SEG / 256u;
|
||||
const uint32_t lane = threadIdx.x & 31u;
|
||||
const uint32_t warp = threadIdx.x >> 5u;
|
||||
|
||||
/* Phase 1: one warp per (matmul m, row, kseg) tile. */
|
||||
const uint32_t tiles_per_m = width * KS;
|
||||
const uint32_t nwarp = gridDim.x * (blockDim.x >> 5u);
|
||||
for (uint32_t t = blockIdx.x * (blockDim.x >> 5u) + warp;
|
||||
t < 2u * tiles_per_m; t += nwarp) {
|
||||
const uint32_t m = t < tiles_per_m ? 0u : 1u;
|
||||
const uint32_t tt = m ? t - tiles_per_m : t;
|
||||
const uint32_t row = tt / KS;
|
||||
const uint32_t kseg = tt % KS;
|
||||
const uint64_t k0 = (uint64_t)kseg * SEG;
|
||||
const __half *wr = (m ? w_sc : w_kv) + (uint64_t)row * N_IN;
|
||||
float s[A];
|
||||
#pragma unroll
|
||||
for (uint32_t a = 0; a < A; a++) {
|
||||
const uint64_t i = k0 + ((uint64_t)lane + 32u * a) * 8u;
|
||||
const uint4 wv = *reinterpret_cast<const uint4 *>(wr + i);
|
||||
const float4 xa = *reinterpret_cast<const float4 *>(x + i);
|
||||
const float4 xb = *reinterpret_cast<const float4 *>(x + i + 4);
|
||||
const __half2 *h = reinterpret_cast<const __half2 *>(&wv);
|
||||
const float2 w0 = __half22float2(h[0]);
|
||||
const float2 w1 = __half22float2(h[1]);
|
||||
const float2 w2 = __half22float2(h[2]);
|
||||
const float2 w3 = __half22float2(h[3]);
|
||||
float sum = 0.0f;
|
||||
sum += w0.x * xa.x + w0.y * xa.y + w1.x * xa.z + w1.y * xa.w
|
||||
+ w2.x * xb.x + w2.y * xb.y + w3.x * xb.z + w3.y * xb.w;
|
||||
s[a] = warp_sum_f32(sum);
|
||||
}
|
||||
/* ref block_reduce_sum_256 cross-warp tree over [s0..s(A-1), 0...]:
|
||||
* shfl_down 4,2,1 -> fixed parenthesization (zero-adds are identity). */
|
||||
float partial;
|
||||
if (KS == 8u) { /* A == 2 */
|
||||
partial = s[0] + s[1 % A];
|
||||
} else if (KS == 4u) { /* A == 4 */
|
||||
partial = (s[0] + s[2 % A]) + (s[1 % A] + s[3 % A]);
|
||||
} else { /* KS == 2, A == 8 */
|
||||
partial = ((s[0] + s[4 % A]) + (s[2 % A] + s[6 % A]))
|
||||
+ ((s[1 % A] + s[5 % A]) + (s[3 % A] + s[7 % A]));
|
||||
}
|
||||
if (lane == 0u) partials[(uint64_t)m * tiles_per_m + (uint64_t)row * KS + kseg] = partial;
|
||||
}
|
||||
|
||||
cooperative_groups::this_grid().sync();
|
||||
|
||||
/* Phase 2: combine (kseg ascending, verbatim) + verbatim store body. */
|
||||
const uint32_t gid = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
const uint32_t n2 = 2u * width;
|
||||
if (gid >= n2) return;
|
||||
const uint32_t m = gid < width ? 0u : 1u;
|
||||
const uint32_t row = m ? gid - width : gid;
|
||||
const float *p = partials + (uint64_t)m * tiles_per_m + (uint64_t)row * KS;
|
||||
float s = 0.0f;
|
||||
for (uint32_t k = 0u; k < KS; k++) s += p[k];
|
||||
uint32_t p0 = s_override ? s_override->pos0 : pos0;
|
||||
const uint32_t pos_mod = p0 % ratio;
|
||||
const uint32_t dst_row = ratio == 4u ? ratio + pos_mod : pos_mod;
|
||||
if (m == 0u) {
|
||||
kv_cur[row] = s;
|
||||
state_kv[(uint64_t)dst_row * width + row] = s;
|
||||
} else {
|
||||
sc_cur[row] = s;
|
||||
state_score[(uint64_t)dst_row * width + row] =
|
||||
s + model_scalar_dev(ape_map, ape_offset, ape_type, (uint64_t)pos_mod * width + row);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Host harness */
|
||||
|
||||
struct cfg_t {
|
||||
const char *name;
|
||||
uint32_t head_dim, ratio, width, ksplit;
|
||||
};
|
||||
static const cfg_t CFGS[3] = {
|
||||
{ "primary-r4 (1024,k2)", 512, 4, 1024, 2 },
|
||||
{ "primary-r2 (512,k4)", 512, 2, 512, 4 },
|
||||
{ "index-r4 (256,k8)", 128, 4, 256, 8 },
|
||||
};
|
||||
|
||||
static void fill_rand(float *p, size_t n, unsigned seed, float scale) {
|
||||
unsigned s = seed;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
s = s * 1664525u + 1013904223u;
|
||||
p[i] = ((float)(s >> 8) / (float)(1u << 24) - 0.5f) * 2.0f * scale;
|
||||
}
|
||||
}
|
||||
|
||||
struct bufs {
|
||||
__half *w_kv, *w_sc;
|
||||
float *x, *kv_cur, *sc_cur, *state_kv, *state_score, *part_ref, *part_fu;
|
||||
char *ape; /* f32 or f16 depending on ape_type */
|
||||
ds4_decode_scalars *scalars;
|
||||
};
|
||||
|
||||
static void *fused_ptr(uint32_t ks) {
|
||||
if (ks == 2u) return (void *)comp_pair_store_fused_kernel<2u>;
|
||||
if (ks == 4u) return (void *)comp_pair_store_fused_kernel<4u>;
|
||||
return (void *)comp_pair_store_fused_kernel<8u>;
|
||||
}
|
||||
|
||||
static void run_ref(const bufs &b, const cfg_t &c, uint32_t ape_type, uint32_t pos0, int via_scalars) {
|
||||
dim3 sg1(c.width, c.ksplit, 1);
|
||||
matmul_f16_splitk_kernel<<<sg1, 256>>>(b.part_ref, b.w_kv, b.x, N_IN, c.width, c.ksplit, 1);
|
||||
matmul_f16_splitk_combine_kernel<<<(c.width + 255u) / 256u, 256>>>(b.kv_cur, b.part_ref, c.width, c.ksplit);
|
||||
matmul_f16_splitk_kernel<<<sg1, 256>>>(b.part_ref, b.w_sc, b.x, N_IN, c.width, c.ksplit, 1);
|
||||
matmul_f16_splitk_combine_kernel<<<(c.width + 255u) / 256u, 256>>>(b.sc_cur, b.part_ref, c.width, c.ksplit);
|
||||
compressor_store_kernel<<<(c.width + 255u) / 256u, 256>>>(
|
||||
b.kv_cur, b.sc_cur, b.state_kv, b.state_score,
|
||||
b.ape, 0, ape_type, c.head_dim, c.ratio,
|
||||
via_scalars ? 0xdeadbeefu : pos0, 1,
|
||||
via_scalars ? b.scalars : NULL);
|
||||
CHECK(cudaGetLastError());
|
||||
}
|
||||
|
||||
static int run_fused(const bufs &b, const cfg_t &c, uint32_t ape_type, uint32_t pos0, int via_scalars,
|
||||
unsigned grid_blocks, cudaStream_t stream) {
|
||||
float *kv_cur = b.kv_cur, *sc_cur = b.sc_cur, *partials = b.part_fu;
|
||||
float *state_kv = b.state_kv, *state_score = b.state_score, *x = b.x;
|
||||
const __half *w_kv = b.w_kv, *w_sc = b.w_sc;
|
||||
const void *ape = b.ape;
|
||||
uint64_t ape_off = 0;
|
||||
uint32_t at = ape_type, hd = c.head_dim, ra = c.ratio, wi = c.width;
|
||||
uint32_t p0 = via_scalars ? 0xdeadbeefu : pos0;
|
||||
const ds4_decode_scalars *sc_ptr = via_scalars ? b.scalars : NULL;
|
||||
void *args[] = {
|
||||
(void *)&kv_cur, (void *)&sc_cur, (void *)&partials,
|
||||
(void *)&state_kv, (void *)&state_score,
|
||||
(void *)&w_kv, (void *)&w_sc, (void *)&x,
|
||||
(void *)&ape, (void *)&ape_off, (void *)&at,
|
||||
(void *)&hd, (void *)&ra, (void *)&wi, (void *)&p0, (void *)&sc_ptr };
|
||||
cudaError_t err = cudaLaunchCooperativeKernel(
|
||||
fused_ptr(c.ksplit), dim3(grid_blocks, 1, 1), dim3(256, 1, 1), args, 0, stream);
|
||||
if (err != cudaSuccess) {
|
||||
fprintf(stderr, "coop launch failed (%s, %u blk): %s\n", c.name, grid_blocks, cudaGetErrorString(err));
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int dev = 0, coop = 0, n_sm = 0;
|
||||
CHECK(cudaGetDevice(&dev));
|
||||
CHECK(cudaDeviceGetAttribute(&coop, cudaDevAttrCooperativeLaunch, dev));
|
||||
CHECK(cudaDeviceGetAttribute(&n_sm, cudaDevAttrMultiProcessorCount, dev));
|
||||
printf("coop=%d n_sm=%d\n", coop, n_sm);
|
||||
if (!coop) return 1;
|
||||
unsigned cap[3];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int bps = 0;
|
||||
CHECK(cudaOccupancyMaxActiveBlocksPerMultiprocessor(&bps, fused_ptr(CFGS[i].ksplit), 256, 0));
|
||||
cap[i] = (unsigned)(bps * n_sm);
|
||||
printf(" %s: blocks/sm=%d cap=%u\n", CFGS[i].name, bps, cap[i]);
|
||||
}
|
||||
|
||||
const uint32_t WMAX = 1024;
|
||||
bufs b = {};
|
||||
CHECK(cudaMalloc(&b.w_kv, (size_t)WMAX * N_IN * sizeof(__half)));
|
||||
CHECK(cudaMalloc(&b.w_sc, (size_t)WMAX * N_IN * sizeof(__half)));
|
||||
CHECK(cudaMalloc(&b.x, N_IN * sizeof(float)));
|
||||
CHECK(cudaMalloc(&b.kv_cur, WMAX * sizeof(float)));
|
||||
CHECK(cudaMalloc(&b.sc_cur, WMAX * sizeof(float)));
|
||||
CHECK(cudaMalloc(&b.state_kv, 8u * WMAX * sizeof(float)));
|
||||
CHECK(cudaMalloc(&b.state_score, 8u * WMAX * sizeof(float)));
|
||||
CHECK(cudaMalloc(&b.part_ref, (size_t)WMAX * 8u * sizeof(float)));
|
||||
CHECK(cudaMalloc(&b.part_fu, 2u * (size_t)WMAX * 8u * sizeof(float)));
|
||||
CHECK(cudaMalloc(&b.ape, 4u * WMAX * sizeof(float)));
|
||||
CHECK(cudaMalloc(&b.scalars, sizeof(ds4_decode_scalars)));
|
||||
|
||||
float *hbuf = (float *)malloc((size_t)WMAX * N_IN * sizeof(float));
|
||||
__half *hh = (__half *)malloc((size_t)WMAX * N_IN * sizeof(__half));
|
||||
float *hape = (float *)malloc(4u * WMAX * sizeof(float));
|
||||
__half *hape16 = (__half *)malloc(4u * WMAX * sizeof(__half));
|
||||
|
||||
const size_t OUTB = WMAX * sizeof(float);
|
||||
const size_t STB = 8u * WMAX * sizeof(float);
|
||||
float *ref_out = (float *)malloc(2 * OUTB + 2 * STB);
|
||||
float *fu_out = (float *)malloc(2 * OUTB + 2 * STB);
|
||||
|
||||
int total_bad = 0, n_cases = 0;
|
||||
const unsigned grids[] = { 48u, 96u, 128u };
|
||||
const uint32_t poss[] = { 0u, 1u, 2u, 3u, 7u, 1023u };
|
||||
for (int ci = 0; ci < 3; ci++) {
|
||||
const cfg_t &c = CFGS[ci];
|
||||
for (unsigned seed = 1; seed <= 3; seed++) {
|
||||
fill_rand(hbuf, (size_t)c.width * N_IN, seed * 7919u + ci, 0.05f);
|
||||
for (size_t i = 0; i < (size_t)c.width * N_IN; i++) hh[i] = __float2half(hbuf[i]);
|
||||
CHECK(cudaMemcpy(b.w_kv, hh, (size_t)c.width * N_IN * sizeof(__half), cudaMemcpyHostToDevice));
|
||||
fill_rand(hbuf, (size_t)c.width * N_IN, seed * 104729u + ci, 0.05f);
|
||||
for (size_t i = 0; i < (size_t)c.width * N_IN; i++) hh[i] = __float2half(hbuf[i]);
|
||||
CHECK(cudaMemcpy(b.w_sc, hh, (size_t)c.width * N_IN * sizeof(__half), cudaMemcpyHostToDevice));
|
||||
fill_rand(hbuf, N_IN, seed * 31u, 1.0f);
|
||||
CHECK(cudaMemcpy(b.x, hbuf, N_IN * sizeof(float), cudaMemcpyHostToDevice));
|
||||
fill_rand(hape, (size_t)c.ratio * c.width, seed * 71u, 0.5f);
|
||||
for (size_t i = 0; i < (size_t)c.ratio * c.width; i++) hape16[i] = __float2half(hape[i]);
|
||||
for (uint32_t ape_type = 0; ape_type <= 1; ape_type++) {
|
||||
if (ape_type == 0) CHECK(cudaMemcpy(b.ape, hape, (size_t)c.ratio * c.width * sizeof(float), cudaMemcpyHostToDevice));
|
||||
else CHECK(cudaMemcpy(b.ape, hape16, (size_t)c.ratio * c.width * sizeof(__half), cudaMemcpyHostToDevice));
|
||||
for (uint32_t pi = 0; pi < 6; pi++) {
|
||||
const uint32_t pos0 = poss[pi];
|
||||
const int via_scalars = (int)(pi & 1u);
|
||||
ds4_decode_scalars sc = {};
|
||||
sc.pos0 = pos0;
|
||||
CHECK(cudaMemcpy(b.scalars, &sc, sizeof(sc), cudaMemcpyHostToDevice));
|
||||
/* ref */
|
||||
CHECK(cudaMemset(b.kv_cur, 0xCB, OUTB));
|
||||
CHECK(cudaMemset(b.sc_cur, 0xCB, OUTB));
|
||||
CHECK(cudaMemset(b.state_kv, 0xCB, STB));
|
||||
CHECK(cudaMemset(b.state_score, 0xCB, STB));
|
||||
run_ref(b, c, ape_type, pos0, via_scalars);
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
CHECK(cudaMemcpy(ref_out, b.kv_cur, OUTB, cudaMemcpyDeviceToHost));
|
||||
CHECK(cudaMemcpy(ref_out + WMAX, b.sc_cur, OUTB, cudaMemcpyDeviceToHost));
|
||||
CHECK(cudaMemcpy(ref_out + 2 * WMAX, b.state_kv, STB, cudaMemcpyDeviceToHost));
|
||||
CHECK(cudaMemcpy(ref_out + 10 * WMAX, b.state_score, STB, cudaMemcpyDeviceToHost));
|
||||
for (unsigned g : grids) {
|
||||
if (g > cap[ci]) continue;
|
||||
CHECK(cudaMemset(b.kv_cur, 0xCB, OUTB));
|
||||
CHECK(cudaMemset(b.sc_cur, 0xCB, OUTB));
|
||||
CHECK(cudaMemset(b.state_kv, 0xCB, STB));
|
||||
CHECK(cudaMemset(b.state_score, 0xCB, STB));
|
||||
if (!run_fused(b, c, ape_type, pos0, via_scalars, g, 0)) return 1;
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
CHECK(cudaMemcpy(fu_out, b.kv_cur, OUTB, cudaMemcpyDeviceToHost));
|
||||
CHECK(cudaMemcpy(fu_out + WMAX, b.sc_cur, OUTB, cudaMemcpyDeviceToHost));
|
||||
CHECK(cudaMemcpy(fu_out + 2 * WMAX, b.state_kv, STB, cudaMemcpyDeviceToHost));
|
||||
CHECK(cudaMemcpy(fu_out + 10 * WMAX, b.state_score, STB, cudaMemcpyDeviceToHost));
|
||||
n_cases++;
|
||||
if (memcmp(ref_out, fu_out, 2 * OUTB + 2 * STB)) {
|
||||
total_bad++;
|
||||
printf("MISMATCH %s seed%u ape%u pos%u g%u\n", c.name, seed, ape_type, pos0, g);
|
||||
for (int i = 0; i < 6; i++)
|
||||
printf(" kv[%d] ref=%.9g fu=%.9g sc ref=%.9g fu=%.9g\n", i,
|
||||
ref_out[i], fu_out[i], ref_out[WMAX + i], fu_out[WMAX + i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("parity: %d/%d cases bit-identical\n", n_cases - total_bad, n_cases);
|
||||
if (total_bad) return 1;
|
||||
|
||||
/* capture/replay bit-identity (one config) */
|
||||
{
|
||||
const cfg_t &c = CFGS[2];
|
||||
const unsigned g = cap[2] < 128u ? cap[2] : 128u;
|
||||
ds4_decode_scalars sc = {};
|
||||
sc.pos0 = 5;
|
||||
CHECK(cudaMemcpy(b.scalars, &sc, sizeof(sc), cudaMemcpyHostToDevice));
|
||||
if (!run_fused(b, c, 1, 0, 1, g, 0)) return 1;
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
CHECK(cudaMemcpy(ref_out, b.kv_cur, OUTB, cudaMemcpyDeviceToHost));
|
||||
CHECK(cudaMemcpy(ref_out + 2 * WMAX, b.state_score, STB, cudaMemcpyDeviceToHost));
|
||||
cudaStream_t cs;
|
||||
CHECK(cudaStreamCreate(&cs));
|
||||
cudaGraph_t graph;
|
||||
cudaGraphExec_t gexec;
|
||||
CHECK(cudaStreamBeginCapture(cs, cudaStreamCaptureModeGlobal));
|
||||
if (!run_fused(b, c, 1, 0, 1, g, cs)) return 1;
|
||||
CHECK(cudaStreamEndCapture(cs, &graph));
|
||||
CHECK(cudaGraphInstantiate(&gexec, graph, NULL, NULL, 0));
|
||||
for (int r = 0; r < 3; r++) {
|
||||
CHECK(cudaMemset(b.kv_cur, 0xCB, OUTB));
|
||||
CHECK(cudaGraphLaunch(gexec, cs));
|
||||
CHECK(cudaStreamSynchronize(cs));
|
||||
CHECK(cudaMemcpy(fu_out, b.kv_cur, OUTB, cudaMemcpyDeviceToHost));
|
||||
CHECK(cudaMemcpy(fu_out + 2 * WMAX, b.state_score, STB, cudaMemcpyDeviceToHost));
|
||||
if (memcmp(ref_out, fu_out, OUTB) || memcmp(ref_out + 2 * WMAX, fu_out + 2 * WMAX, STB)) {
|
||||
printf("capture-replay MISMATCH\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
printf("capture/replay: bit-identical (3 replays)\n");
|
||||
CHECK(cudaGraphExecDestroy(gexec));
|
||||
CHECK(cudaGraphDestroy(graph));
|
||||
CHECK(cudaStreamDestroy(cs));
|
||||
}
|
||||
|
||||
/* timing: rotating 16-layer weight sets (L2-defeated) per config */
|
||||
{
|
||||
const int NL = 16, ITERS = 2000, WARM = 200;
|
||||
cudaEvent_t e0, e1;
|
||||
CHECK(cudaEventCreate(&e0));
|
||||
CHECK(cudaEventCreate(&e1));
|
||||
float ms;
|
||||
for (int ci = 0; ci < 3; ci++) {
|
||||
const cfg_t &c = CFGS[ci];
|
||||
__half *wl[NL][2];
|
||||
for (int l = 0; l < NL; l++)
|
||||
for (int m = 0; m < 2; m++) {
|
||||
CHECK(cudaMalloc(&wl[l][m], (size_t)c.width * N_IN * sizeof(__half)));
|
||||
CHECK(cudaMemcpy(wl[l][m], m ? b.w_sc : b.w_kv,
|
||||
(size_t)c.width * N_IN * sizeof(__half), cudaMemcpyDeviceToDevice));
|
||||
}
|
||||
bufs bl = b;
|
||||
for (int it = 0; it < WARM; it++) {
|
||||
bl.w_kv = wl[it % NL][0]; bl.w_sc = wl[it % NL][1];
|
||||
run_ref(bl, c, 1, 3, 1);
|
||||
}
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
CHECK(cudaEventRecord(e0));
|
||||
for (int it = 0; it < ITERS; it++) {
|
||||
bl.w_kv = wl[it % NL][0]; bl.w_sc = wl[it % NL][1];
|
||||
run_ref(bl, c, 1, 3, 1);
|
||||
}
|
||||
CHECK(cudaEventRecord(e1));
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
CHECK(cudaEventElapsedTime(&ms, e0, e1));
|
||||
printf("%s ref 5-launch : %8.3f us/event\n", c.name, ms * 1000.0f / ITERS);
|
||||
for (unsigned g : grids) {
|
||||
if (g > cap[ci]) continue;
|
||||
for (int it = 0; it < WARM; it++) {
|
||||
bl.w_kv = wl[it % NL][0]; bl.w_sc = wl[it % NL][1];
|
||||
run_fused(bl, c, 1, 3, 1, g, 0);
|
||||
}
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
CHECK(cudaEventRecord(e0));
|
||||
for (int it = 0; it < ITERS; it++) {
|
||||
bl.w_kv = wl[it % NL][0]; bl.w_sc = wl[it % NL][1];
|
||||
run_fused(bl, c, 1, 3, 1, g, 0);
|
||||
}
|
||||
CHECK(cudaEventRecord(e1));
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
CHECK(cudaEventElapsedTime(&ms, e0, e1));
|
||||
printf("%s fused g=%3u : %8.3f us/event\n", c.name, g, ms * 1000.0f / ITERS);
|
||||
}
|
||||
for (int l = 0; l < NL; l++)
|
||||
for (int m = 0; m < 2; m++) CHECK(cudaFree(wl[l][m]));
|
||||
}
|
||||
}
|
||||
|
||||
printf("PROTO_M2_COMP OK\n");
|
||||
return 0;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,741 @@
|
||||
// proto_m2_q2k.cu — M2 megakernel program, moe-down Q2K aligned-SoA proto.
|
||||
//
|
||||
// Target: mul_mat_vec_q_moe<GGML_TYPE_Q2_K, 2> (cuda/mmq/mmvq.cu:608), the
|
||||
// production decode down-proj kernel: 43 launches/step at 87.3 us = 3.755
|
||||
// ms/step reading raw 84-byte block_q2_K stacks at ~190 GB/s vs the 216-233
|
||||
// GB/s device substrate (nsys33d). This proto vendors the production kernel
|
||||
// as the bit-exact reference and races two aligned-SoA layout twins that keep
|
||||
// the SAME lane->(block,iqs) mapping and float accumulation order (layout-only
|
||||
// change => bit-identical outputs, M1-Inc4 gate recipe):
|
||||
//
|
||||
// V1: plain SoA sections [half2 dm[nblk]] [pad64] [u8 scales[nblk*16]]
|
||||
// [pad64] [u32 qs[nblk*16]], block order = raw tensor byte order.
|
||||
// V2: row-pair SoA keyed to rows_per_block=2 (row pair = 2*blockIdx.x):
|
||||
// [uint2 dm2[npair*nb]] [pad64] [int4 sc4[npair*nb*2]] [pad64]
|
||||
// [uint2 qs2[npair*nb*16]]; per lane-iter one 8B qs load (both rows),
|
||||
// one 16B scales load (both rows' 8B window), one 8B dm load.
|
||||
//
|
||||
// Parity: full-buffer bitwise compare vs the raw reference across seeds x id
|
||||
// patterns (random experts, duplicates, -1 router ids, all -1) x ncols_dst
|
||||
// {1, 6, 8}, plus graph capture/replay == eager for both variants.
|
||||
//
|
||||
// Timing: rotating L-layer weight pool (defeats L2 the way the 43-layer model
|
||||
// does), production shape M=4096 K=2048 top-6, 6 distinct experts per launch.
|
||||
//
|
||||
// build (GB10): nvcc -O3 --use_fast_math -std=c++17 -arch=sm_121 \
|
||||
// -o proto_m2_q2k proto_m2_q2k.cu
|
||||
|
||||
#include <cuda_runtime.h>
|
||||
#include <cuda_fp16.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
|
||||
#define CHECK(x) do { cudaError_t e_ = (x); if (e_ != cudaSuccess) { \
|
||||
fprintf(stderr, "CUDA error %s at %s:%d\n", cudaGetErrorString(e_), __FILE__, __LINE__); exit(1); } } while (0)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Vendored ggml pieces (verbatim math).
|
||||
// ---------------------------------------------------------------------------
|
||||
#define QK_K 256
|
||||
#define QI2_K 16 // QK_K / (4*QR2_K)
|
||||
#define QR2_K 4
|
||||
#define QK8_1 32
|
||||
#define QI8_1 8 // QK8_1 / (4*QR8_1)
|
||||
|
||||
typedef struct {
|
||||
__half2 ds; // d (delta), s (d * sum of quants)
|
||||
int8_t qs[QK8_1];
|
||||
} block_q8_1;
|
||||
static_assert(sizeof(block_q8_1) == 36, "block_q8_1 size");
|
||||
|
||||
typedef struct {
|
||||
uint8_t scales[QK_K/16];
|
||||
uint8_t qs[QK_K/4];
|
||||
__half2 dm; // d, dmin
|
||||
} block_q2_K;
|
||||
static_assert(sizeof(block_q2_K) == 84, "block_q2_K size");
|
||||
|
||||
static __device__ __forceinline__ int get_int_b4(const void *x, const int &i32) {
|
||||
return ((const int *)x)[i32]; // 4-byte aligned
|
||||
}
|
||||
|
||||
static __device__ __forceinline__ int dp4a_(const int a, const int b, int c) {
|
||||
return __dp4a(a, b, c);
|
||||
}
|
||||
|
||||
template<int width = 32>
|
||||
static __device__ __forceinline__ float warp_reduce_sum(float x) {
|
||||
#pragma unroll
|
||||
for (int offset = width/2; offset > 0; offset >>= 1) {
|
||||
x += __shfl_xor_sync(0xffffffff, x, offset, width);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
// vec_dot_q2_K_q8_1_impl_mmvq, verbatim (vecdotq.cuh:364).
|
||||
static __device__ __forceinline__ float vec_dot_q2_K_q8_1_impl_mmvq(
|
||||
const int & v, const int * __restrict__ u, const uint8_t * __restrict__ scales,
|
||||
const __half2 & dm2, const float * __restrict__ d8) {
|
||||
|
||||
float sumf_d = 0.0f;
|
||||
float sumf_m = 0.0f;
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < QR2_K; ++i) {
|
||||
const int sc = scales[2*i];
|
||||
|
||||
const int vi = (v >> (2*i)) & 0x03030303;
|
||||
|
||||
sumf_d += d8[i] * (dp4a_(vi, u[i], 0) * (sc & 0xF));
|
||||
|
||||
int m = sc >> 4;
|
||||
m |= m << 8;
|
||||
m |= m << 16;
|
||||
sumf_m += d8[i] * dp4a_(m, u[i], 0);
|
||||
}
|
||||
|
||||
const float2 dm2f = __half22float2(dm2);
|
||||
|
||||
return dm2f.x*sumf_d - dm2f.y*sumf_m;
|
||||
}
|
||||
|
||||
// Same impl but the four scale bytes come from two pre-loaded 32-bit window
|
||||
// words (V2's 16B scales load). sc value chain identical: byte (lo + 2*i) of
|
||||
// the 8-byte window == scales[scale_offset + 2*i] of the raw block.
|
||||
static __device__ __forceinline__ float vec_dot_q2_K_q8_1_impl_mmvq_w(
|
||||
const int & v, const int * __restrict__ u, const uint32_t w0, const uint32_t w1,
|
||||
const int lo, const __half2 & dm2, const float * __restrict__ d8) {
|
||||
|
||||
float sumf_d = 0.0f;
|
||||
float sumf_m = 0.0f;
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < QR2_K; ++i) {
|
||||
const int bidx = lo + 2*i; // 0..7 within the window
|
||||
const uint32_t w = (bidx < 4) ? w0 : w1;
|
||||
const int sc = (int)((w >> ((bidx & 3) * 8)) & 0xFFu);
|
||||
|
||||
const int vi = (v >> (2*i)) & 0x03030303;
|
||||
|
||||
sumf_d += d8[i] * (dp4a_(vi, u[i], 0) * (sc & 0xF));
|
||||
|
||||
int m = sc >> 4;
|
||||
m |= m << 8;
|
||||
m |= m << 16;
|
||||
sumf_m += d8[i] * dp4a_(m, u[i], 0);
|
||||
}
|
||||
|
||||
const float2 dm2f = __half22float2(dm2);
|
||||
|
||||
return dm2f.x*sumf_d - dm2f.y*sumf_m;
|
||||
}
|
||||
|
||||
// vec_dot_q2_K_q8_1, verbatim (vecdotq.cuh:814).
|
||||
static __device__ __forceinline__ float vec_dot_q2_K_q8_1(
|
||||
const void * __restrict__ vbq, const block_q8_1 * __restrict__ bq8_1, const int & kbx, const int & iqs) {
|
||||
|
||||
const block_q2_K * bq2_K = (const block_q2_K *) vbq + kbx;
|
||||
|
||||
const int bq8_offset = QR2_K * (iqs / QI8_1);
|
||||
const int scale_offset = iqs - iqs % QI8_1 + (iqs % QI8_1) / (QI8_1/2);
|
||||
|
||||
const uint8_t * scales = bq2_K->scales + scale_offset;
|
||||
|
||||
const int v = get_int_b4(bq2_K->qs, iqs);
|
||||
int u[QR2_K];
|
||||
float d8[QR2_K];
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < QR2_K; ++ i) {
|
||||
u[i] = get_int_b4(bq8_1[bq8_offset + i].qs, iqs % QI8_1);
|
||||
d8[i] = __low2float(bq8_1[bq8_offset + i].ds);
|
||||
}
|
||||
|
||||
return vec_dot_q2_K_q8_1_impl_mmvq(v, u, scales, bq2_K->dm, d8);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Reference: mul_mat_vec_q_moe<GGML_TYPE_Q2_K, 2> (mmvq.cu:608), verbatim up
|
||||
// to Q2_K constant folding (qk 256, qi 16, vdr 1) and fastmodulo -> % (index
|
||||
// math only). GB10 (sm_121 >= Ada): launch bounds MMVQ_MAX_BATCH_SIZE(8)*32.
|
||||
// ---------------------------------------------------------------------------
|
||||
template <int c_rows_per_block>
|
||||
__launch_bounds__(8*32, 1)
|
||||
static __global__ void ref_mul_mat_vec_q_moe(
|
||||
const void * __restrict__ vx, const void * __restrict__ vy, const int32_t * __restrict__ ids,
|
||||
float * __restrict__ dst,
|
||||
const uint32_t ncols_x, const uint32_t 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 uint32_t stride_channel_x, const uint32_t stride_channel_y, const uint32_t stride_channel_dst,
|
||||
const uint32_t ncols_dst, const uint32_t ids_stride) {
|
||||
|
||||
constexpr int qk = QK_K;
|
||||
constexpr int qi = QI2_K;
|
||||
constexpr int vdr = 1; // VDR_Q2_K_Q8_1_MMVQ
|
||||
constexpr int warp_size = 32;
|
||||
|
||||
const uint32_t token_idx = threadIdx.y;
|
||||
const int row0 = c_rows_per_block*blockIdx.x;
|
||||
const int blocks_per_row_x = ncols_x / qk;
|
||||
constexpr int blocks_per_iter = vdr * warp_size / qi; // 2
|
||||
|
||||
const uint32_t channel_dst = blockIdx.y;
|
||||
|
||||
if (token_idx >= ncols_dst) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int32_t id_raw = ids[channel_dst + token_idx * ids_stride];
|
||||
const bool invalid_id = id_raw < 0;
|
||||
const uint32_t channel_x = invalid_id ? 0u : (uint32_t)id_raw;
|
||||
const uint32_t channel_y = channel_dst % nchannels_y;
|
||||
|
||||
const block_q8_1 * y = ((const block_q8_1 *) vy) + channel_y*stride_channel_y + token_idx*stride_col_y;
|
||||
const int kbx_offset = channel_x*stride_channel_x + row0*stride_row_x;
|
||||
|
||||
float tmp[c_rows_per_block] = {0.0f};
|
||||
|
||||
for (int kbx = threadIdx.x / (qi/vdr); !invalid_id && kbx < blocks_per_row_x; kbx += blocks_per_iter) {
|
||||
const int kby = kbx * (qk/QK8_1);
|
||||
const int kqs = vdr * (threadIdx.x % (qi/vdr));
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < c_rows_per_block; ++i) {
|
||||
tmp[i] += vec_dot_q2_K_q8_1(vx, &y[kby], kbx_offset + i*stride_row_x + kbx, kqs);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < c_rows_per_block; ++i) {
|
||||
tmp[i] = warp_reduce_sum<warp_size>(tmp[i]);
|
||||
}
|
||||
|
||||
if (threadIdx.x < c_rows_per_block && (c_rows_per_block == 1 || uint32_t(row0 + threadIdx.x) < nrows_x)) {
|
||||
dst[channel_dst*stride_channel_dst + token_idx*stride_col_dst + row0 + threadIdx.x] = tmp[threadIdx.x];
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// V1: plain SoA twin. Same skeleton, same lane mapping, same accumulation
|
||||
// order; only the three weight streams are re-sourced from aligned sections.
|
||||
// ---------------------------------------------------------------------------
|
||||
template <int c_rows_per_block>
|
||||
__launch_bounds__(8*32, 1)
|
||||
static __global__ void v1_soa_moe_kernel(
|
||||
const __half2 * __restrict__ dm_soa, // [nblk]
|
||||
const uint8_t * __restrict__ scales_soa, // [nblk*16]
|
||||
const int * __restrict__ qs_soa, // [nblk*16]
|
||||
const void * __restrict__ vy, const int32_t * __restrict__ ids,
|
||||
float * __restrict__ dst,
|
||||
const uint32_t ncols_x, const uint32_t 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 uint32_t stride_channel_x, const uint32_t stride_channel_y, const uint32_t stride_channel_dst,
|
||||
const uint32_t ncols_dst, const uint32_t ids_stride) {
|
||||
|
||||
constexpr int qk = QK_K;
|
||||
constexpr int qi = QI2_K;
|
||||
constexpr int vdr = 1;
|
||||
constexpr int warp_size = 32;
|
||||
|
||||
const uint32_t token_idx = threadIdx.y;
|
||||
const int row0 = c_rows_per_block*blockIdx.x;
|
||||
const int blocks_per_row_x = ncols_x / qk;
|
||||
constexpr int blocks_per_iter = vdr * warp_size / qi;
|
||||
|
||||
const uint32_t channel_dst = blockIdx.y;
|
||||
|
||||
if (token_idx >= ncols_dst) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int32_t id_raw = ids[channel_dst + token_idx * ids_stride];
|
||||
const bool invalid_id = id_raw < 0;
|
||||
const uint32_t channel_x = invalid_id ? 0u : (uint32_t)id_raw;
|
||||
const uint32_t channel_y = channel_dst % nchannels_y;
|
||||
|
||||
const block_q8_1 * y = ((const block_q8_1 *) vy) + channel_y*stride_channel_y + token_idx*stride_col_y;
|
||||
const int kbx_offset = channel_x*stride_channel_x + row0*stride_row_x;
|
||||
|
||||
float tmp[c_rows_per_block] = {0.0f};
|
||||
|
||||
for (int kbx = threadIdx.x / (qi/vdr); !invalid_id && kbx < blocks_per_row_x; kbx += blocks_per_iter) {
|
||||
const int kby = kbx * (qk/QK8_1);
|
||||
const int kqs = vdr * (threadIdx.x % (qi/vdr));
|
||||
|
||||
const int iqs = kqs;
|
||||
const int bq8_offset = QR2_K * (iqs / QI8_1);
|
||||
const int scale_offset = iqs - iqs % QI8_1 + (iqs % QI8_1) / (QI8_1/2);
|
||||
const block_q8_1 * bq8_1 = &y[kby];
|
||||
|
||||
int u[QR2_K];
|
||||
float d8[QR2_K];
|
||||
#pragma unroll
|
||||
for (int i = 0; i < QR2_K; ++i) {
|
||||
u[i] = get_int_b4(bq8_1[bq8_offset + i].qs, iqs % QI8_1);
|
||||
d8[i] = __low2float(bq8_1[bq8_offset + i].ds);
|
||||
}
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < c_rows_per_block; ++i) {
|
||||
const size_t blk = (size_t)(kbx_offset + i*stride_row_x + kbx);
|
||||
const int v = qs_soa[blk*16u + (unsigned)iqs];
|
||||
tmp[i] += vec_dot_q2_K_q8_1_impl_mmvq(v, u, scales_soa + blk*16u + (unsigned)scale_offset,
|
||||
dm_soa[blk], d8);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < c_rows_per_block; ++i) {
|
||||
tmp[i] = warp_reduce_sum<warp_size>(tmp[i]);
|
||||
}
|
||||
|
||||
if (threadIdx.x < c_rows_per_block && (c_rows_per_block == 1 || uint32_t(row0 + threadIdx.x) < nrows_x)) {
|
||||
dst[channel_dst*stride_channel_dst + token_idx*stride_col_dst + row0 + threadIdx.x] = tmp[threadIdx.x];
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// V2: row-pair SoA twin (rows_per_block == 2 baked into the layout).
|
||||
// Per lane-iter: one uint2 qs load (rows 0/1), one int4 scales-window load
|
||||
// (both rows' 8B window for this lane's iqs half), one uint2 dm load. Scale
|
||||
// BYTES, qs ints, dm halves and the float chain are value-identical to the
|
||||
// reference; only load shapes change.
|
||||
// ---------------------------------------------------------------------------
|
||||
__launch_bounds__(8*32, 1)
|
||||
static __global__ void v2_pair_soa_moe_kernel(
|
||||
const uint2 * __restrict__ dm2_soa, // [npair*nb] {row0 half2, row1 half2}
|
||||
const int4 * __restrict__ sc4_soa, // [npair*nb*2] {row0 w0,w1, row1 w0,w1} per half
|
||||
const uint2 * __restrict__ qs2_soa, // [npair*nb*16] {row0 int, row1 int}
|
||||
const void * __restrict__ vy, const int32_t * __restrict__ ids,
|
||||
float * __restrict__ dst,
|
||||
const uint32_t ncols_x, const uint32_t nchannels_y, const uint32_t nrows_x,
|
||||
const uint32_t stride_col_y, const uint32_t stride_col_dst,
|
||||
const uint32_t stride_channel_y, const uint32_t stride_channel_dst,
|
||||
const uint32_t ncols_dst, const uint32_t ids_stride) {
|
||||
|
||||
constexpr int qk = QK_K;
|
||||
constexpr int qi = QI2_K;
|
||||
constexpr int vdr = 1;
|
||||
constexpr int warp_size = 32;
|
||||
|
||||
const uint32_t token_idx = threadIdx.y;
|
||||
const int row0 = 2*blockIdx.x;
|
||||
const int blocks_per_row_x = ncols_x / qk;
|
||||
constexpr int blocks_per_iter = vdr * warp_size / qi;
|
||||
|
||||
const uint32_t channel_dst = blockIdx.y;
|
||||
|
||||
if (token_idx >= ncols_dst) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int32_t id_raw = ids[channel_dst + token_idx * ids_stride];
|
||||
const bool invalid_id = id_raw < 0;
|
||||
const uint32_t channel_x = invalid_id ? 0u : (uint32_t)id_raw;
|
||||
const uint32_t channel_y = channel_dst % nchannels_y;
|
||||
|
||||
const block_q8_1 * y = ((const block_q8_1 *) vy) + channel_y*stride_channel_y + token_idx*stride_col_y;
|
||||
// pair-block base for (expert, row pair): pairs are laid out expert-major,
|
||||
// pair-major, block-minor -- same linear order as raw blocks per pair.
|
||||
const size_t pair_base = ((size_t)channel_x * (nrows_x/2u) + (size_t)blockIdx.x) * (size_t)blocks_per_row_x;
|
||||
|
||||
float tmp[2] = {0.0f, 0.0f};
|
||||
|
||||
for (int kbx = threadIdx.x / (qi/vdr); !invalid_id && kbx < blocks_per_row_x; kbx += blocks_per_iter) {
|
||||
const int kby = kbx * (qk/QK8_1);
|
||||
const int kqs = vdr * (threadIdx.x % (qi/vdr));
|
||||
|
||||
const int iqs = kqs;
|
||||
const int bq8_offset = QR2_K * (iqs / QI8_1);
|
||||
const int scale_offset = iqs - iqs % QI8_1 + (iqs % QI8_1) / (QI8_1/2);
|
||||
const int whalf = iqs / QI8_1; // 0: window at scales[0..7], 1: at [8..15]
|
||||
const int lo = scale_offset - 8*whalf; // 0 or 1 within the window
|
||||
const block_q8_1 * bq8_1 = &y[kby];
|
||||
|
||||
int u[QR2_K];
|
||||
float d8[QR2_K];
|
||||
#pragma unroll
|
||||
for (int i = 0; i < QR2_K; ++i) {
|
||||
u[i] = get_int_b4(bq8_1[bq8_offset + i].qs, iqs % QI8_1);
|
||||
d8[i] = __low2float(bq8_1[bq8_offset + i].ds);
|
||||
}
|
||||
|
||||
const size_t pblk = pair_base + (size_t)kbx;
|
||||
const uint2 v2 = qs2_soa[pblk*16u + (unsigned)iqs];
|
||||
const uint2 dmw = dm2_soa[pblk];
|
||||
const int4 scw = sc4_soa[pblk*2u + (unsigned)whalf];
|
||||
const __half2 dm0 = *(const __half2 *)&dmw.x;
|
||||
const __half2 dm1 = *(const __half2 *)&dmw.y;
|
||||
|
||||
tmp[0] += vec_dot_q2_K_q8_1_impl_mmvq_w((int)v2.x, u, (uint32_t)scw.x, (uint32_t)scw.y, lo, dm0, d8);
|
||||
tmp[1] += vec_dot_q2_K_q8_1_impl_mmvq_w((int)v2.y, u, (uint32_t)scw.z, (uint32_t)scw.w, lo, dm1, d8);
|
||||
}
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
tmp[i] = warp_reduce_sum<warp_size>(tmp[i]);
|
||||
}
|
||||
|
||||
if (threadIdx.x < 2 && uint32_t(row0 + threadIdx.x) < nrows_x) {
|
||||
dst[channel_dst*stride_channel_dst + token_idx*stride_col_dst + row0 + threadIdx.x] = tmp[threadIdx.x];
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Host helpers.
|
||||
// ---------------------------------------------------------------------------
|
||||
static const int M = 4096; // out rows (down out_dim)
|
||||
static const int K = 2048; // in cols (expert_mid_dim)
|
||||
static const int NB = K / QK_K; // 8 superblocks per row
|
||||
static const int NYB = K / QK8_1; // 64 q8_1 blocks per activation row
|
||||
static const int NEXP = 256;
|
||||
static const int NUSED = 6;
|
||||
|
||||
static size_t nblk_layer() { return (size_t)NEXP * M * NB; }
|
||||
|
||||
static void fill_raw_layer(std::vector<block_q2_K> &w, std::mt19937 &rng) {
|
||||
std::uniform_int_distribution<int> byte(0, 255);
|
||||
std::uniform_real_distribution<float> mag(0.001f, 0.05f);
|
||||
for (block_q2_K &b : w) {
|
||||
for (int i = 0; i < 16; i++) b.scales[i] = (uint8_t)byte(rng);
|
||||
for (int i = 0; i < 64; i++) b.qs[i] = (uint8_t)byte(rng);
|
||||
b.dm = __floats2half2_rn(mag(rng), mag(rng));
|
||||
}
|
||||
}
|
||||
|
||||
static void quantize_q8_1(const float *x, block_q8_1 *y, int n) {
|
||||
for (int ib = 0; ib < n / QK8_1; ib++) {
|
||||
const float *xb = x + ib*QK8_1;
|
||||
float amax = 0.0f;
|
||||
for (int j = 0; j < QK8_1; j++) { float a = fabsf(xb[j]); if (a > amax) amax = a; }
|
||||
const float d = amax / 127.0f;
|
||||
const float idf = d != 0.0f ? 1.0f/d : 0.0f;
|
||||
int sum = 0;
|
||||
for (int j = 0; j < QK8_1; j++) {
|
||||
int q = (int)roundf(xb[j] * idf);
|
||||
if (q > 127) q = 127;
|
||||
if (q < -127) q = -127;
|
||||
y[ib].qs[j] = (int8_t)q;
|
||||
sum += q;
|
||||
}
|
||||
y[ib].ds = __floats2half2_rn(d, d * (float)sum);
|
||||
}
|
||||
}
|
||||
|
||||
// V1 repack: [dm nblk*4][pad64][scales nblk*16][pad64][qs nblk*64]
|
||||
static void repack_v1(const block_q2_K *raw, size_t nblk,
|
||||
__half2 *dm, uint8_t *scales, int *qs) {
|
||||
for (size_t b = 0; b < nblk; b++) {
|
||||
dm[b] = raw[b].dm;
|
||||
memcpy(scales + b*16u, raw[b].scales, 16);
|
||||
memcpy(qs + b*16u, raw[b].qs, 64);
|
||||
}
|
||||
}
|
||||
|
||||
// V2 repack: pair p covers raw blocks of rows (2p, 2p+1) within an expert.
|
||||
// Raw linear block index of (expert e, row r, block b) = (e*M + r)*NB + b.
|
||||
static void repack_v2(const block_q2_K *raw,
|
||||
uint2 *dm2, int4 *sc4, uint2 *qs2) {
|
||||
const size_t npair_blk = (size_t)NEXP * (M/2) * NB;
|
||||
for (size_t pb = 0; pb < npair_blk; pb++) {
|
||||
const size_t b = pb % NB;
|
||||
const size_t pr = (pb / NB) % (M/2);
|
||||
const size_t e = pb / ((size_t)NB * (M/2));
|
||||
const block_q2_K *r0 = raw + ((e*M + 2*pr + 0)*NB + b);
|
||||
const block_q2_K *r1 = raw + ((e*M + 2*pr + 1)*NB + b);
|
||||
uint2 dmw;
|
||||
memcpy(&dmw.x, &r0->dm, 4);
|
||||
memcpy(&dmw.y, &r1->dm, 4);
|
||||
dm2[pb] = dmw;
|
||||
// sc4[pb*2 + h] = {row0 w(h)[0:4], row0 w(h)[4:8], row1 w(h)[0:4], row1 w(h)[4:8]}
|
||||
for (int h = 0; h < 2; h++) {
|
||||
int4 s;
|
||||
memcpy(&s.x, r0->scales + 8*h + 0, 4);
|
||||
memcpy(&s.y, r0->scales + 8*h + 4, 4);
|
||||
memcpy(&s.z, r1->scales + 8*h + 0, 4);
|
||||
memcpy(&s.w, r1->scales + 8*h + 4, 4);
|
||||
sc4[pb*2 + h] = s;
|
||||
}
|
||||
for (int i = 0; i < 16; i++) {
|
||||
uint2 q;
|
||||
memcpy(&q.x, r0->qs + 4*i, 4);
|
||||
memcpy(&q.y, r1->qs + 4*i, 4);
|
||||
qs2[pb*16 + i] = q;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct dev_layer {
|
||||
block_q2_K *raw = nullptr;
|
||||
__half2 *v1_dm = nullptr; uint8_t *v1_sc = nullptr; int *v1_qs = nullptr;
|
||||
uint2 *v2_dm = nullptr; int4 *v2_sc = nullptr; uint2 *v2_qs = nullptr;
|
||||
};
|
||||
|
||||
// Production launch config for the down leg (routed_moe_launch vec tier):
|
||||
// ncols_dst = n_assignments, nchannels_dst = 1, ids_stride = 1,
|
||||
// stride_col_dst = M, stride_channel_dst = M, stride_col_y = NYB.
|
||||
static void launch_ref(const dev_layer &L, const block_q8_1 *y, const int32_t *ids,
|
||||
float *dst, int ncols, cudaStream_t s) {
|
||||
dim3 grid(M/2, 1);
|
||||
dim3 block(32, ncols);
|
||||
ref_mul_mat_vec_q_moe<2><<<grid, block, 0, s>>>(
|
||||
L.raw, y, ids, dst,
|
||||
(uint32_t)K, 1u, (uint32_t)M,
|
||||
(uint32_t)NB, (uint32_t)NYB, (uint32_t)M,
|
||||
(uint32_t)(M*NB), (uint32_t)NYB, (uint32_t)M,
|
||||
(uint32_t)ncols, 1u);
|
||||
}
|
||||
|
||||
static void launch_v1(const dev_layer &L, const block_q8_1 *y, const int32_t *ids,
|
||||
float *dst, int ncols, cudaStream_t s) {
|
||||
dim3 grid(M/2, 1);
|
||||
dim3 block(32, ncols);
|
||||
v1_soa_moe_kernel<2><<<grid, block, 0, s>>>(
|
||||
L.v1_dm, L.v1_sc, L.v1_qs, y, ids, dst,
|
||||
(uint32_t)K, 1u, (uint32_t)M,
|
||||
(uint32_t)NB, (uint32_t)NYB, (uint32_t)M,
|
||||
(uint32_t)(M*NB), (uint32_t)NYB, (uint32_t)M,
|
||||
(uint32_t)ncols, 1u);
|
||||
}
|
||||
|
||||
static void launch_v2(const dev_layer &L, const block_q8_1 *y, const int32_t *ids,
|
||||
float *dst, int ncols, cudaStream_t s) {
|
||||
dim3 grid(M/2, 1);
|
||||
dim3 block(32, ncols);
|
||||
v2_pair_soa_moe_kernel<<<grid, block, 0, s>>>(
|
||||
L.v2_dm, L.v2_sc, L.v2_qs, y, ids, dst,
|
||||
(uint32_t)K, 1u, (uint32_t)M,
|
||||
(uint32_t)NYB, (uint32_t)M,
|
||||
(uint32_t)NYB, (uint32_t)M,
|
||||
(uint32_t)ncols, 1u);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int n_layers = 6;
|
||||
int timing_iters = 300;
|
||||
if (argc > 1) n_layers = atoi(argv[1]);
|
||||
if (argc > 2) timing_iters = atoi(argv[2]);
|
||||
|
||||
const size_t nblk = nblk_layer();
|
||||
const size_t raw_bytes = nblk * sizeof(block_q2_K);
|
||||
printf("proto_m2_q2k: M=%d K=%d E=%d nb=%d layers=%d raw=%.1f MiB/layer\n",
|
||||
M, K, NEXP, NB, n_layers, raw_bytes / 1048576.0);
|
||||
|
||||
std::mt19937 rng(0xC0FFEE);
|
||||
|
||||
// Host-side raw layer template + repacks (one host copy reused per layer
|
||||
// with a per-layer byte perturbation so layers differ without n_layers
|
||||
// full random generations).
|
||||
std::vector<block_q2_K> hraw(nblk);
|
||||
fill_raw_layer(hraw, rng);
|
||||
|
||||
std::vector<__half2> h_v1dm(nblk);
|
||||
std::vector<uint8_t> h_v1sc(nblk * 16u);
|
||||
std::vector<int> h_v1qs(nblk * 16u);
|
||||
std::vector<uint2> h_v2dm(nblk / 2u);
|
||||
std::vector<int4> h_v2sc(nblk); // nblk/2 pairs * 2
|
||||
std::vector<uint2> h_v2qs((nblk / 2u) * 16u);
|
||||
|
||||
std::vector<dev_layer> layers(n_layers);
|
||||
for (int l = 0; l < n_layers; l++) {
|
||||
if (l > 0) {
|
||||
// cheap per-layer variation: rotate scales/qs bytes by l
|
||||
for (size_t b = 0; b < nblk; b += 977u) {
|
||||
hraw[b].qs[(l*7) & 63] ^= (uint8_t)(0x5A + l);
|
||||
hraw[b].scales[(l*3) & 15] ^= (uint8_t)(0xA5 - l);
|
||||
}
|
||||
}
|
||||
repack_v1(hraw.data(), nblk, h_v1dm.data(), h_v1sc.data(), h_v1qs.data());
|
||||
repack_v2(hraw.data(), h_v2dm.data(), h_v2sc.data(), h_v2qs.data());
|
||||
|
||||
dev_layer &L = layers[l];
|
||||
CHECK(cudaMalloc(&L.raw, raw_bytes));
|
||||
CHECK(cudaMalloc(&L.v1_dm, nblk * 4u));
|
||||
CHECK(cudaMalloc(&L.v1_sc, nblk * 16u));
|
||||
CHECK(cudaMalloc(&L.v1_qs, nblk * 64u));
|
||||
CHECK(cudaMalloc(&L.v2_dm, (nblk/2u) * 8u));
|
||||
CHECK(cudaMalloc(&L.v2_sc, nblk * 16u));
|
||||
CHECK(cudaMalloc(&L.v2_qs, (nblk/2u) * 16u * 8u));
|
||||
CHECK(cudaMemcpy(L.raw, hraw.data(), raw_bytes, cudaMemcpyHostToDevice));
|
||||
CHECK(cudaMemcpy(L.v1_dm, h_v1dm.data(), nblk * 4u, cudaMemcpyHostToDevice));
|
||||
CHECK(cudaMemcpy(L.v1_sc, h_v1sc.data(), nblk * 16u, cudaMemcpyHostToDevice));
|
||||
CHECK(cudaMemcpy(L.v1_qs, h_v1qs.data(), nblk * 64u, cudaMemcpyHostToDevice));
|
||||
CHECK(cudaMemcpy(L.v2_dm, h_v2dm.data(), (nblk/2u) * 8u, cudaMemcpyHostToDevice));
|
||||
CHECK(cudaMemcpy(L.v2_sc, h_v2sc.data(), nblk * 16u, cudaMemcpyHostToDevice));
|
||||
CHECK(cudaMemcpy(L.v2_qs, h_v2qs.data(), (nblk/2u) * 16u * 8u, cudaMemcpyHostToDevice));
|
||||
}
|
||||
|
||||
// Activations: MAX_COLS q8_1 rows.
|
||||
const int MAX_COLS = 8;
|
||||
std::vector<float> hx(MAX_COLS * K);
|
||||
std::vector<block_q8_1> hy(MAX_COLS * NYB);
|
||||
std::uniform_real_distribution<float> xf(-1.0f, 1.0f);
|
||||
for (float &v : hx) v = xf(rng);
|
||||
for (int c = 0; c < MAX_COLS; c++) quantize_q8_1(hx.data() + c*K, hy.data() + c*NYB, K);
|
||||
block_q8_1 *dy = nullptr;
|
||||
CHECK(cudaMalloc(&dy, hy.size() * sizeof(block_q8_1)));
|
||||
CHECK(cudaMemcpy(dy, hy.data(), hy.size() * sizeof(block_q8_1), cudaMemcpyHostToDevice));
|
||||
|
||||
int32_t *dids = nullptr;
|
||||
CHECK(cudaMalloc(&dids, MAX_COLS * sizeof(int32_t)));
|
||||
float *dref = nullptr, *dv1 = nullptr, *dv2 = nullptr;
|
||||
const size_t out_bytes = (size_t)MAX_COLS * M * sizeof(float);
|
||||
CHECK(cudaMalloc(&dref, out_bytes));
|
||||
CHECK(cudaMalloc(&dv1, out_bytes));
|
||||
CHECK(cudaMalloc(&dv2, out_bytes));
|
||||
|
||||
cudaStream_t stream;
|
||||
CHECK(cudaStreamCreate(&stream));
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Parity: seeds x id patterns x ncols on rotating layers.
|
||||
// ------------------------------------------------------------------
|
||||
std::vector<float> href(MAX_COLS * M), hv1(MAX_COLS * M), hv2(MAX_COLS * M);
|
||||
const int ncols_cases[3] = {1, 6, 8};
|
||||
int checks = 0, fails = 0;
|
||||
std::uniform_int_distribution<int> expd(0, NEXP - 1);
|
||||
for (int seed = 0; seed < 10; seed++) {
|
||||
for (int pat = 0; pat < 4; pat++) {
|
||||
for (int nc = 0; nc < 3; nc++) {
|
||||
const int ncols = ncols_cases[nc];
|
||||
const dev_layer &L = layers[(seed + pat + nc) % n_layers];
|
||||
int32_t ids[MAX_COLS];
|
||||
for (int i = 0; i < ncols; i++) {
|
||||
switch (pat) {
|
||||
case 0: ids[i] = expd(rng); break; // random
|
||||
case 1: ids[i] = (i < 2) ? expd(rng) : ids[i % 2]; // duplicates
|
||||
break;
|
||||
case 2: ids[i] = (i % 3 == 1) ? -1 : expd(rng); break; // sparse -1
|
||||
case 3: ids[i] = -1; break; // all -1
|
||||
}
|
||||
}
|
||||
CHECK(cudaMemcpy(dids, ids, ncols * sizeof(int32_t), cudaMemcpyHostToDevice));
|
||||
CHECK(cudaMemset(dref, 0xCB, out_bytes));
|
||||
CHECK(cudaMemset(dv1, 0xCB, out_bytes));
|
||||
CHECK(cudaMemset(dv2, 0xCB, out_bytes));
|
||||
launch_ref(L, dy, dids, dref, ncols, stream);
|
||||
launch_v1(L, dy, dids, dv1, ncols, stream);
|
||||
launch_v2(L, dy, dids, dv2, ncols, stream);
|
||||
CHECK(cudaStreamSynchronize(stream));
|
||||
CHECK(cudaGetLastError());
|
||||
const size_t cmp = (size_t)ncols * M * sizeof(float);
|
||||
CHECK(cudaMemcpy(href.data(), dref, cmp, cudaMemcpyDeviceToHost));
|
||||
CHECK(cudaMemcpy(hv1.data(), dv1, cmp, cudaMemcpyDeviceToHost));
|
||||
CHECK(cudaMemcpy(hv2.data(), dv2, cmp, cudaMemcpyDeviceToHost));
|
||||
checks += 2;
|
||||
if (memcmp(href.data(), hv1.data(), cmp) != 0) {
|
||||
fails++;
|
||||
printf("PARITY FAIL V1 seed=%d pat=%d ncols=%d\n", seed, pat, ncols);
|
||||
}
|
||||
if (memcmp(href.data(), hv2.data(), cmp) != 0) {
|
||||
fails++;
|
||||
printf("PARITY FAIL V2 seed=%d pat=%d ncols=%d\n", seed, pat, ncols);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("parity: %d/%d bit-identical%s\n", checks - fails, checks, fails ? " <-- FAIL" : "");
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Capture/replay parity: capture each variant once, replay, compare to
|
||||
// the eager output just validated.
|
||||
// ------------------------------------------------------------------
|
||||
{
|
||||
int32_t ids[MAX_COLS];
|
||||
for (int i = 0; i < NUSED; i++) ids[i] = expd(rng);
|
||||
ids[3] = -1;
|
||||
CHECK(cudaMemcpy(dids, ids, NUSED * sizeof(int32_t), cudaMemcpyHostToDevice));
|
||||
const dev_layer &L = layers[0];
|
||||
const size_t cmp = (size_t)NUSED * M * sizeof(float);
|
||||
|
||||
launch_ref(L, dy, dids, dref, NUSED, stream);
|
||||
CHECK(cudaStreamSynchronize(stream));
|
||||
CHECK(cudaMemcpy(href.data(), dref, cmp, cudaMemcpyDeviceToHost));
|
||||
|
||||
int replay_fails = 0;
|
||||
for (int variant = 0; variant < 2; variant++) {
|
||||
float *dout = variant == 0 ? dv1 : dv2;
|
||||
CHECK(cudaMemset(dout, 0xCB, out_bytes));
|
||||
cudaGraph_t graph;
|
||||
cudaGraphExec_t exec;
|
||||
CHECK(cudaStreamBeginCapture(stream, cudaStreamCaptureModeGlobal));
|
||||
if (variant == 0) launch_v1(L, dy, dids, dout, NUSED, stream);
|
||||
else launch_v2(L, dy, dids, dout, NUSED, stream);
|
||||
CHECK(cudaStreamEndCapture(stream, &graph));
|
||||
CHECK(cudaGraphInstantiate(&exec, graph, nullptr, nullptr, 0));
|
||||
CHECK(cudaGraphLaunch(exec, stream));
|
||||
CHECK(cudaStreamSynchronize(stream));
|
||||
CHECK(cudaMemcpy(hv1.data(), dout, cmp, cudaMemcpyDeviceToHost));
|
||||
if (memcmp(href.data(), hv1.data(), cmp) != 0) {
|
||||
replay_fails++;
|
||||
printf("REPLAY FAIL V%d\n", variant + 1);
|
||||
}
|
||||
CHECK(cudaGraphExecDestroy(exec));
|
||||
CHECK(cudaGraphDestroy(graph));
|
||||
}
|
||||
printf("capture/replay: %d/2 bit-identical%s\n", 2 - replay_fails, replay_fails ? " <-- FAIL" : "");
|
||||
fails += replay_fails;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Timing: rotating layers, 6 distinct experts per launch (production
|
||||
// decode shape), events around the whole loop.
|
||||
// ------------------------------------------------------------------
|
||||
{
|
||||
// Pre-generate rotating id sets on device (one buffer per iteration
|
||||
// slot would perturb the graph; production re-writes ids in place, so
|
||||
// do the same: ids uploaded once per timing pass, kernels rotate
|
||||
// layers only -- weight reads dominate).
|
||||
int32_t ids[MAX_COLS];
|
||||
bool used[NEXP] = {false};
|
||||
int n = 0;
|
||||
while (n < NUSED) {
|
||||
int e = expd(rng);
|
||||
if (used[e]) continue;
|
||||
used[e] = true;
|
||||
ids[n++] = e;
|
||||
}
|
||||
CHECK(cudaMemcpy(dids, ids, NUSED * sizeof(int32_t), cudaMemcpyHostToDevice));
|
||||
|
||||
const double gib = (double)NUSED * M * NB * 84.0; // distinct-expert weight bytes/launch
|
||||
cudaEvent_t e0, e1;
|
||||
CHECK(cudaEventCreate(&e0));
|
||||
CHECK(cudaEventCreate(&e1));
|
||||
|
||||
for (int variant = 0; variant < 3; variant++) {
|
||||
const char *name = variant == 0 ? "ref-raw" : (variant == 1 ? "v1-soa " : "v2-pair");
|
||||
float *dout = variant == 0 ? dref : (variant == 1 ? dv1 : dv2);
|
||||
// warm
|
||||
for (int it = 0; it < 30; it++) {
|
||||
const dev_layer &L = layers[it % n_layers];
|
||||
if (variant == 0) launch_ref(L, dy, dids, dout, NUSED, stream);
|
||||
else if (variant == 1) launch_v1(L, dy, dids, dout, NUSED, stream);
|
||||
else launch_v2(L, dy, dids, dout, NUSED, stream);
|
||||
}
|
||||
CHECK(cudaStreamSynchronize(stream));
|
||||
CHECK(cudaEventRecord(e0, stream));
|
||||
for (int it = 0; it < timing_iters; it++) {
|
||||
const dev_layer &L = layers[it % n_layers];
|
||||
if (variant == 0) launch_ref(L, dy, dids, dout, NUSED, stream);
|
||||
else if (variant == 1) launch_v1(L, dy, dids, dout, NUSED, stream);
|
||||
else launch_v2(L, dy, dids, dout, NUSED, stream);
|
||||
}
|
||||
CHECK(cudaEventRecord(e1, stream));
|
||||
CHECK(cudaStreamSynchronize(stream));
|
||||
float ms = 0.0f;
|
||||
CHECK(cudaEventElapsedTime(&ms, e0, e1));
|
||||
const double us = ms * 1000.0 / timing_iters;
|
||||
printf("%s: %8.2f us/launch %7.1f GB/s (x43 = %.3f ms/step)\n",
|
||||
name, us, gib / (us * 1e-6) / 1e9, us * 43.0 / 1000.0);
|
||||
}
|
||||
CHECK(cudaEventDestroy(e0));
|
||||
CHECK(cudaEventDestroy(e1));
|
||||
}
|
||||
|
||||
printf(fails ? "RESULT: FAIL\n" : "RESULT: PASS\n");
|
||||
return fails ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,604 @@
|
||||
/* proto_m2_qkv.cu — M2-Inc2 prototypes: QKV-post chain fusions.
|
||||
*
|
||||
* Three kill-switch-ready fusions over the decode QKV-post pool, each proven
|
||||
* BIT-EXACT here against verbatim copies of the production kernels:
|
||||
*
|
||||
* 2a dsv4_qkv_rms_norm_rows + q8_1 emission of the q row (kills the q_b
|
||||
* mmvq quantize prelude, quantize_q8_1 grid=4).
|
||||
* 2b head_rms_norm + rope_tail(q) fused, device-scalars pos source (the
|
||||
* existing fused kernel is dead code because it bakes host pos0 into
|
||||
* captures; this is its capture-safe twin).
|
||||
* 2c rope_tail(kv) + fp8_kv_quantize + store_raw_kv fused into ONE
|
||||
* 8-block kernel: rope touches only the 64-float rotary tail, fp8
|
||||
* covers only the 448 nope elems (disjoint), and the production fp8
|
||||
* kernel's 5.9 us is 7 SEQUENTIAL 64-elem groups in a single 64-thread
|
||||
* block — the groups are independent, so they become 7 parallel blocks
|
||||
* (+1 rope/tail block). Raw-store f16 round-trip preserved.
|
||||
*
|
||||
* Build: nvcc -O3 --use_fast_math -std=c++17 -arch=sm_121 proto_m2_qkv.cu -o proto_qkv
|
||||
*/
|
||||
#include <cuda_runtime.h>
|
||||
#include <cuda_fp16.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
|
||||
#define CK(x) do { cudaError_t e_ = (x); if (e_ != cudaSuccess) { \
|
||||
printf("CUDA error %s at %s:%d\n", cudaGetErrorString(e_), __FILE__, __LINE__); exit(1); } } while (0)
|
||||
|
||||
#define N_HEAD 64u
|
||||
#define HEAD_DIM 512u
|
||||
#define N_ROT 64u
|
||||
#define Q_RANK 1024u
|
||||
#define KV_DIM 512u
|
||||
#define RAW_CAP 1024u
|
||||
#define RMS_EPS 1e-6f
|
||||
|
||||
/* mirror of the production decode-scalars substrate (only pos0/raw_row read) */
|
||||
struct ds4_decode_scalars {
|
||||
uint32_t pos0, raw_row, raw_start, n_raw, n_comp, emit_phase,
|
||||
comp_row, index_row, flags, token;
|
||||
};
|
||||
|
||||
typedef struct { __half2 ds; int8_t qs[32]; } block_q8_1_t;
|
||||
|
||||
/* ============ verbatim production device helpers ============ */
|
||||
__device__ static float rope_yarn_ramp_dev(float low, float high, int i0) {
|
||||
float y = ((float)(i0 / 2) - low) / fmaxf(0.001f, high - low);
|
||||
return 1.0f - fminf(1.0f, fmaxf(0.0f, y));
|
||||
}
|
||||
__device__ static float dsv4_e4m3fn_value_dev(int i) {
|
||||
int exp = (i >> 3) & 15;
|
||||
int mant = i & 7;
|
||||
if (exp == 0) return (float)mant * 0.001953125f;
|
||||
return (1.0f + (float)mant * 0.125f) * exp2f((float)exp - 7.0f);
|
||||
}
|
||||
__device__ static float dsv4_e4m3fn_dequant_dev(float x) {
|
||||
float sign = x < 0.0f ? -1.0f : 1.0f;
|
||||
float ax = fminf(fabsf(x), 448.0f);
|
||||
int lo = 0, hi = 126;
|
||||
while (lo < hi) {
|
||||
int mid = (lo + hi + 1) >> 1;
|
||||
if (dsv4_e4m3fn_value_dev(mid) <= ax) lo = mid;
|
||||
else hi = mid - 1;
|
||||
}
|
||||
int best = lo;
|
||||
if (best < 126) {
|
||||
float bd = fabsf(ax - dsv4_e4m3fn_value_dev(best));
|
||||
float nd = fabsf(ax - dsv4_e4m3fn_value_dev(best + 1));
|
||||
if (nd < bd || (nd == bd && (((best + 1) & 1) == 0) && ((best & 1) != 0))) best++;
|
||||
}
|
||||
return sign * dsv4_e4m3fn_value_dev(best);
|
||||
}
|
||||
|
||||
/* ============ verbatim production kernels (references) ============ */
|
||||
__global__ static void dsv4_qkv_rms_norm_rows_kernel(
|
||||
float *q_out, const float *q, const float *q_w, uint32_t q_n,
|
||||
float *kv_out, const float *kv, const float *kv_w, uint32_t kv_n,
|
||||
uint32_t rows, float eps) {
|
||||
const uint32_t row = blockIdx.x;
|
||||
const uint32_t which = blockIdx.y;
|
||||
if (row >= rows || which > 1u) return;
|
||||
const uint32_t n = which == 0u ? q_n : kv_n;
|
||||
const float *xr = (which == 0u ? q : kv) + (uint64_t)row * n;
|
||||
float *orow = (which == 0u ? q_out : kv_out) + (uint64_t)row * n;
|
||||
const float *w = which == 0u ? q_w : kv_w;
|
||||
float sum = 0.0f;
|
||||
for (uint32_t i = threadIdx.x; i < n; i += blockDim.x) {
|
||||
const float v = xr[i];
|
||||
sum += v * v;
|
||||
}
|
||||
__shared__ float partial[256];
|
||||
partial[threadIdx.x] = sum;
|
||||
__syncthreads();
|
||||
for (uint32_t stride = blockDim.x >> 1; stride > 0; stride >>= 1) {
|
||||
if (threadIdx.x < stride) partial[threadIdx.x] += partial[threadIdx.x + stride];
|
||||
__syncthreads();
|
||||
}
|
||||
const float scale = rsqrtf(partial[0] / (float)n + eps);
|
||||
for (uint32_t i = threadIdx.x; i < n; i += blockDim.x) {
|
||||
orow[i] = xr[i] * scale * w[i];
|
||||
}
|
||||
}
|
||||
|
||||
__global__ static void head_rms_norm_kernel(float *x, uint32_t n_tok, uint32_t n_head, uint32_t head_dim, float eps) {
|
||||
uint32_t row = blockIdx.x;
|
||||
if (row >= n_tok * n_head) return;
|
||||
float *xr = x + (uint64_t)row * head_dim;
|
||||
float sum = 0.0f;
|
||||
for (uint32_t i = threadIdx.x; i < head_dim; i += blockDim.x) {
|
||||
float v = xr[i];
|
||||
sum += v * v;
|
||||
}
|
||||
__shared__ float partial[256];
|
||||
partial[threadIdx.x] = sum;
|
||||
__syncthreads();
|
||||
for (uint32_t stride = blockDim.x >> 1; stride > 0; stride >>= 1) {
|
||||
if (threadIdx.x < stride) partial[threadIdx.x] += partial[threadIdx.x + stride];
|
||||
__syncthreads();
|
||||
}
|
||||
float scale = rsqrtf(partial[0] / (float)head_dim + eps);
|
||||
for (uint32_t i = threadIdx.x; i < head_dim; i += blockDim.x) xr[i] *= scale;
|
||||
}
|
||||
|
||||
__global__ static void rope_tail_scalars_kernel(
|
||||
float *x, uint32_t n_tok, uint32_t n_head, uint32_t head_dim, uint32_t n_rot,
|
||||
const struct ds4_decode_scalars * __restrict__ scalars,
|
||||
int32_t pos_offset, uint32_t pos_stride, uint32_t n_ctx_orig, int inverse,
|
||||
float freq_base, float freq_scale, float ext_factor, float attn_factor,
|
||||
float beta_fast, float beta_slow) {
|
||||
uint32_t gid = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
uint32_t pairs = n_tok * n_head * (n_rot / 2);
|
||||
if (gid >= pairs) return;
|
||||
uint32_t pair = gid % (n_rot / 2);
|
||||
uint32_t tmp = gid / (n_rot / 2);
|
||||
uint32_t h = tmp % n_head;
|
||||
uint32_t t = tmp / n_head;
|
||||
uint32_t n_nope = head_dim - n_rot;
|
||||
uint32_t i = pair * 2;
|
||||
const uint32_t pos0 = (uint32_t)((int32_t)scalars->pos0 + pos_offset);
|
||||
float corr0 = 0.0f, corr1 = 0.0f;
|
||||
if (ext_factor != 0.0f) {
|
||||
float denom = 2.0f * logf(freq_base);
|
||||
corr0 = floorf((float)n_rot * logf((float)n_ctx_orig / (beta_fast * 2.0f * (float)M_PI)) / denom);
|
||||
corr1 = ceilf((float)n_rot * logf((float)n_ctx_orig / (beta_slow * 2.0f * (float)M_PI)) / denom);
|
||||
corr0 = fmaxf(0.0f, corr0);
|
||||
corr1 = fminf((float)(n_rot - 1), corr1);
|
||||
}
|
||||
float theta_extrap = (float)(pos0 + t * pos_stride) * powf(freq_base, -((float)i) / (float)n_rot);
|
||||
float theta_interp = freq_scale * theta_extrap;
|
||||
float theta = theta_interp;
|
||||
float mscale = attn_factor;
|
||||
if (ext_factor != 0.0f) {
|
||||
float ramp_mix = rope_yarn_ramp_dev(corr0, corr1, (int)i) * ext_factor;
|
||||
theta = theta_interp * (1.0f - ramp_mix) + theta_extrap * ramp_mix;
|
||||
mscale *= 1.0f + 0.1f * logf(1.0f / freq_scale);
|
||||
}
|
||||
float c = cosf(theta) * mscale;
|
||||
float s = sinf(theta) * mscale;
|
||||
if (inverse) s = -s;
|
||||
float *tail = x + ((uint64_t)t * n_head + h) * head_dim + n_nope;
|
||||
float x0 = tail[i];
|
||||
float x1 = tail[i + 1];
|
||||
tail[i] = x0 * c - x1 * s;
|
||||
tail[i + 1] = x0 * s + x1 * c;
|
||||
}
|
||||
|
||||
__global__ static void fp8_kv_quantize_kernel(
|
||||
float *x, uint32_t n_tok, uint32_t head_dim, uint32_t n_rot,
|
||||
unsigned char * __restrict__ codes_base, float * __restrict__ scale_base) {
|
||||
uint32_t row = blockIdx.x;
|
||||
uint32_t tid = threadIdx.x;
|
||||
uint32_t n_nope = head_dim - n_rot;
|
||||
const uint64_t row_stride = (uint64_t)n_nope + (uint64_t)n_rot * sizeof(float);
|
||||
const uint64_t scale_stride = (uint64_t)(n_nope / 64u);
|
||||
float *xr = x + (uint64_t)row * head_dim;
|
||||
unsigned char *codes_row = codes_base
|
||||
? codes_base + (uint64_t)row * row_stride : (unsigned char *)0;
|
||||
float *scale_row = scale_base
|
||||
? scale_base + (uint64_t)row * scale_stride : (float *)0;
|
||||
__shared__ float scratch[64];
|
||||
for (uint32_t off = 0; off < n_nope; off += 64) {
|
||||
float v = 0.0f;
|
||||
if (off + tid < n_nope) v = xr[off + tid];
|
||||
scratch[tid] = off + tid < n_nope ? fabsf(v) : 0.0f;
|
||||
__syncthreads();
|
||||
for (uint32_t stride = 32; stride > 0; stride >>= 1) {
|
||||
if (tid < stride) scratch[tid] = fmaxf(scratch[tid], scratch[tid + stride]);
|
||||
__syncthreads();
|
||||
}
|
||||
float scale = exp2f(ceilf(log2f(fmaxf(scratch[0], 1.0e-4f) / 448.0f)));
|
||||
if (off + tid < n_nope) {
|
||||
float clamp = fminf(448.0f, fmaxf(-448.0f, v / scale));
|
||||
xr[off + tid] = dsv4_e4m3fn_dequant_dev(clamp) * scale;
|
||||
if (codes_row) codes_row[off + tid] = 0; /* unused in decode path */
|
||||
}
|
||||
if (scale_row && tid == 0) scale_row[off / 64u] = scale;
|
||||
__syncthreads();
|
||||
}
|
||||
(void)row_stride;
|
||||
}
|
||||
|
||||
__global__ static void store_raw_kv_batch_kernel(
|
||||
float *raw, const float *kv,
|
||||
uint32_t raw_cap, uint32_t pos0, uint32_t n_tokens, uint32_t head_dim,
|
||||
const int32_t * __restrict__ positions, const int32_t * __restrict__ seq_id,
|
||||
const struct ds4_decode_scalars * __restrict__ s_override) {
|
||||
uint64_t gid = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x;
|
||||
uint64_t n = (uint64_t)n_tokens * head_dim;
|
||||
if (gid >= n) return;
|
||||
uint32_t d = gid % head_dim;
|
||||
uint32_t t = gid / head_dim;
|
||||
uint32_t row;
|
||||
if (s_override != NULL && n_tokens == 1u) {
|
||||
row = s_override->raw_row;
|
||||
} else {
|
||||
uint32_t pos = positions ? (uint32_t)positions[t] : pos0 + t;
|
||||
uint32_t seq_base = seq_id ? (uint32_t)seq_id[t] * raw_cap : 0u;
|
||||
row = seq_base + (pos % raw_cap);
|
||||
}
|
||||
raw[(uint64_t)row * head_dim + d] = __half2float(__float2half(kv[(uint64_t)t * head_dim + d]));
|
||||
}
|
||||
|
||||
/* q8_1 reference (vendored quantize_q8_1 semantics, contiguous case) */
|
||||
__global__ static void ref_quantize_q8_1_kernel(block_q8_1_t *y, const float *x, uint32_t n) {
|
||||
const uint32_t i = blockIdx.x * 32u + threadIdx.x;
|
||||
if (i >= n) return;
|
||||
const float xi = x[i];
|
||||
float amax = fabsf(xi);
|
||||
float sum = xi;
|
||||
for (uint32_t off = 16u; off > 0u; off >>= 1u) {
|
||||
amax = fmaxf(amax, __shfl_xor_sync(0xffffffffu, amax, off, 32));
|
||||
sum += __shfl_xor_sync(0xffffffffu, sum, off, 32);
|
||||
}
|
||||
const float d = amax / 127.0f;
|
||||
const int8_t q = amax == 0.0f ? (int8_t)0 : (int8_t)roundf(xi / d);
|
||||
y[blockIdx.x].qs[threadIdx.x] = q;
|
||||
if (threadIdx.x == 0) y[blockIdx.x].ds = __floats2half2_rn(d, sum);
|
||||
}
|
||||
|
||||
/* ============ 2a: qkv_rms_norm_rows + q8_1(q row) ============ */
|
||||
__global__ static void dsv4_qkv_rms_norm_rows_q81_kernel(
|
||||
float *q_out, const float *q, const float *q_w, uint32_t q_n,
|
||||
float *kv_out, const float *kv, const float *kv_w, uint32_t kv_n,
|
||||
uint32_t rows, float eps, block_q8_1_t *q81) {
|
||||
const uint32_t row = blockIdx.x;
|
||||
const uint32_t which = blockIdx.y;
|
||||
if (row >= rows || which > 1u) return;
|
||||
const uint32_t n = which == 0u ? q_n : kv_n;
|
||||
const float *xr = (which == 0u ? q : kv) + (uint64_t)row * n;
|
||||
float *orow = (which == 0u ? q_out : kv_out) + (uint64_t)row * n;
|
||||
const float *w = which == 0u ? q_w : kv_w;
|
||||
float sum = 0.0f;
|
||||
for (uint32_t i = threadIdx.x; i < n; i += blockDim.x) {
|
||||
const float v = xr[i];
|
||||
sum += v * v;
|
||||
}
|
||||
__shared__ float partial[256];
|
||||
partial[threadIdx.x] = sum;
|
||||
__syncthreads();
|
||||
for (uint32_t stride = blockDim.x >> 1; stride > 0; stride >>= 1) {
|
||||
if (threadIdx.x < stride) partial[threadIdx.x] += partial[threadIdx.x + stride];
|
||||
__syncthreads();
|
||||
}
|
||||
const float scale = rsqrtf(partial[0] / (float)n + eps);
|
||||
for (uint32_t i = threadIdx.x; i < n; i += blockDim.x) {
|
||||
orow[i] = xr[i] * scale * w[i];
|
||||
}
|
||||
/* q8_1 emission of the q row (row 0 decode): re-read the just-written
|
||||
* orow values (identical bits) in warp-contiguous granules. */
|
||||
if (which == 0u && q81 != NULL && row == 0u && (n & 31u) == 0u) {
|
||||
__syncthreads();
|
||||
const uint32_t lane = threadIdx.x & 31u;
|
||||
const uint32_t warp = threadIdx.x >> 5u;
|
||||
const uint32_t nwarp = blockDim.x >> 5u;
|
||||
for (uint32_t qb = warp; qb < n / 32u; qb += nwarp) {
|
||||
const float v = orow[qb * 32u + lane];
|
||||
float amax = fabsf(v);
|
||||
float s = v;
|
||||
for (uint32_t off = 16u; off > 0u; off >>= 1u) {
|
||||
amax = fmaxf(amax, __shfl_xor_sync(0xffffffffu, amax, off, 32));
|
||||
s += __shfl_xor_sync(0xffffffffu, s, off, 32);
|
||||
}
|
||||
const float d1 = amax / 127.0f;
|
||||
const int8_t qv = amax == 0.0f ? (int8_t)0 : (int8_t)roundf(v / d1);
|
||||
q81[qb].qs[lane] = qv;
|
||||
if (lane == 0u) q81[qb].ds = __floats2half2_rn(d1, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ============ 2b: head_rms + rope_tail(q), scalars pos ============ */
|
||||
__global__ static void head_rms_norm_rope_tail_scalars_kernel(
|
||||
float *x, uint32_t n_tok, uint32_t n_head, uint32_t head_dim, uint32_t n_rot,
|
||||
const struct ds4_decode_scalars * __restrict__ scalars,
|
||||
int32_t pos_offset, uint32_t pos_stride, uint32_t n_ctx_orig, int inverse,
|
||||
float freq_base, float freq_scale, float ext_factor, float attn_factor,
|
||||
float beta_fast, float beta_slow, float eps) {
|
||||
uint32_t row = blockIdx.x;
|
||||
if (row >= n_tok * n_head) return;
|
||||
uint32_t t = row / n_head;
|
||||
float *xr = x + (uint64_t)row * head_dim;
|
||||
float sum = 0.0f;
|
||||
for (uint32_t i = threadIdx.x; i < head_dim; i += blockDim.x) {
|
||||
float v = xr[i];
|
||||
sum += v * v;
|
||||
}
|
||||
__shared__ float partial[256];
|
||||
partial[threadIdx.x] = sum;
|
||||
__syncthreads();
|
||||
for (uint32_t stride = blockDim.x >> 1; stride > 0; stride >>= 1) {
|
||||
if (threadIdx.x < stride) partial[threadIdx.x] += partial[threadIdx.x + stride];
|
||||
__syncthreads();
|
||||
}
|
||||
const float scale = rsqrtf(partial[0] / (float)head_dim + eps);
|
||||
const uint32_t n_nope = head_dim - n_rot;
|
||||
for (uint32_t i = threadIdx.x; i < n_nope; i += blockDim.x) {
|
||||
xr[i] *= scale;
|
||||
}
|
||||
const uint32_t pos0 = (uint32_t)((int32_t)scalars->pos0 + pos_offset);
|
||||
float corr0 = 0.0f, corr1 = 0.0f;
|
||||
if (ext_factor != 0.0f) {
|
||||
float denom = 2.0f * logf(freq_base);
|
||||
corr0 = floorf((float)n_rot * logf((float)n_ctx_orig / (beta_fast * 2.0f * (float)M_PI)) / denom);
|
||||
corr1 = ceilf((float)n_rot * logf((float)n_ctx_orig / (beta_slow * 2.0f * (float)M_PI)) / denom);
|
||||
corr0 = fmaxf(0.0f, corr0);
|
||||
corr1 = fminf((float)(n_rot - 1), corr1);
|
||||
}
|
||||
for (uint32_t pair = threadIdx.x; pair < n_rot / 2; pair += blockDim.x) {
|
||||
uint32_t i = pair * 2u;
|
||||
float theta_extrap = (float)(pos0 + t * pos_stride) * powf(freq_base, -((float)i) / (float)n_rot);
|
||||
float theta_interp = freq_scale * theta_extrap;
|
||||
float theta = theta_interp;
|
||||
float mscale = attn_factor;
|
||||
if (ext_factor != 0.0f) {
|
||||
float ramp_mix = rope_yarn_ramp_dev(corr0, corr1, (int)i) * ext_factor;
|
||||
theta = theta_interp * (1.0f - ramp_mix) + theta_extrap * ramp_mix;
|
||||
mscale *= 1.0f + 0.1f * logf(1.0f / freq_scale);
|
||||
}
|
||||
float c = cosf(theta) * mscale;
|
||||
float s = sinf(theta) * mscale;
|
||||
if (inverse) s = -s;
|
||||
float *tail = xr + n_nope;
|
||||
float x0 = tail[i] * scale;
|
||||
float x1 = tail[i + 1] * scale;
|
||||
tail[i] = x0 * c - x1 * s;
|
||||
tail[i + 1] = x0 * s + x1 * c;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============ 2c: rope_tail(kv) + fp8 quantize + raw store ============ */
|
||||
/* Grid: (n_nope/64 + 1) blocks x 64 threads. Blocks [0, n_nope/64) each own
|
||||
* one independent fp8 group (identical 64-wide shared-tree reduce as the
|
||||
* production kernel, so scales and rounded values are bit-exact) and store
|
||||
* their post-quant region to the raw row. The last block ropes the rotary
|
||||
* tail (32 pairs, verbatim rope_tail_scalars math at n_head=1/t=0) and
|
||||
* stores the rotated tail. Disjoint regions -> no cross-block ordering. */
|
||||
__global__ static void kv_rope_fp8_store_scalars_kernel(
|
||||
float *kv, float *raw, uint32_t raw_cap, uint32_t head_dim, uint32_t n_rot,
|
||||
const struct ds4_decode_scalars * __restrict__ scalars,
|
||||
int32_t pos_offset, uint32_t n_ctx_orig, int inverse,
|
||||
float freq_base, float freq_scale, float ext_factor, float attn_factor,
|
||||
float beta_fast, float beta_slow) {
|
||||
const uint32_t n_nope = head_dim - n_rot;
|
||||
const uint32_t b = blockIdx.x;
|
||||
const uint32_t tid = threadIdx.x;
|
||||
const uint32_t row = scalars->raw_row;
|
||||
float *raw_row = raw + (uint64_t)(row % raw_cap) * head_dim;
|
||||
if (b < n_nope / 64u) {
|
||||
/* fp8 group b: verbatim group math from fp8_kv_quantize_kernel */
|
||||
const uint32_t off = b * 64u;
|
||||
float v = 0.0f;
|
||||
if (off + tid < n_nope) v = kv[off + tid];
|
||||
__shared__ float scratch[64];
|
||||
scratch[tid] = off + tid < n_nope ? fabsf(v) : 0.0f;
|
||||
__syncthreads();
|
||||
for (uint32_t stride = 32; stride > 0; stride >>= 1) {
|
||||
if (tid < stride) scratch[tid] = fmaxf(scratch[tid], scratch[tid + stride]);
|
||||
__syncthreads();
|
||||
}
|
||||
float scale = exp2f(ceilf(log2f(fmaxf(scratch[0], 1.0e-4f) / 448.0f)));
|
||||
if (off + tid < n_nope) {
|
||||
float clamp = fminf(448.0f, fmaxf(-448.0f, v / scale));
|
||||
const float qv = dsv4_e4m3fn_dequant_dev(clamp) * scale;
|
||||
kv[off + tid] = qv;
|
||||
raw_row[off + tid] = __half2float(__float2half(qv));
|
||||
}
|
||||
} else {
|
||||
/* rope the rotary tail then store it */
|
||||
const uint32_t pos0 = (uint32_t)((int32_t)scalars->pos0 + pos_offset);
|
||||
float corr0 = 0.0f, corr1 = 0.0f;
|
||||
if (ext_factor != 0.0f) {
|
||||
float denom = 2.0f * logf(freq_base);
|
||||
corr0 = floorf((float)n_rot * logf((float)n_ctx_orig / (beta_fast * 2.0f * (float)M_PI)) / denom);
|
||||
corr1 = ceilf((float)n_rot * logf((float)n_ctx_orig / (beta_slow * 2.0f * (float)M_PI)) / denom);
|
||||
corr0 = fmaxf(0.0f, corr0);
|
||||
corr1 = fminf((float)(n_rot - 1), corr1);
|
||||
}
|
||||
float *tail = kv + n_nope;
|
||||
if (tid < n_rot / 2u) {
|
||||
const uint32_t i = tid * 2u;
|
||||
float theta_extrap = (float)pos0 * powf(freq_base, -((float)i) / (float)n_rot);
|
||||
float theta_interp = freq_scale * theta_extrap;
|
||||
float theta = theta_interp;
|
||||
float mscale = attn_factor;
|
||||
if (ext_factor != 0.0f) {
|
||||
float ramp_mix = rope_yarn_ramp_dev(corr0, corr1, (int)i) * ext_factor;
|
||||
theta = theta_interp * (1.0f - ramp_mix) + theta_extrap * ramp_mix;
|
||||
mscale *= 1.0f + 0.1f * logf(1.0f / freq_scale);
|
||||
}
|
||||
float c = cosf(theta) * mscale;
|
||||
float s = sinf(theta) * mscale;
|
||||
if (inverse) s = -s;
|
||||
float x0 = tail[i];
|
||||
float x1 = tail[i + 1];
|
||||
const float r0 = x0 * c - x1 * s;
|
||||
const float r1 = x0 * s + x1 * c;
|
||||
tail[i] = r0;
|
||||
tail[i + 1] = r1;
|
||||
raw_row[n_nope + i] = __half2float(__float2half(r0));
|
||||
raw_row[n_nope + i + 1] = __half2float(__float2half(r1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ============ harness ============ */
|
||||
static float *dmalloc_f(uint64_t n) { float *p; CK(cudaMalloc(&p, n * sizeof(float))); return p; }
|
||||
static void fill_rand(float *d, uint64_t n, unsigned seed) {
|
||||
float *h = (float *)malloc(n * sizeof(float));
|
||||
srand(seed);
|
||||
for (uint64_t i = 0; i < n; i++) h[i] = ((float)rand() / RAND_MAX - 0.5f) * 4.0f;
|
||||
CK(cudaMemcpy(d, h, n * sizeof(float), cudaMemcpyHostToDevice));
|
||||
free(h);
|
||||
}
|
||||
static int cmp_dev(const char *what, const float *a, const float *b, uint64_t n) {
|
||||
float *ha = (float *)malloc(n * sizeof(float)), *hb = (float *)malloc(n * sizeof(float));
|
||||
CK(cudaMemcpy(ha, a, n * sizeof(float), cudaMemcpyDeviceToHost));
|
||||
CK(cudaMemcpy(hb, b, n * sizeof(float), cudaMemcpyDeviceToHost));
|
||||
int bad = memcmp(ha, hb, n * sizeof(float)) != 0;
|
||||
if (bad) {
|
||||
uint64_t k = 0;
|
||||
for (; k < n; k++) if (ha[k] != hb[k]) break;
|
||||
printf(" %s: MISMATCH at %llu (%a vs %a)\n", what, (unsigned long long)k, ha[k], hb[k]);
|
||||
} else printf(" %s: bit-identical (%llu floats)\n", what, (unsigned long long)n);
|
||||
free(ha); free(hb);
|
||||
return bad;
|
||||
}
|
||||
|
||||
int main() {
|
||||
cudaStream_t st;
|
||||
CK(cudaStreamCreate(&st));
|
||||
int total_bad = 0;
|
||||
|
||||
/* rope parameter sets: plain and yarn-extended */
|
||||
struct RP { float fb, fs, ef, af, bf, bs; uint32_t ctxo; const char *name; };
|
||||
RP rps[2] = {
|
||||
{ 10000.0f, 1.0f, 0.0f, 1.0f, 32.0f, 1.0f, 0u, "plain" },
|
||||
{ 10000.0f, 0.25f, 1.0f, 1.0f, 32.0f, 1.0f, 4096u, "yarn" },
|
||||
};
|
||||
uint32_t poss[3] = { 0u, 999u, 32767u };
|
||||
|
||||
ds4_decode_scalars hs;
|
||||
memset(&hs, 0, sizeof(hs));
|
||||
ds4_decode_scalars *ds;
|
||||
CK(cudaMalloc(&ds, sizeof(hs)));
|
||||
|
||||
/* ---- 2a ---- */
|
||||
{
|
||||
float *q_in = dmalloc_f(Q_RANK), *kv_in = dmalloc_f(KV_DIM);
|
||||
float *qw = dmalloc_f(Q_RANK), *kvw = dmalloc_f(KV_DIM);
|
||||
float *qo_r = dmalloc_f(Q_RANK), *kvo_r = dmalloc_f(KV_DIM);
|
||||
float *qo_f = dmalloc_f(Q_RANK), *kvo_f = dmalloc_f(KV_DIM);
|
||||
block_q8_1_t *q81_r, *q81_f;
|
||||
CK(cudaMalloc(&q81_r, (Q_RANK / 32) * sizeof(block_q8_1_t)));
|
||||
CK(cudaMalloc(&q81_f, (Q_RANK / 32) * sizeof(block_q8_1_t)));
|
||||
fill_rand(q_in, Q_RANK, 11); fill_rand(kv_in, KV_DIM, 12);
|
||||
fill_rand(qw, Q_RANK, 13); fill_rand(kvw, KV_DIM, 14);
|
||||
dim3 g(1, 2, 1);
|
||||
dsv4_qkv_rms_norm_rows_kernel<<<g, 256, 0, st>>>(qo_r, q_in, qw, Q_RANK, kvo_r, kv_in, kvw, KV_DIM, 1, RMS_EPS);
|
||||
ref_quantize_q8_1_kernel<<<Q_RANK / 32, 32, 0, st>>>(q81_r, qo_r, Q_RANK);
|
||||
dsv4_qkv_rms_norm_rows_q81_kernel<<<g, 256, 0, st>>>(qo_f, q_in, qw, Q_RANK, kvo_f, kv_in, kvw, KV_DIM, 1, RMS_EPS, q81_f);
|
||||
CK(cudaStreamSynchronize(st));
|
||||
printf("2a qkv_rms + q8_1 emission:\n");
|
||||
total_bad += cmp_dev("q_out", qo_r, qo_f, Q_RANK);
|
||||
total_bad += cmp_dev("kv_out", kvo_r, kvo_f, KV_DIM);
|
||||
char *h1 = (char *)malloc((Q_RANK / 32) * sizeof(block_q8_1_t));
|
||||
char *h2 = (char *)malloc((Q_RANK / 32) * sizeof(block_q8_1_t));
|
||||
CK(cudaMemcpy(h1, q81_r, (Q_RANK / 32) * sizeof(block_q8_1_t), cudaMemcpyDeviceToHost));
|
||||
CK(cudaMemcpy(h2, q81_f, (Q_RANK / 32) * sizeof(block_q8_1_t), cudaMemcpyDeviceToHost));
|
||||
int bad = memcmp(h1, h2, (Q_RANK / 32) * sizeof(block_q8_1_t)) != 0;
|
||||
printf(" q8_1 codes: %s\n", bad ? "MISMATCH" : "bit-identical");
|
||||
total_bad += bad;
|
||||
free(h1); free(h2);
|
||||
}
|
||||
|
||||
/* ---- 2b ---- */
|
||||
for (int r = 0; r < 2; r++) for (int p = 0; p < 3; p++) {
|
||||
RP rp = rps[r];
|
||||
hs.pos0 = poss[p];
|
||||
CK(cudaMemcpy(ds, &hs, sizeof(hs), cudaMemcpyHostToDevice));
|
||||
const uint64_t qn = (uint64_t)N_HEAD * HEAD_DIM;
|
||||
float *qa = dmalloc_f(qn), *qb = dmalloc_f(qn);
|
||||
fill_rand(qa, qn, 100 + r * 10 + p);
|
||||
CK(cudaMemcpy(qb, qa, qn * sizeof(float), cudaMemcpyDeviceToDevice));
|
||||
head_rms_norm_kernel<<<N_HEAD, 256, 0, st>>>(qa, 1, N_HEAD, HEAD_DIM, RMS_EPS);
|
||||
uint32_t pairs = N_HEAD * (N_ROT / 2);
|
||||
rope_tail_scalars_kernel<<<(pairs + 255) / 256, 256, 0, st>>>(qa, 1, N_HEAD, HEAD_DIM, N_ROT,
|
||||
ds, 0, 1u, rp.ctxo, 0, rp.fb, rp.fs, rp.ef, rp.af, rp.bf, rp.bs);
|
||||
head_rms_norm_rope_tail_scalars_kernel<<<N_HEAD, 256, 0, st>>>(qb, 1, N_HEAD, HEAD_DIM, N_ROT,
|
||||
ds, 0, 1u, rp.ctxo, 0, rp.fb, rp.fs, rp.ef, rp.af, rp.bf, rp.bs, RMS_EPS);
|
||||
CK(cudaStreamSynchronize(st));
|
||||
printf("2b head_rms+rope (%s, pos=%u):\n", rp.name, poss[p]);
|
||||
total_bad += cmp_dev("q", qa, qb, qn);
|
||||
CK(cudaFree(qa)); CK(cudaFree(qb));
|
||||
}
|
||||
|
||||
/* ---- 2c ---- */
|
||||
for (int r = 0; r < 2; r++) for (int p = 0; p < 3; p++) {
|
||||
RP rp = rps[r];
|
||||
hs.pos0 = poss[p];
|
||||
hs.raw_row = poss[p] % RAW_CAP;
|
||||
CK(cudaMemcpy(ds, &hs, sizeof(hs), cudaMemcpyHostToDevice));
|
||||
float *kva = dmalloc_f(KV_DIM), *kvb = dmalloc_f(KV_DIM);
|
||||
float *rawa = dmalloc_f((uint64_t)RAW_CAP * HEAD_DIM), *rawb = dmalloc_f((uint64_t)RAW_CAP * HEAD_DIM);
|
||||
CK(cudaMemset(rawa, 0, (uint64_t)RAW_CAP * HEAD_DIM * sizeof(float)));
|
||||
CK(cudaMemset(rawb, 0, (uint64_t)RAW_CAP * HEAD_DIM * sizeof(float)));
|
||||
fill_rand(kva, KV_DIM, 200 + r * 10 + p);
|
||||
CK(cudaMemcpy(kvb, kva, KV_DIM * sizeof(float), cudaMemcpyDeviceToDevice));
|
||||
/* reference chain: rope(kv) -> fp8 -> store */
|
||||
uint32_t pairs = 1 * (N_ROT / 2);
|
||||
rope_tail_scalars_kernel<<<(pairs + 255) / 256, 256, 0, st>>>(kva, 1, 1, HEAD_DIM, N_ROT,
|
||||
ds, 0, 1u, rp.ctxo, 0, rp.fb, rp.fs, rp.ef, rp.af, rp.bf, rp.bs);
|
||||
fp8_kv_quantize_kernel<<<1, 64, 0, st>>>(kva, 1, HEAD_DIM, N_ROT, NULL, NULL);
|
||||
store_raw_kv_batch_kernel<<<(HEAD_DIM + 255) / 256, 256, 0, st>>>(rawa, kva, RAW_CAP, 0, 1, HEAD_DIM, NULL, NULL, ds);
|
||||
/* fused */
|
||||
kv_rope_fp8_store_scalars_kernel<<<(HEAD_DIM - N_ROT) / 64 + 1, 64, 0, st>>>(kvb, rawb, RAW_CAP, HEAD_DIM, N_ROT,
|
||||
ds, 0, rp.ctxo, 0, rp.fb, rp.fs, rp.ef, rp.af, rp.bf, rp.bs);
|
||||
CK(cudaStreamSynchronize(st));
|
||||
printf("2c kv rope+fp8+store (%s, pos=%u, row=%u):\n", rp.name, poss[p], hs.raw_row);
|
||||
total_bad += cmp_dev("kv", kva, kvb, KV_DIM);
|
||||
total_bad += cmp_dev("raw row", rawa + (uint64_t)hs.raw_row * HEAD_DIM,
|
||||
rawb + (uint64_t)hs.raw_row * HEAD_DIM, HEAD_DIM);
|
||||
CK(cudaFree(kva)); CK(cudaFree(kvb)); CK(cudaFree(rawa)); CK(cudaFree(rawb));
|
||||
}
|
||||
|
||||
/* ---- timing: reference chains vs fused, 43-layer sweeps ---- */
|
||||
{
|
||||
const int REPS = 500, LAYERS = 43;
|
||||
hs.pos0 = 1000u; hs.raw_row = 1000u % RAW_CAP;
|
||||
CK(cudaMemcpy(ds, &hs, sizeof(hs), cudaMemcpyHostToDevice));
|
||||
RP rp = rps[1];
|
||||
const uint64_t qn = (uint64_t)N_HEAD * HEAD_DIM;
|
||||
float *q = dmalloc_f(qn), *kv = dmalloc_f(KV_DIM);
|
||||
float *raw = dmalloc_f((uint64_t)RAW_CAP * HEAD_DIM);
|
||||
float *qi = dmalloc_f(Q_RANK), *kvi = dmalloc_f(KV_DIM);
|
||||
float *qw = dmalloc_f(Q_RANK), *kvw = dmalloc_f(KV_DIM);
|
||||
float *qo = dmalloc_f(Q_RANK), *kvo = dmalloc_f(KV_DIM);
|
||||
block_q8_1_t *q81; CK(cudaMalloc(&q81, (Q_RANK / 32) * sizeof(block_q8_1_t)));
|
||||
fill_rand(q, qn, 31); fill_rand(kv, KV_DIM, 32);
|
||||
fill_rand(qi, Q_RANK, 33); fill_rand(kvi, KV_DIM, 34);
|
||||
fill_rand(qw, Q_RANK, 35); fill_rand(kvw, KV_DIM, 36);
|
||||
cudaEvent_t e0, e1; CK(cudaEventCreate(&e0)); CK(cudaEventCreate(&e1));
|
||||
uint32_t pairs_q = N_HEAD * (N_ROT / 2), pairs_kv = N_ROT / 2;
|
||||
dim3 g2(1, 2, 1);
|
||||
float ms;
|
||||
/* reference chain */
|
||||
for (int w = 0; w < 2; w++) {
|
||||
if (w) CK(cudaEventRecord(e0, st));
|
||||
for (int rr = 0; rr < (w ? REPS : 10); rr++) for (int l = 0; l < LAYERS; l++) {
|
||||
dsv4_qkv_rms_norm_rows_kernel<<<g2, 256, 0, st>>>(qo, qi, qw, Q_RANK, kvo, kvi, kvw, KV_DIM, 1, RMS_EPS);
|
||||
ref_quantize_q8_1_kernel<<<Q_RANK / 32, 32, 0, st>>>(q81, qo, Q_RANK);
|
||||
head_rms_norm_kernel<<<N_HEAD, 256, 0, st>>>(q, 1, N_HEAD, HEAD_DIM, RMS_EPS);
|
||||
rope_tail_scalars_kernel<<<(pairs_q + 255) / 256, 256, 0, st>>>(q, 1, N_HEAD, HEAD_DIM, N_ROT, ds, 0, 1u, rp.ctxo, 0, rp.fb, rp.fs, rp.ef, rp.af, rp.bf, rp.bs);
|
||||
rope_tail_scalars_kernel<<<1, 256, 0, st>>>(kv, 1, 1, HEAD_DIM, N_ROT, ds, 0, 1u, rp.ctxo, 0, rp.fb, rp.fs, rp.ef, rp.af, rp.bf, rp.bs);
|
||||
fp8_kv_quantize_kernel<<<1, 64, 0, st>>>(kv, 1, HEAD_DIM, N_ROT, NULL, NULL);
|
||||
store_raw_kv_batch_kernel<<<(HEAD_DIM + 255) / 256, 256, 0, st>>>(raw, kv, RAW_CAP, 0, 1, HEAD_DIM, NULL, NULL, ds);
|
||||
(void)pairs_kv;
|
||||
}
|
||||
if (w) { CK(cudaEventRecord(e1, st)); CK(cudaStreamSynchronize(st)); }
|
||||
else CK(cudaStreamSynchronize(st));
|
||||
}
|
||||
CK(cudaEventElapsedTime(&ms, e0, e1));
|
||||
printf("timing reference chain (7 launches): %.2f us/layer (x43 = %.3f ms/step)\n",
|
||||
1000.0f * ms / (REPS * LAYERS), ms / REPS);
|
||||
/* fused chain */
|
||||
for (int w = 0; w < 2; w++) {
|
||||
if (w) CK(cudaEventRecord(e0, st));
|
||||
for (int rr = 0; rr < (w ? REPS : 10); rr++) for (int l = 0; l < LAYERS; l++) {
|
||||
dsv4_qkv_rms_norm_rows_q81_kernel<<<g2, 256, 0, st>>>(qo, qi, qw, Q_RANK, kvo, kvi, kvw, KV_DIM, 1, RMS_EPS, q81);
|
||||
head_rms_norm_rope_tail_scalars_kernel<<<N_HEAD, 256, 0, st>>>(q, 1, N_HEAD, HEAD_DIM, N_ROT, ds, 0, 1u, rp.ctxo, 0, rp.fb, rp.fs, rp.ef, rp.af, rp.bf, rp.bs, RMS_EPS);
|
||||
kv_rope_fp8_store_scalars_kernel<<<(HEAD_DIM - N_ROT) / 64 + 1, 64, 0, st>>>(kv, raw, RAW_CAP, HEAD_DIM, N_ROT, ds, 0, rp.ctxo, 0, rp.fb, rp.fs, rp.ef, rp.af, rp.bf, rp.bs);
|
||||
}
|
||||
if (w) { CK(cudaEventRecord(e1, st)); CK(cudaStreamSynchronize(st)); }
|
||||
else CK(cudaStreamSynchronize(st));
|
||||
}
|
||||
CK(cudaEventElapsedTime(&ms, e0, e1));
|
||||
printf("timing fused chain (3 launches): %.2f us/layer (x43 = %.3f ms/step)\n",
|
||||
1000.0f * ms / (REPS * LAYERS), ms / REPS);
|
||||
}
|
||||
|
||||
printf(total_bad ? "PROTO_M2_QKV FAILED (%d)\n" : "PROTO_M2_QKV_DONE all bit-exact\n", total_bad);
|
||||
return total_bad != 0;
|
||||
}
|
||||
@@ -0,0 +1,939 @@
|
||||
/* proto_m2_router.cu — M2-Inc3 prototype: fused router
|
||||
* (f16 split-K logits matmul + combine + top-6 select) as ONE cooperative
|
||||
* kernel, replacing the production 3-kernel chain
|
||||
* matmul_f16_splitk_kernel (grid 256x8, 256 thr)
|
||||
* + matmul_f16_splitk_combine_kernel (1 blk, 256 thr)
|
||||
* + router_select_warp_topk_kernel (1 blk, 32x4)
|
||||
* at the router shape in_dim=4096, out_dim=256 (=n_expert), n_tok=1, ksplit=8.
|
||||
*
|
||||
* Gate: all four outputs (logits, probs, selected, weights) BIT-IDENTICAL to
|
||||
* the reference chain, in topk-bias / no-bias / hash modes, incl. exact-tie
|
||||
* logits; capture==eager bit-identity; then per-layer timing vs the chain.
|
||||
*
|
||||
* Bit-exactness argument for the matmul phase: the ref (row,kseg) block gives
|
||||
* each thread tid<64 exactly one 8-half chunk (seg 512 = 64 chunks); its
|
||||
* block_reduce_sum_256 reduces warp 0 (chunks 0..31) and warp 1 (chunks
|
||||
* 32..63) with warp_sum_f32, then the cross-warp tree only adds those two
|
||||
* warp sums and exact zeros. The fused warp-per-tile form computes the SAME
|
||||
* chunk values at the SAME lanes, applies the SAME warp_sum_f32 trees, and
|
||||
* adds s0+s1 -- identical up to x+0.0f==x (breaks only for a partial that is
|
||||
* exactly -0.0f, i.e. an all-zero activation segment; unreachable for
|
||||
* rms-normed activations, and select-invariant even then).
|
||||
*
|
||||
* Build (GB10):
|
||||
* nvcc -O3 --use_fast_math -std=c++17 -arch=sm_121 proto_m2_router.cu -o proto_m2_router
|
||||
*/
|
||||
#include <cooperative_groups.h>
|
||||
#include <cuda_fp16.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
|
||||
#define CHECK(x) do { cudaError_t e_ = (x); if (e_ != cudaSuccess) { \
|
||||
fprintf(stderr, "CUDA error %s at %s:%d\n", cudaGetErrorString(e_), __FILE__, __LINE__); exit(1); } } while (0)
|
||||
|
||||
#define N_IN 4096u
|
||||
#define N_EXPERT 256u
|
||||
#define KSPLIT 8u
|
||||
#define SEG 512u
|
||||
#define N_TOP 6u
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Vendored production device functions + kernels (ds4_cuda.cu, verbatim) */
|
||||
|
||||
struct ds4_decode_scalars {
|
||||
uint32_t pos0;
|
||||
uint32_t raw_row;
|
||||
uint32_t raw_start;
|
||||
uint32_t n_raw;
|
||||
uint32_t n_comp;
|
||||
uint32_t emit_phase;
|
||||
uint32_t comp_row;
|
||||
uint32_t index_row;
|
||||
uint32_t flags;
|
||||
uint32_t token;
|
||||
};
|
||||
|
||||
__device__ static float warp_sum_f32(float v) {
|
||||
for (int offset = 16; offset > 0; offset >>= 1) {
|
||||
v += __shfl_down_sync(0xffffffffu, v, offset);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
__device__ static float block_reduce_sum_256(float v) {
|
||||
v = warp_sum_f32(v);
|
||||
__shared__ float s[8];
|
||||
const uint32_t lane = threadIdx.x & 31u;
|
||||
const uint32_t warp = threadIdx.x >> 5u;
|
||||
if (lane == 0u) s[warp] = v;
|
||||
__syncthreads();
|
||||
float r = 0.0f;
|
||||
if (warp == 0u) {
|
||||
r = (lane < 8u) ? s[lane] : 0.0f;
|
||||
r += __shfl_down_sync(0xffffffffu, r, 4);
|
||||
r += __shfl_down_sync(0xffffffffu, r, 2);
|
||||
r += __shfl_down_sync(0xffffffffu, r, 1);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
__global__ static void matmul_f16_splitk_kernel(
|
||||
float *partial,
|
||||
const __half *w,
|
||||
const float *x,
|
||||
uint64_t in_dim,
|
||||
uint64_t out_dim,
|
||||
uint32_t ksplit,
|
||||
int vec_ok) {
|
||||
const uint64_t row = (uint64_t)blockIdx.x;
|
||||
const uint32_t kseg = blockIdx.y;
|
||||
if (row >= out_dim) return;
|
||||
uint64_t seg = (in_dim + (uint64_t)ksplit - 1u) / (uint64_t)ksplit;
|
||||
seg = (seg + 7u) & ~(uint64_t)7u;
|
||||
const uint64_t k0 = (uint64_t)kseg * seg;
|
||||
if (k0 >= in_dim) {
|
||||
if (threadIdx.x == 0u) partial[row * (uint64_t)ksplit + (uint64_t)kseg] = 0.0f;
|
||||
return;
|
||||
}
|
||||
uint64_t k1 = k0 + seg;
|
||||
if (k1 > in_dim) k1 = in_dim;
|
||||
const __half *wr = w + row * in_dim;
|
||||
const uint32_t tid = threadIdx.x;
|
||||
const uint32_t nt = blockDim.x;
|
||||
float sum = 0.0f;
|
||||
if (vec_ok) {
|
||||
const uint64_t vend = k0 + ((k1 - k0) & ~(uint64_t)7u);
|
||||
for (uint64_t i = k0 + (uint64_t)tid * 8u; i < vend; i += (uint64_t)nt * 8u) {
|
||||
const uint4 wv = *reinterpret_cast<const uint4 *>(wr + i);
|
||||
const float4 xa = *reinterpret_cast<const float4 *>(x + i);
|
||||
const float4 xb = *reinterpret_cast<const float4 *>(x + i + 4);
|
||||
const __half2 *h = reinterpret_cast<const __half2 *>(&wv);
|
||||
const float2 w0 = __half22float2(h[0]);
|
||||
const float2 w1 = __half22float2(h[1]);
|
||||
const float2 w2 = __half22float2(h[2]);
|
||||
const float2 w3 = __half22float2(h[3]);
|
||||
sum += w0.x * xa.x + w0.y * xa.y + w1.x * xa.z + w1.y * xa.w
|
||||
+ w2.x * xb.x + w2.y * xb.y + w3.x * xb.z + w3.y * xb.w;
|
||||
}
|
||||
for (uint64_t i = vend + tid; i < k1; i += nt) sum += __half2float(wr[i]) * x[i];
|
||||
} else {
|
||||
for (uint64_t i = k0 + tid; i < k1; i += nt) sum += __half2float(wr[i]) * x[i];
|
||||
}
|
||||
sum = block_reduce_sum_256(sum);
|
||||
if (threadIdx.x == 0u) partial[row * (uint64_t)ksplit + (uint64_t)kseg] = sum;
|
||||
}
|
||||
|
||||
__global__ static void matmul_f16_splitk_combine_kernel(
|
||||
float *out,
|
||||
const float *partial,
|
||||
uint64_t out_dim,
|
||||
uint32_t ksplit) {
|
||||
const uint64_t row = (uint64_t)blockIdx.x * (uint64_t)blockDim.x + (uint64_t)threadIdx.x;
|
||||
if (row >= out_dim) return;
|
||||
const float *p = partial + row * (uint64_t)ksplit;
|
||||
float s = 0.0f;
|
||||
for (uint32_t k = 0u; k < ksplit; k++) s += p[k];
|
||||
out[row] = s;
|
||||
}
|
||||
|
||||
__device__ static float softplus_dev(float x) {
|
||||
if (x > 20.0f) return x;
|
||||
if (x < -20.0f) return expf(x);
|
||||
return log1pf(expf(x));
|
||||
}
|
||||
|
||||
__device__ __forceinline__ static bool router_score_better(float av, uint32_t ai, float bv, uint32_t bi) {
|
||||
return av > bv || (av == bv && ai < bi);
|
||||
}
|
||||
|
||||
__global__ static void router_select_warp_topk_kernel(
|
||||
int32_t *selected,
|
||||
float *weights,
|
||||
float *probs,
|
||||
const float *bias,
|
||||
const int32_t *hash,
|
||||
const float *logits,
|
||||
const int32_t *tokens,
|
||||
int32_t token_scalar,
|
||||
uint32_t hash_rows,
|
||||
uint32_t n_tokens,
|
||||
int has_bias,
|
||||
int hash_mode,
|
||||
const struct ds4_decode_scalars *scalars) {
|
||||
const uint32_t lane = threadIdx.x;
|
||||
const uint32_t row_in_block = threadIdx.y;
|
||||
const uint32_t t = blockIdx.x * blockDim.y + row_in_block;
|
||||
if (t >= n_tokens || lane >= 32u) return;
|
||||
|
||||
const float *log = logits + (uint64_t)t * 256u;
|
||||
float *prob = probs + (uint64_t)t * 256u;
|
||||
int32_t *sel = selected + (uint64_t)t * 6u;
|
||||
float *w = weights + (uint64_t)t * 6u;
|
||||
__shared__ float sprob[4][256];
|
||||
float local_prob[8];
|
||||
float local_score[8];
|
||||
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 8u; j++) {
|
||||
const uint32_t e = lane + j * 32u;
|
||||
const float p = sqrtf(softplus_dev(log[e]));
|
||||
local_prob[j] = p;
|
||||
local_score[j] = p + (has_bias ? bias[e] : 0.0f);
|
||||
sprob[row_in_block][e] = p;
|
||||
prob[e] = p;
|
||||
}
|
||||
__syncwarp();
|
||||
|
||||
if (hash_mode) {
|
||||
if (lane == 0) {
|
||||
int32_t tok = tokens ? tokens[t]
|
||||
: (scalars ? (int32_t)scalars->token : token_scalar);
|
||||
if (tok < 0 || (uint32_t)tok >= hash_rows) tok = 0;
|
||||
const int32_t *row = hash + (uint64_t)tok * 6u;
|
||||
float sum = 0.0f;
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 6u; j++) {
|
||||
const int32_t e = row[j];
|
||||
sel[j] = e;
|
||||
const float v = (e >= 0 && e < 256) ? sprob[row_in_block][(uint32_t)e] : 0.0f;
|
||||
w[j] = v;
|
||||
sum += v;
|
||||
}
|
||||
sum = fmaxf(sum, 6.103515625e-5f);
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 6u; j++) w[j] = w[j] / sum * 1.5f;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
float out_prob[6] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||
uint32_t out_idx[6] = {0, 0, 0, 0, 0, 0};
|
||||
#pragma unroll
|
||||
for (uint32_t k = 0; k < 6u; k++) {
|
||||
float best_score = -INFINITY;
|
||||
float best_prob = 0.0f;
|
||||
uint32_t best_idx = UINT32_MAX;
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 8u; j++) {
|
||||
const uint32_t e = lane + j * 32u;
|
||||
const float s = local_score[j];
|
||||
if (router_score_better(s, e, best_score, best_idx)) {
|
||||
best_score = s;
|
||||
best_prob = local_prob[j];
|
||||
best_idx = e;
|
||||
}
|
||||
}
|
||||
#pragma unroll
|
||||
for (uint32_t mask = 16u; mask > 0u; mask >>= 1u) {
|
||||
const float other_score = __shfl_xor_sync(0xffffffffu, best_score, mask);
|
||||
const float other_prob = __shfl_xor_sync(0xffffffffu, best_prob, mask);
|
||||
const uint32_t other_idx = __shfl_xor_sync(0xffffffffu, best_idx, mask);
|
||||
if (router_score_better(other_score, other_idx, best_score, best_idx)) {
|
||||
best_score = other_score;
|
||||
best_prob = other_prob;
|
||||
best_idx = other_idx;
|
||||
}
|
||||
}
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 8u; j++) {
|
||||
const uint32_t e = lane + j * 32u;
|
||||
if (e == best_idx) local_score[j] = -INFINITY;
|
||||
}
|
||||
if (lane == 0) {
|
||||
out_idx[k] = best_idx;
|
||||
out_prob[k] = best_prob;
|
||||
}
|
||||
}
|
||||
|
||||
if (lane == 0) {
|
||||
float sum = 0.0f;
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 6u; j++) {
|
||||
sel[j] = (int32_t)out_idx[j];
|
||||
w[j] = out_prob[j];
|
||||
sum += out_prob[j];
|
||||
}
|
||||
sum = fmaxf(sum, 6.103515625e-5f);
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 6u; j++) w[j] = w[j] / sum * 1.5f;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* FUSED: one cooperative kernel for the whole router stage. */
|
||||
|
||||
__global__ static void router_fused_coop_kernel(
|
||||
float *logits, /* [256] */
|
||||
float *partials, /* [256*8] dedicated scratch */
|
||||
int32_t *selected, /* [6] */
|
||||
float *weights, /* [6] */
|
||||
float *probs, /* [256] */
|
||||
const __half *w, /* [256][4096] f16 */
|
||||
const float *x, /* [4096] */
|
||||
const float *bias, /* [256] or NULL */
|
||||
const int32_t *hash, /* [hash_rows][6] or NULL */
|
||||
const int32_t *tokens, /* decode: NULL */
|
||||
int32_t token_scalar,
|
||||
uint32_t hash_rows,
|
||||
int has_bias,
|
||||
int hash_mode,
|
||||
const struct ds4_decode_scalars *scalars) {
|
||||
__shared__ float sbias[256];
|
||||
__shared__ float sprob[256];
|
||||
__shared__ int32_t shash[6];
|
||||
const uint32_t lane = threadIdx.x & 31u;
|
||||
const uint32_t warp = threadIdx.x >> 5u;
|
||||
|
||||
/* Phase 0 (block 0): prefetch the tail's small model-map reads so their
|
||||
* latency hides behind the matmul phase. Verbatim copies -- the select
|
||||
* below consumes identical values. */
|
||||
if (blockIdx.x == 0u) {
|
||||
if (has_bias) sbias[threadIdx.x] = bias[threadIdx.x];
|
||||
if (hash_mode && threadIdx.x == 0u) {
|
||||
int32_t tok = tokens ? tokens[0]
|
||||
: (scalars ? (int32_t)scalars->token : token_scalar);
|
||||
if (tok < 0 || (uint32_t)tok >= hash_rows) tok = 0;
|
||||
const int32_t *row = hash + (uint64_t)tok * 6u;
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 6u; j++) shash[j] = row[j];
|
||||
}
|
||||
}
|
||||
|
||||
/* Phase 1: split-K logits matmul, one warp per (row, kseg) tile.
|
||||
* Chunk values, lane placement, and both warp_sum_f32 trees replicate the
|
||||
* ref block's warps 0/1 exactly; s0+s1 matches its cross-warp tree, which
|
||||
* otherwise only adds exact zeros. */
|
||||
const uint32_t nwarp = gridDim.x * (blockDim.x >> 5u);
|
||||
for (uint32_t t = blockIdx.x * (blockDim.x >> 5u) + warp;
|
||||
t < N_EXPERT * KSPLIT; t += nwarp) {
|
||||
const uint32_t row = t >> 3u;
|
||||
const uint32_t kseg = t & 7u;
|
||||
const uint64_t k0 = (uint64_t)kseg * SEG;
|
||||
const __half *wr = w + (uint64_t)row * N_IN;
|
||||
float sum0 = 0.0f;
|
||||
float sum1 = 0.0f;
|
||||
{
|
||||
const uint64_t i = k0 + (uint64_t)lane * 8u;
|
||||
const uint4 wv = *reinterpret_cast<const uint4 *>(wr + i);
|
||||
const float4 xa = *reinterpret_cast<const float4 *>(x + i);
|
||||
const float4 xb = *reinterpret_cast<const float4 *>(x + i + 4);
|
||||
const __half2 *h = reinterpret_cast<const __half2 *>(&wv);
|
||||
const float2 w0 = __half22float2(h[0]);
|
||||
const float2 w1 = __half22float2(h[1]);
|
||||
const float2 w2 = __half22float2(h[2]);
|
||||
const float2 w3 = __half22float2(h[3]);
|
||||
sum0 += w0.x * xa.x + w0.y * xa.y + w1.x * xa.z + w1.y * xa.w
|
||||
+ w2.x * xb.x + w2.y * xb.y + w3.x * xb.z + w3.y * xb.w;
|
||||
}
|
||||
{
|
||||
const uint64_t i = k0 + ((uint64_t)lane + 32u) * 8u;
|
||||
const uint4 wv = *reinterpret_cast<const uint4 *>(wr + i);
|
||||
const float4 xa = *reinterpret_cast<const float4 *>(x + i);
|
||||
const float4 xb = *reinterpret_cast<const float4 *>(x + i + 4);
|
||||
const __half2 *h = reinterpret_cast<const __half2 *>(&wv);
|
||||
const float2 w0 = __half22float2(h[0]);
|
||||
const float2 w1 = __half22float2(h[1]);
|
||||
const float2 w2 = __half22float2(h[2]);
|
||||
const float2 w3 = __half22float2(h[3]);
|
||||
sum1 += w0.x * xa.x + w0.y * xa.y + w1.x * xa.z + w1.y * xa.w
|
||||
+ w2.x * xb.x + w2.y * xb.y + w3.x * xb.z + w3.y * xb.w;
|
||||
}
|
||||
const float s0 = warp_sum_f32(sum0);
|
||||
const float s1 = warp_sum_f32(sum1);
|
||||
if (lane == 0u) partials[(uint64_t)row * KSPLIT + kseg] = s0 + s1;
|
||||
}
|
||||
|
||||
cooperative_groups::this_grid().sync();
|
||||
if (blockIdx.x != 0u) return;
|
||||
|
||||
/* Phase 2: combine partials in fixed kseg order (verbatim combine kernel;
|
||||
* out_dim 256 == one 256-thread block). */
|
||||
{
|
||||
const uint64_t row = (uint64_t)threadIdx.x;
|
||||
const float *p = partials + row * KSPLIT;
|
||||
float s = 0.0f;
|
||||
for (uint32_t k = 0u; k < KSPLIT; k++) s += p[k];
|
||||
logits[row] = s;
|
||||
}
|
||||
__syncthreads();
|
||||
|
||||
/* Phase 3: verbatim router_select_warp_topk_kernel warp body (t = 0,
|
||||
* row_in_block = 0); bias/hash reads swapped for the prefetched copies. */
|
||||
if (warp != 0u) return;
|
||||
const float *log = logits;
|
||||
float local_prob[8];
|
||||
float local_score[8];
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 8u; j++) {
|
||||
const uint32_t e = lane + j * 32u;
|
||||
const float p = sqrtf(softplus_dev(log[e]));
|
||||
local_prob[j] = p;
|
||||
local_score[j] = p + (has_bias ? sbias[e] : 0.0f);
|
||||
sprob[e] = p;
|
||||
probs[e] = p;
|
||||
}
|
||||
__syncwarp();
|
||||
|
||||
if (hash_mode) {
|
||||
if (lane == 0u) {
|
||||
float sum = 0.0f;
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 6u; j++) {
|
||||
const int32_t e = shash[j];
|
||||
selected[j] = e;
|
||||
const float v = (e >= 0 && e < 256) ? sprob[(uint32_t)e] : 0.0f;
|
||||
weights[j] = v;
|
||||
sum += v;
|
||||
}
|
||||
sum = fmaxf(sum, 6.103515625e-5f);
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 6u; j++) weights[j] = weights[j] / sum * 1.5f;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
float out_prob[6] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||
uint32_t out_idx[6] = {0, 0, 0, 0, 0, 0};
|
||||
#pragma unroll
|
||||
for (uint32_t k = 0; k < 6u; k++) {
|
||||
float best_score = -INFINITY;
|
||||
float best_prob = 0.0f;
|
||||
uint32_t best_idx = UINT32_MAX;
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 8u; j++) {
|
||||
const uint32_t e = lane + j * 32u;
|
||||
const float s = local_score[j];
|
||||
if (router_score_better(s, e, best_score, best_idx)) {
|
||||
best_score = s;
|
||||
best_prob = local_prob[j];
|
||||
best_idx = e;
|
||||
}
|
||||
}
|
||||
#pragma unroll
|
||||
for (uint32_t mask = 16u; mask > 0u; mask >>= 1u) {
|
||||
const float other_score = __shfl_xor_sync(0xffffffffu, best_score, mask);
|
||||
const float other_prob = __shfl_xor_sync(0xffffffffu, best_prob, mask);
|
||||
const uint32_t other_idx = __shfl_xor_sync(0xffffffffu, best_idx, mask);
|
||||
if (router_score_better(other_score, other_idx, best_score, best_idx)) {
|
||||
best_score = other_score;
|
||||
best_prob = other_prob;
|
||||
best_idx = other_idx;
|
||||
}
|
||||
}
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 8u; j++) {
|
||||
const uint32_t e = lane + j * 32u;
|
||||
if (e == best_idx) local_score[j] = -INFINITY;
|
||||
}
|
||||
if (lane == 0) {
|
||||
out_idx[k] = best_idx;
|
||||
out_prob[k] = best_prob;
|
||||
}
|
||||
}
|
||||
|
||||
if (lane == 0) {
|
||||
float sum = 0.0f;
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 6u; j++) {
|
||||
selected[j] = (int32_t)out_idx[j];
|
||||
weights[j] = out_prob[j];
|
||||
sum += out_prob[j];
|
||||
}
|
||||
sum = fmaxf(sum, 6.103515625e-5f);
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 6u; j++) weights[j] = weights[j] / sum * 1.5f;
|
||||
}
|
||||
}
|
||||
|
||||
/* V2: identical numerics, but each warp owns TWO tiles per pass with all four
|
||||
* DRAM-latency w-vector loads hoisted before any math, doubling per-warp
|
||||
* memory-level parallelism (x reads stay L1-resident after warmup). */
|
||||
__global__ static void router_fused_coop_kernel_v2(
|
||||
float *logits,
|
||||
float *partials,
|
||||
int32_t *selected,
|
||||
float *weights,
|
||||
float *probs,
|
||||
const __half *w,
|
||||
const float *x,
|
||||
const float *bias,
|
||||
const int32_t *hash,
|
||||
const int32_t *tokens,
|
||||
int32_t token_scalar,
|
||||
uint32_t hash_rows,
|
||||
int has_bias,
|
||||
int hash_mode,
|
||||
const struct ds4_decode_scalars *scalars) {
|
||||
__shared__ float sbias[256];
|
||||
__shared__ float sprob[256];
|
||||
__shared__ int32_t shash[6];
|
||||
const uint32_t lane = threadIdx.x & 31u;
|
||||
const uint32_t warp = threadIdx.x >> 5u;
|
||||
|
||||
if (blockIdx.x == 0u) {
|
||||
if (has_bias) sbias[threadIdx.x] = bias[threadIdx.x];
|
||||
if (hash_mode && threadIdx.x == 0u) {
|
||||
int32_t tok = tokens ? tokens[0]
|
||||
: (scalars ? (int32_t)scalars->token : token_scalar);
|
||||
if (tok < 0 || (uint32_t)tok >= hash_rows) tok = 0;
|
||||
const int32_t *row = hash + (uint64_t)tok * 6u;
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 6u; j++) shash[j] = row[j];
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t nwarp = gridDim.x * (blockDim.x >> 5u);
|
||||
const uint32_t gwarp = blockIdx.x * (blockDim.x >> 5u) + warp;
|
||||
for (uint32_t t0 = gwarp; t0 < N_EXPERT * KSPLIT; t0 += 2u * nwarp) {
|
||||
const uint32_t t1 = t0 + nwarp;
|
||||
const int have1 = t1 < N_EXPERT * KSPLIT;
|
||||
const uint64_t k0a = (uint64_t)(t0 & 7u) * SEG;
|
||||
const __half *wra = w + (uint64_t)(t0 >> 3u) * N_IN;
|
||||
const uint64_t ia0 = k0a + (uint64_t)lane * 8u;
|
||||
const uint64_t ia1 = k0a + ((uint64_t)lane + 32u) * 8u;
|
||||
const uint64_t k0b = have1 ? (uint64_t)(t1 & 7u) * SEG : k0a;
|
||||
const __half *wrb = have1 ? w + (uint64_t)(t1 >> 3u) * N_IN : wra;
|
||||
const uint64_t ib0 = k0b + (uint64_t)lane * 8u;
|
||||
const uint64_t ib1 = k0b + ((uint64_t)lane + 32u) * 8u;
|
||||
/* all four w vectors in flight before any dependent math */
|
||||
const uint4 wva0 = *reinterpret_cast<const uint4 *>(wra + ia0);
|
||||
const uint4 wva1 = *reinterpret_cast<const uint4 *>(wra + ia1);
|
||||
const uint4 wvb0 = *reinterpret_cast<const uint4 *>(wrb + ib0);
|
||||
const uint4 wvb1 = *reinterpret_cast<const uint4 *>(wrb + ib1);
|
||||
float s0, s1;
|
||||
{
|
||||
float sum0 = 0.0f;
|
||||
float sum1 = 0.0f;
|
||||
{
|
||||
const float4 xa = *reinterpret_cast<const float4 *>(x + ia0);
|
||||
const float4 xb = *reinterpret_cast<const float4 *>(x + ia0 + 4);
|
||||
const __half2 *h = reinterpret_cast<const __half2 *>(&wva0);
|
||||
const float2 w0 = __half22float2(h[0]);
|
||||
const float2 w1 = __half22float2(h[1]);
|
||||
const float2 w2 = __half22float2(h[2]);
|
||||
const float2 w3 = __half22float2(h[3]);
|
||||
sum0 += w0.x * xa.x + w0.y * xa.y + w1.x * xa.z + w1.y * xa.w
|
||||
+ w2.x * xb.x + w2.y * xb.y + w3.x * xb.z + w3.y * xb.w;
|
||||
}
|
||||
{
|
||||
const float4 xa = *reinterpret_cast<const float4 *>(x + ia1);
|
||||
const float4 xb = *reinterpret_cast<const float4 *>(x + ia1 + 4);
|
||||
const __half2 *h = reinterpret_cast<const __half2 *>(&wva1);
|
||||
const float2 w0 = __half22float2(h[0]);
|
||||
const float2 w1 = __half22float2(h[1]);
|
||||
const float2 w2 = __half22float2(h[2]);
|
||||
const float2 w3 = __half22float2(h[3]);
|
||||
sum1 += w0.x * xa.x + w0.y * xa.y + w1.x * xa.z + w1.y * xa.w
|
||||
+ w2.x * xb.x + w2.y * xb.y + w3.x * xb.z + w3.y * xb.w;
|
||||
}
|
||||
s0 = warp_sum_f32(sum0);
|
||||
s1 = warp_sum_f32(sum1);
|
||||
}
|
||||
float s2, s3;
|
||||
if (have1) {
|
||||
float sum0 = 0.0f;
|
||||
float sum1 = 0.0f;
|
||||
{
|
||||
const float4 xa = *reinterpret_cast<const float4 *>(x + ib0);
|
||||
const float4 xb = *reinterpret_cast<const float4 *>(x + ib0 + 4);
|
||||
const __half2 *h = reinterpret_cast<const __half2 *>(&wvb0);
|
||||
const float2 w0 = __half22float2(h[0]);
|
||||
const float2 w1 = __half22float2(h[1]);
|
||||
const float2 w2 = __half22float2(h[2]);
|
||||
const float2 w3 = __half22float2(h[3]);
|
||||
sum0 += w0.x * xa.x + w0.y * xa.y + w1.x * xa.z + w1.y * xa.w
|
||||
+ w2.x * xb.x + w2.y * xb.y + w3.x * xb.z + w3.y * xb.w;
|
||||
}
|
||||
{
|
||||
const float4 xa = *reinterpret_cast<const float4 *>(x + ib1);
|
||||
const float4 xb = *reinterpret_cast<const float4 *>(x + ib1 + 4);
|
||||
const __half2 *h = reinterpret_cast<const __half2 *>(&wvb1);
|
||||
const float2 w0 = __half22float2(h[0]);
|
||||
const float2 w1 = __half22float2(h[1]);
|
||||
const float2 w2 = __half22float2(h[2]);
|
||||
const float2 w3 = __half22float2(h[3]);
|
||||
sum1 += w0.x * xa.x + w0.y * xa.y + w1.x * xa.z + w1.y * xa.w
|
||||
+ w2.x * xb.x + w2.y * xb.y + w3.x * xb.z + w3.y * xb.w;
|
||||
}
|
||||
s2 = warp_sum_f32(sum0);
|
||||
s3 = warp_sum_f32(sum1);
|
||||
}
|
||||
if (lane == 0u) {
|
||||
partials[(uint64_t)(t0 >> 3u) * KSPLIT + (t0 & 7u)] = s0 + s1;
|
||||
if (have1) partials[(uint64_t)(t1 >> 3u) * KSPLIT + (t1 & 7u)] = s2 + s3;
|
||||
}
|
||||
}
|
||||
|
||||
cooperative_groups::this_grid().sync();
|
||||
if (blockIdx.x != 0u) return;
|
||||
|
||||
{
|
||||
const uint64_t row = (uint64_t)threadIdx.x;
|
||||
const float *p = partials + row * KSPLIT;
|
||||
float s = 0.0f;
|
||||
for (uint32_t k = 0u; k < KSPLIT; k++) s += p[k];
|
||||
logits[row] = s;
|
||||
}
|
||||
__syncthreads();
|
||||
|
||||
if (warp != 0u) return;
|
||||
const float *log = logits;
|
||||
float local_prob[8];
|
||||
float local_score[8];
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 8u; j++) {
|
||||
const uint32_t e = lane + j * 32u;
|
||||
const float p = sqrtf(softplus_dev(log[e]));
|
||||
local_prob[j] = p;
|
||||
local_score[j] = p + (has_bias ? sbias[e] : 0.0f);
|
||||
sprob[e] = p;
|
||||
probs[e] = p;
|
||||
}
|
||||
__syncwarp();
|
||||
|
||||
if (hash_mode) {
|
||||
if (lane == 0u) {
|
||||
float sum = 0.0f;
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 6u; j++) {
|
||||
const int32_t e = shash[j];
|
||||
selected[j] = e;
|
||||
const float v = (e >= 0 && e < 256) ? sprob[(uint32_t)e] : 0.0f;
|
||||
weights[j] = v;
|
||||
sum += v;
|
||||
}
|
||||
sum = fmaxf(sum, 6.103515625e-5f);
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 6u; j++) weights[j] = weights[j] / sum * 1.5f;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
float out_prob[6] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||
uint32_t out_idx[6] = {0, 0, 0, 0, 0, 0};
|
||||
#pragma unroll
|
||||
for (uint32_t k = 0; k < 6u; k++) {
|
||||
float best_score = -INFINITY;
|
||||
float best_prob = 0.0f;
|
||||
uint32_t best_idx = UINT32_MAX;
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 8u; j++) {
|
||||
const uint32_t e = lane + j * 32u;
|
||||
const float s = local_score[j];
|
||||
if (router_score_better(s, e, best_score, best_idx)) {
|
||||
best_score = s;
|
||||
best_prob = local_prob[j];
|
||||
best_idx = e;
|
||||
}
|
||||
}
|
||||
#pragma unroll
|
||||
for (uint32_t mask = 16u; mask > 0u; mask >>= 1u) {
|
||||
const float other_score = __shfl_xor_sync(0xffffffffu, best_score, mask);
|
||||
const float other_prob = __shfl_xor_sync(0xffffffffu, best_prob, mask);
|
||||
const uint32_t other_idx = __shfl_xor_sync(0xffffffffu, best_idx, mask);
|
||||
if (router_score_better(other_score, other_idx, best_score, best_idx)) {
|
||||
best_score = other_score;
|
||||
best_prob = other_prob;
|
||||
best_idx = other_idx;
|
||||
}
|
||||
}
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 8u; j++) {
|
||||
const uint32_t e = lane + j * 32u;
|
||||
if (e == best_idx) local_score[j] = -INFINITY;
|
||||
}
|
||||
if (lane == 0) {
|
||||
out_idx[k] = best_idx;
|
||||
out_prob[k] = best_prob;
|
||||
}
|
||||
}
|
||||
|
||||
if (lane == 0) {
|
||||
float sum = 0.0f;
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 6u; j++) {
|
||||
selected[j] = (int32_t)out_idx[j];
|
||||
weights[j] = out_prob[j];
|
||||
sum += out_prob[j];
|
||||
}
|
||||
sum = fmaxf(sum, 6.103515625e-5f);
|
||||
#pragma unroll
|
||||
for (uint32_t j = 0; j < 6u; j++) weights[j] = weights[j] / sum * 1.5f;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Host harness */
|
||||
|
||||
struct outs {
|
||||
float logits[N_EXPERT];
|
||||
float probs[N_EXPERT];
|
||||
int32_t sel[N_TOP];
|
||||
float wgt[N_TOP];
|
||||
};
|
||||
|
||||
static void fill_rand(float *p, size_t n, unsigned seed, float scale) {
|
||||
unsigned s = seed;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
s = s * 1664525u + 1013904223u;
|
||||
p[i] = ((float)(s >> 8) / (float)(1u << 24) - 0.5f) * 2.0f * scale;
|
||||
}
|
||||
}
|
||||
|
||||
struct dev_bufs {
|
||||
__half *w;
|
||||
float *x, *bias, *logits, *probs, *partials_ref, *partials_fu, *wgt;
|
||||
int32_t *hash, *sel;
|
||||
ds4_decode_scalars *scalars;
|
||||
uint32_t hash_rows;
|
||||
};
|
||||
|
||||
static void run_ref(const dev_bufs &b, int has_bias, int hash_mode, int32_t token_via_scalars) {
|
||||
dim3 sgrid(N_EXPERT, KSPLIT, 1);
|
||||
matmul_f16_splitk_kernel<<<sgrid, 256>>>(b.partials_ref, b.w, b.x, N_IN, N_EXPERT, KSPLIT, 1);
|
||||
matmul_f16_splitk_combine_kernel<<<1, 256>>>(b.logits, b.partials_ref, N_EXPERT, KSPLIT);
|
||||
dim3 block(32, 4, 1);
|
||||
router_select_warp_topk_kernel<<<1, block>>>(b.sel, b.wgt, b.probs,
|
||||
has_bias && !hash_mode ? b.bias : NULL,
|
||||
hash_mode ? b.hash : NULL,
|
||||
b.logits, NULL, /*token_scalar=*/-1, b.hash_rows, 1,
|
||||
has_bias && !hash_mode, hash_mode,
|
||||
token_via_scalars ? b.scalars : NULL);
|
||||
CHECK(cudaGetLastError());
|
||||
}
|
||||
|
||||
static int launch_fused_k(void *kernel, const dev_bufs &b, int has_bias, int hash_mode,
|
||||
int32_t token_via_scalars, unsigned grid_blocks, cudaStream_t stream) {
|
||||
float *logits = b.logits, *partials = b.partials_fu, *probs = b.probs, *wgt = b.wgt, *x = b.x;
|
||||
const __half *w = b.w;
|
||||
const float *bias = has_bias && !hash_mode ? b.bias : NULL;
|
||||
const int32_t *hash = hash_mode ? b.hash : NULL;
|
||||
int32_t *sel = b.sel;
|
||||
const int32_t *tokens = NULL;
|
||||
int32_t token_scalar = -1;
|
||||
uint32_t hash_rows = b.hash_rows;
|
||||
int hb = has_bias && !hash_mode;
|
||||
int hm = hash_mode;
|
||||
const ds4_decode_scalars *scalars = token_via_scalars ? b.scalars : NULL;
|
||||
void *args[] = {
|
||||
(void *)&logits, (void *)&partials, (void *)&sel, (void *)&wgt, (void *)&probs,
|
||||
(void *)&w, (void *)&x, (void *)&bias, (void *)&hash, (void *)&tokens,
|
||||
(void *)&token_scalar, (void *)&hash_rows, (void *)&hb, (void *)&hm, (void *)&scalars };
|
||||
cudaError_t err = cudaLaunchCooperativeKernel(
|
||||
kernel,
|
||||
dim3(grid_blocks, 1, 1), dim3(256, 1, 1), args, 0, stream);
|
||||
if (err != cudaSuccess) {
|
||||
fprintf(stderr, "coop launch failed (%u blk): %s\n", grid_blocks, cudaGetErrorString(err));
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int launch_fused(const dev_bufs &b, int has_bias, int hash_mode, int32_t token_via_scalars,
|
||||
unsigned grid_blocks, cudaStream_t stream) {
|
||||
return launch_fused_k((void *)router_fused_coop_kernel, b, has_bias, hash_mode,
|
||||
token_via_scalars, grid_blocks, stream);
|
||||
}
|
||||
|
||||
static void read_outs(const dev_bufs &b, outs *o) {
|
||||
CHECK(cudaMemcpy(o->logits, b.logits, sizeof(o->logits), cudaMemcpyDeviceToHost));
|
||||
CHECK(cudaMemcpy(o->probs, b.probs, sizeof(o->probs), cudaMemcpyDeviceToHost));
|
||||
CHECK(cudaMemcpy(o->sel, b.sel, sizeof(o->sel), cudaMemcpyDeviceToHost));
|
||||
CHECK(cudaMemcpy(o->wgt, b.wgt, sizeof(o->wgt), cudaMemcpyDeviceToHost));
|
||||
}
|
||||
|
||||
static int cmp_outs(const outs &a, const outs &bb, const char *tag) {
|
||||
int bad = 0;
|
||||
if (memcmp(a.logits, bb.logits, sizeof(a.logits))) { printf(" [%s] logits MISMATCH\n", tag); bad = 1; }
|
||||
if (memcmp(a.probs, bb.probs, sizeof(a.probs))) { printf(" [%s] probs MISMATCH\n", tag); bad = 1; }
|
||||
if (memcmp(a.sel, bb.sel, sizeof(a.sel))) { printf(" [%s] selected MISMATCH\n", tag); bad = 1; }
|
||||
if (memcmp(a.wgt, bb.wgt, sizeof(a.wgt))) { printf(" [%s] weights MISMATCH\n", tag); bad = 1; }
|
||||
if (bad) {
|
||||
for (int i = 0; i < 8; i++) printf(" logit[%d] ref=%.9g fused=%.9g\n", i, a.logits[i], bb.logits[i]);
|
||||
for (int i = 0; i < 6; i++) printf(" sel[%d] ref=%d fused=%d w ref=%.9g fused=%.9g\n",
|
||||
i, a.sel[i], bb.sel[i], a.wgt[i], bb.wgt[i]);
|
||||
}
|
||||
return bad;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int dev = 0, coop = 0, n_sm = 0, bps = 0;
|
||||
CHECK(cudaGetDevice(&dev));
|
||||
CHECK(cudaDeviceGetAttribute(&coop, cudaDevAttrCooperativeLaunch, dev));
|
||||
CHECK(cudaDeviceGetAttribute(&n_sm, cudaDevAttrMultiProcessorCount, dev));
|
||||
CHECK(cudaOccupancyMaxActiveBlocksPerMultiprocessor(&bps, router_fused_coop_kernel, 256, 0));
|
||||
const unsigned max_blocks = (unsigned)(bps * n_sm);
|
||||
printf("coop=%d n_sm=%d blocks/sm=%d max co-resident=%u\n", coop, n_sm, bps, max_blocks);
|
||||
if (!coop) { printf("NO cooperative launch support\n"); return 1; }
|
||||
|
||||
dev_bufs b = {};
|
||||
b.hash_rows = 1000;
|
||||
CHECK(cudaMalloc(&b.w, (size_t)N_EXPERT * N_IN * sizeof(__half)));
|
||||
CHECK(cudaMalloc(&b.x, N_IN * sizeof(float)));
|
||||
CHECK(cudaMalloc(&b.bias, N_EXPERT * sizeof(float)));
|
||||
CHECK(cudaMalloc(&b.logits, N_EXPERT * sizeof(float)));
|
||||
CHECK(cudaMalloc(&b.probs, N_EXPERT * sizeof(float)));
|
||||
CHECK(cudaMalloc(&b.partials_ref, N_EXPERT * KSPLIT * sizeof(float)));
|
||||
CHECK(cudaMalloc(&b.partials_fu, N_EXPERT * KSPLIT * sizeof(float)));
|
||||
CHECK(cudaMalloc(&b.sel, N_TOP * sizeof(int32_t)));
|
||||
CHECK(cudaMalloc(&b.wgt, N_TOP * sizeof(float)));
|
||||
CHECK(cudaMalloc(&b.hash, (size_t)b.hash_rows * N_TOP * sizeof(int32_t)));
|
||||
CHECK(cudaMalloc(&b.scalars, sizeof(ds4_decode_scalars)));
|
||||
|
||||
float *hw = (float *)malloc((size_t)N_EXPERT * N_IN * sizeof(float));
|
||||
__half *hwh = (__half *)malloc((size_t)N_EXPERT * N_IN * sizeof(__half));
|
||||
float *hx = (float *)malloc(N_IN * sizeof(float));
|
||||
float *hb = (float *)malloc(N_EXPERT * sizeof(float));
|
||||
int32_t *hh = (int32_t *)malloc((size_t)b.hash_rows * N_TOP * sizeof(int32_t));
|
||||
|
||||
/* -------- parity sweep -------- */
|
||||
int total_bad = 0, n_cases = 0;
|
||||
const unsigned grids[] = { 48u, 96u, 128u, 192u, 256u };
|
||||
for (unsigned seed = 1; seed <= 10; seed++) {
|
||||
fill_rand(hw, (size_t)N_EXPERT * N_IN, seed * 7919u, 0.05f);
|
||||
if (seed == 3) {
|
||||
/* exact-tie case: duplicate rows -> equal logits (and equal bias) ->
|
||||
* tie-break by lower index must survive fusion */
|
||||
for (int r = 0; r < 6; r++)
|
||||
memcpy(hw + (size_t)(100 + r) * N_IN, hw + (size_t)(10 + r) * N_IN, N_IN * sizeof(float));
|
||||
}
|
||||
for (size_t i = 0; i < (size_t)N_EXPERT * N_IN; i++) hwh[i] = __float2half(hw[i]);
|
||||
fill_rand(hx, N_IN, seed * 104729u, 1.0f);
|
||||
if (seed == 5) for (size_t i = 0; i < N_IN; i++) hx[i] = 0.0f; /* all-equal logits */
|
||||
fill_rand(hb, N_EXPERT, seed * 31u, 0.3f);
|
||||
if (seed == 3) for (int r = 0; r < 6; r++) hb[100 + r] = hb[10 + r];
|
||||
unsigned s = seed * 2654435761u;
|
||||
for (size_t i = 0; i < (size_t)b.hash_rows * N_TOP; i++) {
|
||||
s = s * 1664525u + 1013904223u;
|
||||
hh[i] = (int32_t)(s % 300u) - 20; /* includes out-of-range ids */
|
||||
}
|
||||
CHECK(cudaMemcpy(b.w, hwh, (size_t)N_EXPERT * N_IN * sizeof(__half), cudaMemcpyHostToDevice));
|
||||
CHECK(cudaMemcpy(b.x, hx, N_IN * sizeof(float), cudaMemcpyHostToDevice));
|
||||
CHECK(cudaMemcpy(b.bias, hb, N_EXPERT * sizeof(float), cudaMemcpyHostToDevice));
|
||||
CHECK(cudaMemcpy(b.hash, hh, (size_t)b.hash_rows * N_TOP * sizeof(int32_t), cudaMemcpyHostToDevice));
|
||||
ds4_decode_scalars sc = {};
|
||||
sc.token = (seed * 37u) % 1200u; /* sometimes >= hash_rows -> clamp path */
|
||||
CHECK(cudaMemcpy(b.scalars, &sc, sizeof(sc), cudaMemcpyHostToDevice));
|
||||
|
||||
struct { int has_bias, hash_mode, via_scalars; const char *name; } cases[] = {
|
||||
{ 1, 0, 0, "bias" }, { 0, 0, 0, "nobias" }, { 0, 1, 1, "hash" },
|
||||
};
|
||||
for (auto &c : cases) {
|
||||
outs oref, ofu;
|
||||
run_ref(b, c.has_bias, c.hash_mode, c.via_scalars);
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
read_outs(b, &oref);
|
||||
for (unsigned g : grids) {
|
||||
if (g > max_blocks) continue;
|
||||
/* scrub outputs so a silent non-write is caught */
|
||||
CHECK(cudaMemset(b.logits, 0xCB, N_EXPERT * sizeof(float)));
|
||||
CHECK(cudaMemset(b.probs, 0xCB, N_EXPERT * sizeof(float)));
|
||||
CHECK(cudaMemset(b.sel, 0xCB, N_TOP * sizeof(int32_t)));
|
||||
CHECK(cudaMemset(b.wgt, 0xCB, N_TOP * sizeof(float)));
|
||||
if (!launch_fused(b, c.has_bias, c.hash_mode, c.via_scalars, g, 0)) return 1;
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
read_outs(b, &ofu);
|
||||
char tag[64];
|
||||
snprintf(tag, sizeof(tag), "seed%u %s g%u", seed, c.name, g);
|
||||
total_bad += cmp_outs(oref, ofu, tag);
|
||||
n_cases++;
|
||||
CHECK(cudaMemset(b.logits, 0xCB, N_EXPERT * sizeof(float)));
|
||||
CHECK(cudaMemset(b.probs, 0xCB, N_EXPERT * sizeof(float)));
|
||||
CHECK(cudaMemset(b.sel, 0xCB, N_TOP * sizeof(int32_t)));
|
||||
CHECK(cudaMemset(b.wgt, 0xCB, N_TOP * sizeof(float)));
|
||||
if (!launch_fused_k((void *)router_fused_coop_kernel_v2,
|
||||
b, c.has_bias, c.hash_mode, c.via_scalars, g, 0)) return 1;
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
read_outs(b, &ofu);
|
||||
snprintf(tag, sizeof(tag), "V2 seed%u %s g%u", seed, c.name, g);
|
||||
total_bad += cmp_outs(oref, ofu, tag);
|
||||
n_cases++;
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("parity: %d/%d cases bit-identical\n", n_cases - total_bad, n_cases);
|
||||
if (total_bad) return 1;
|
||||
|
||||
/* -------- capture/replay bit-identity -------- */
|
||||
{
|
||||
const unsigned g = max_blocks < 256u ? max_blocks : 256u;
|
||||
outs oeager, oreplay;
|
||||
if (!launch_fused(b, 1, 0, 0, g, 0)) return 1;
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
read_outs(b, &oeager);
|
||||
cudaStream_t cs;
|
||||
CHECK(cudaStreamCreate(&cs));
|
||||
cudaGraph_t graph;
|
||||
cudaGraphExec_t gexec;
|
||||
CHECK(cudaStreamBeginCapture(cs, cudaStreamCaptureModeGlobal));
|
||||
if (!launch_fused(b, 1, 0, 0, g, cs)) return 1;
|
||||
CHECK(cudaStreamEndCapture(cs, &graph));
|
||||
CHECK(cudaGraphInstantiate(&gexec, graph, NULL, NULL, 0));
|
||||
for (int r = 0; r < 3; r++) {
|
||||
CHECK(cudaMemset(b.logits, 0xCB, N_EXPERT * sizeof(float)));
|
||||
CHECK(cudaGraphLaunch(gexec, cs));
|
||||
CHECK(cudaStreamSynchronize(cs));
|
||||
read_outs(b, &oreplay);
|
||||
if (cmp_outs(oeager, oreplay, "capture-replay")) return 1;
|
||||
}
|
||||
printf("capture/replay: bit-identical (3 replays, %u blk)\n", g);
|
||||
CHECK(cudaGraphExecDestroy(gexec));
|
||||
CHECK(cudaGraphDestroy(graph));
|
||||
CHECK(cudaStreamDestroy(cs));
|
||||
}
|
||||
|
||||
/* -------- timing: rotating 68-layer weight set (L2-defeated) -------- */
|
||||
{
|
||||
const int NL = 68;
|
||||
__half *wl[NL];
|
||||
for (int l = 0; l < NL; l++) {
|
||||
CHECK(cudaMalloc(&wl[l], (size_t)N_EXPERT * N_IN * sizeof(__half)));
|
||||
CHECK(cudaMemcpy(wl[l], b.w, (size_t)N_EXPERT * N_IN * sizeof(__half), cudaMemcpyDeviceToDevice));
|
||||
}
|
||||
const int ITERS = 3000, WARM = 300;
|
||||
cudaEvent_t e0, e1;
|
||||
CHECK(cudaEventCreate(&e0));
|
||||
CHECK(cudaEventCreate(&e1));
|
||||
float ms;
|
||||
|
||||
/* ref chain */
|
||||
dev_bufs bl = b;
|
||||
for (int it = 0; it < WARM; it++) { bl.w = wl[it % NL]; run_ref(bl, 1, 0, 0); }
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
CHECK(cudaEventRecord(e0));
|
||||
for (int it = 0; it < ITERS; it++) { bl.w = wl[it % NL]; run_ref(bl, 1, 0, 0); }
|
||||
CHECK(cudaEventRecord(e1));
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
CHECK(cudaEventElapsedTime(&ms, e0, e1));
|
||||
printf("ref chain : %8.3f us/layer\n", ms * 1000.0f / ITERS);
|
||||
|
||||
int bps2 = 0;
|
||||
CHECK(cudaOccupancyMaxActiveBlocksPerMultiprocessor(&bps2, router_fused_coop_kernel_v2, 256, 0));
|
||||
const unsigned max_blocks2 = (unsigned)(bps2 * n_sm);
|
||||
printf("V2 blocks/sm=%d max co-resident=%u\n", bps2, max_blocks2);
|
||||
struct { void *k; const char *name; unsigned cap; } variants[] = {
|
||||
{ (void *)router_fused_coop_kernel, "V1", max_blocks },
|
||||
{ (void *)router_fused_coop_kernel_v2, "V2", max_blocks2 },
|
||||
};
|
||||
for (auto &v : variants) {
|
||||
for (unsigned g : grids) {
|
||||
if (g > v.cap) continue;
|
||||
for (int it = 0; it < WARM; it++) { bl.w = wl[it % NL]; launch_fused_k(v.k, bl, 1, 0, 0, g, 0); }
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
CHECK(cudaEventRecord(e0));
|
||||
for (int it = 0; it < ITERS; it++) { bl.w = wl[it % NL]; launch_fused_k(v.k, bl, 1, 0, 0, g, 0); }
|
||||
CHECK(cudaEventRecord(e1));
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
CHECK(cudaEventElapsedTime(&ms, e0, e1));
|
||||
printf("fused %s g=%3u : %8.3f us/layer\n", v.name, g, ms * 1000.0f / ITERS);
|
||||
}
|
||||
}
|
||||
for (int l = 0; l < NL; l++) CHECK(cudaFree(wl[l]));
|
||||
}
|
||||
|
||||
printf("PROTO_M2_ROUTER OK\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
/* proto_mm_ids.cu — mm_ids_helper parity harness.
|
||||
*
|
||||
* P4 Inc1 (landed 86c5d4d): mm_ids_helper<1> fast path for the routed-MoE
|
||||
* down matmul (ds4_mmq.cu ds4_mmq_moe_impl: n_expert_used=1, si1=1, sis1=1,
|
||||
* nchannels_y=1, n_experts=256). Without a case-1 dispatch the down matmul
|
||||
* fell to the generic <0> template: one warp per expert scanning all
|
||||
* assignment rows with a single active lane — 22.5 ms/launch at W4096.
|
||||
*
|
||||
* P5 (this extension): mm_ids_helper_global — the large-n variant with NO
|
||||
* shared-memory staging. The smem kernel stages the compact (it, iex_used)
|
||||
* list in n_tokens*4 B of dynamic shared memory, capping n_tokens at smpbo/4
|
||||
* (~25k on GB10); the down matmul passes n_tokens = assignment rows = 6x the
|
||||
* forward width, so 8192-row prefill chunks hit 48384 "tokens", the mmq
|
||||
* entries refused, and the WHOLE MoE block fell back to the pre-mmq
|
||||
* expert-tile kernels (the W8192 prefill cliff, trace cmtp5w8.sqlite). The
|
||||
* global variant runs the same per-expert scan twice: pass 0 counts
|
||||
* (nex_prev, bucket size), pass 1 re-scans and writes ids_src1/ids_dst
|
||||
* directly at final offsets.
|
||||
*
|
||||
* Gate: ids_src1 / ids_dst / expert_bounds BIT-IDENTICAL across
|
||||
* (a) smem <0> vs smem <1>/<6> (the landed Inc1 claim),
|
||||
* (b) smem vs global at every sub-cap shape,
|
||||
* (c) global vs a host reference at EVERY shape, including past-cap legs
|
||||
* (48384/49152 assignments = the W8192 down shape; a >cap
|
||||
* n_expert_used=6 leg; the cap boundary +/-1)
|
||||
* with the production pre-zero of both id maps (dropped -1 router ids leave
|
||||
* slots unwritten; expert_bounds[0] counts them, so the zeros sit below
|
||||
* expert 0's bucket and are never consumed).
|
||||
*
|
||||
* Bit-exactness argument for the global variant: identical (it, iex_used)
|
||||
* tuples in identical token order and identical output expressions; the
|
||||
* (lossless) 22/10-bit store round-trip is simply removed. Both variants
|
||||
* are undefined if one token lists the same expert twice (router top-k is
|
||||
* without replacement); the generators here sample distinct experts.
|
||||
*
|
||||
* Build (GB10):
|
||||
* nvcc -O3 --use_fast_math -std=c++17 -arch=sm_121 proto_mm_ids.cu -o proto_mm_ids
|
||||
*/
|
||||
#include <cuda_runtime.h>
|
||||
#include <climits>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
|
||||
#define CHECK(x) do { cudaError_t e_ = (x); if (e_ != cudaSuccess) { \
|
||||
fprintf(stderr, "CUDA error %s at %s:%d\n", cudaGetErrorString(e_), __FILE__, __LINE__); exit(1); } } while (0)
|
||||
|
||||
static constexpr int WARP_SIZE = 32;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Vendored from cuda/mmq/mmid.cu + common.cuh (verbatim semantics) */
|
||||
|
||||
template <int width = WARP_SIZE>
|
||||
static __device__ __forceinline__ int warp_reduce_any(int x) {
|
||||
if (width == WARP_SIZE) {
|
||||
return __any_sync(0xffffffff, x);
|
||||
} else {
|
||||
#pragma unroll
|
||||
for (int offset = width/2; offset > 0; offset >>= 1) {
|
||||
x = __shfl_xor_sync(0xffffffff, x, offset, width) || x;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
template <int width = WARP_SIZE>
|
||||
static __device__ __forceinline__ int warp_reduce_sum(int x) {
|
||||
#pragma unroll
|
||||
for (int offset = width/2; offset > 0; offset >>= 1) {
|
||||
x += __shfl_xor_sync(0xffffffff, x, offset, width);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
struct mm_ids_helper_store {
|
||||
uint32_t data;
|
||||
__device__ mm_ids_helper_store(const uint32_t it, const uint32_t iex_used) {
|
||||
data = (it & 0x003FFFFF) | (iex_used << 22);
|
||||
}
|
||||
__device__ uint32_t it() const { return data & 0x003FFFFF; }
|
||||
__device__ uint32_t iex_used() const { return data >> 22; }
|
||||
};
|
||||
static_assert(sizeof(mm_ids_helper_store) == 4, "unexpected size for mm_ids_helper_store");
|
||||
|
||||
template <int n_expert_used_template>
|
||||
__launch_bounds__(WARP_SIZE, 1)
|
||||
static __global__ void mm_ids_helper(
|
||||
const int32_t * __restrict__ ids, int32_t * __restrict__ ids_src1, int32_t * __restrict__ ids_dst, int32_t * __restrict__ expert_bounds,
|
||||
const int n_tokens, const int n_expert_used_var, const int nchannels_y, const int si1, const int sis1) {
|
||||
constexpr int warp_size = WARP_SIZE;
|
||||
const int n_expert_used = n_expert_used_template == 0 ? n_expert_used_var : n_expert_used_template;
|
||||
const int expert = blockIdx.x;
|
||||
|
||||
extern __shared__ char data_mm_ids_helper[];
|
||||
mm_ids_helper_store * store = (mm_ids_helper_store *) data_mm_ids_helper;
|
||||
|
||||
int nex_prev = 0;
|
||||
int it_compact = 0;
|
||||
|
||||
if constexpr (n_expert_used_template == 0) {
|
||||
for (int it = 0; it < n_tokens; ++it) {
|
||||
int iex_used = -1;
|
||||
for (int iex = threadIdx.x; iex < n_expert_used; iex += warp_size) {
|
||||
const int expert_used = ids[it*si1 + iex];
|
||||
nex_prev += expert_used < expert;
|
||||
if (expert_used == expert) {
|
||||
iex_used = iex;
|
||||
}
|
||||
}
|
||||
if (iex_used != -1) {
|
||||
store[it_compact] = mm_ids_helper_store(it, iex_used);
|
||||
}
|
||||
if (warp_reduce_any<warp_size>(iex_used != -1)) {
|
||||
it_compact++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
static_assert(n_expert_used_template == 6 || warp_size % n_expert_used_template == 0, "bad n_expert_used");
|
||||
constexpr int neu_padded = n_expert_used_template == 6 ? 8 : n_expert_used_template;
|
||||
for (int it0 = 0; it0 < n_tokens; it0 += warp_size/neu_padded) {
|
||||
const int it = it0 + threadIdx.x / neu_padded;
|
||||
|
||||
const int iex = threadIdx.x % neu_padded;
|
||||
const int expert_used = (neu_padded == n_expert_used_template || iex < n_expert_used_template) && it < n_tokens ?
|
||||
ids[it*si1 + iex] : INT_MAX;
|
||||
const int iex_used = expert_used == expert ? iex : -1;
|
||||
nex_prev += expert_used < expert;
|
||||
|
||||
const int it_compact_add_self = warp_reduce_any<neu_padded>(iex_used != -1);
|
||||
|
||||
int it_compact_add_lower = 0;
|
||||
#pragma unroll
|
||||
for (int offset = neu_padded; offset < warp_size; offset += neu_padded) {
|
||||
const int tmp = __shfl_up_sync(0xFFFFFFFF, it_compact_add_self, offset, warp_size);
|
||||
if (threadIdx.x >= static_cast<unsigned int>(offset)) {
|
||||
it_compact_add_lower += tmp;
|
||||
}
|
||||
}
|
||||
|
||||
if (iex_used != -1) {
|
||||
store[it_compact + it_compact_add_lower] = mm_ids_helper_store(it, iex_used);
|
||||
}
|
||||
|
||||
it_compact += __shfl_sync(0xFFFFFFFF, it_compact_add_lower + it_compact_add_self, warp_size - 1, warp_size);
|
||||
}
|
||||
}
|
||||
nex_prev = warp_reduce_sum<warp_size>(nex_prev);
|
||||
|
||||
__syncwarp();
|
||||
|
||||
for (int itc = threadIdx.x; itc < it_compact; itc += warp_size) {
|
||||
const mm_ids_helper_store store_it = store[itc];
|
||||
const int it = store_it.it();
|
||||
const int iex_used = store_it.iex_used();
|
||||
ids_src1[nex_prev + itc] = it*sis1 + iex_used % nchannels_y;
|
||||
ids_dst [nex_prev + itc] = it*n_expert_used + iex_used;
|
||||
}
|
||||
|
||||
if (threadIdx.x != 0) {
|
||||
return;
|
||||
}
|
||||
expert_bounds[expert] = nex_prev;
|
||||
if (expert < static_cast<int>(gridDim.x) - 1) {
|
||||
return;
|
||||
}
|
||||
expert_bounds[gridDim.x] = nex_prev + it_compact;
|
||||
}
|
||||
|
||||
/* Vendored mm_ids_helper_global (P5 large-n variant, mmid.cu) */
|
||||
template <int n_expert_used_template>
|
||||
__launch_bounds__(WARP_SIZE, 1)
|
||||
static __global__ void mm_ids_helper_global(
|
||||
const int32_t * __restrict__ ids, int32_t * __restrict__ ids_src1, int32_t * __restrict__ ids_dst, int32_t * __restrict__ expert_bounds,
|
||||
const int n_tokens, const int n_expert_used_var, const int nchannels_y, const int si1, const int sis1) {
|
||||
constexpr int warp_size = WARP_SIZE;
|
||||
const int n_expert_used = n_expert_used_template == 0 ? n_expert_used_var : n_expert_used_template;
|
||||
const int expert = blockIdx.x;
|
||||
|
||||
int nex_prev = 0;
|
||||
int it_compact_count = 0;
|
||||
|
||||
#pragma unroll 1
|
||||
for (int pass = 0; pass < 2; ++pass) {
|
||||
int it_compact = 0;
|
||||
|
||||
if constexpr (n_expert_used_template == 0) {
|
||||
for (int it = 0; it < n_tokens; ++it) {
|
||||
int iex_used = -1;
|
||||
for (int iex = threadIdx.x; iex < n_expert_used; iex += warp_size) {
|
||||
const int expert_used = ids[it*si1 + iex];
|
||||
if (pass == 0) {
|
||||
nex_prev += expert_used < expert;
|
||||
}
|
||||
if (expert_used == expert) {
|
||||
iex_used = iex;
|
||||
}
|
||||
}
|
||||
|
||||
if (pass == 1 && iex_used != -1) {
|
||||
ids_src1[nex_prev + it_compact] = it*sis1 + iex_used % nchannels_y;
|
||||
ids_dst [nex_prev + it_compact] = it*n_expert_used + iex_used;
|
||||
}
|
||||
|
||||
if (warp_reduce_any<warp_size>(iex_used != -1)) {
|
||||
it_compact++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
static_assert(n_expert_used_template == 6 || warp_size % n_expert_used_template == 0, "bad n_expert_used");
|
||||
constexpr int neu_padded = n_expert_used_template == 6 ? 8 : n_expert_used_template;
|
||||
for (int it0 = 0; it0 < n_tokens; it0 += warp_size/neu_padded) {
|
||||
const int it = it0 + threadIdx.x / neu_padded;
|
||||
|
||||
const int iex = threadIdx.x % neu_padded;
|
||||
const int expert_used = (neu_padded == n_expert_used_template || iex < n_expert_used_template) && it < n_tokens ?
|
||||
ids[it*si1 + iex] : INT_MAX;
|
||||
const int iex_used = expert_used == expert ? iex : -1;
|
||||
if (pass == 0) {
|
||||
nex_prev += expert_used < expert;
|
||||
}
|
||||
|
||||
const int it_compact_add_self = warp_reduce_any<neu_padded>(iex_used != -1);
|
||||
|
||||
int it_compact_add_lower = 0;
|
||||
#pragma unroll
|
||||
for (int offset = neu_padded; offset < warp_size; offset += neu_padded) {
|
||||
const int tmp = __shfl_up_sync(0xFFFFFFFF, it_compact_add_self, offset, warp_size);
|
||||
if (threadIdx.x >= static_cast<unsigned int>(offset)) {
|
||||
it_compact_add_lower += tmp;
|
||||
}
|
||||
}
|
||||
|
||||
if (pass == 1 && iex_used != -1) {
|
||||
const int itc = it_compact + it_compact_add_lower;
|
||||
ids_src1[nex_prev + itc] = it*sis1 + iex_used % nchannels_y;
|
||||
ids_dst [nex_prev + itc] = it*n_expert_used + iex_used;
|
||||
}
|
||||
|
||||
it_compact += __shfl_sync(0xFFFFFFFF, it_compact_add_lower + it_compact_add_self, warp_size - 1, warp_size);
|
||||
}
|
||||
}
|
||||
|
||||
if (pass == 0) {
|
||||
it_compact_count = it_compact;
|
||||
nex_prev = warp_reduce_sum<warp_size>(nex_prev);
|
||||
}
|
||||
}
|
||||
|
||||
if (threadIdx.x != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
expert_bounds[expert] = nex_prev;
|
||||
|
||||
if (expert < static_cast<int>(gridDim.x) - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
expert_bounds[gridDim.x] = nex_prev + it_compact_count;
|
||||
}
|
||||
|
||||
/* NOTE: the proto instantiations replace the vendored kernels' runtime-const
|
||||
* neu_padded with a constexpr derived from the template arg (identical value
|
||||
* per instantiation; the vendored code relies on the same constant folding). */
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static constexpr int N_EXPERTS = 256;
|
||||
|
||||
/* Host reference implementing the mm_ids_helper output contract:
|
||||
* expert_bounds[e] = #{assignment values < e} (signed: -1 counts for all
|
||||
* experts, so invalid rows occupy never-consumed slots below expert 0);
|
||||
* bucket e = token-ascending (it, iex) pairs with ids[it*si1+iex] == e;
|
||||
* ids_src1[pos] = it*sis1 + iex % nchannels_y; ids_dst[pos] = it*neu + iex.
|
||||
* src1/dst must be pre-zeroed by the caller (production memset semantics). */
|
||||
static void cpu_ref(const int32_t *ids, int nt, int neu, int nchannels_y, int si1, int sis1,
|
||||
int32_t *src1, int32_t *dst, int32_t *eb) {
|
||||
std::vector<int32_t> h(N_EXPERTS + 2, 0); // h[b+1] counts value b, b in [-1, N_EXPERTS]
|
||||
for (int it = 0; it < nt; ++it) {
|
||||
for (int iex = 0; iex < neu; ++iex) {
|
||||
int v = ids[it*si1 + iex];
|
||||
if (v < -1) v = -1;
|
||||
if (v > N_EXPERTS) v = N_EXPERTS;
|
||||
h[v + 1]++;
|
||||
}
|
||||
}
|
||||
std::vector<int32_t> below(N_EXPERTS + 1, 0); // below[e] = #{v < e}
|
||||
int32_t acc = h[0];
|
||||
for (int e = 0; e <= N_EXPERTS; ++e) {
|
||||
below[e] = acc;
|
||||
if (e < N_EXPERTS) acc += h[e + 1];
|
||||
}
|
||||
for (int e = 0; e <= N_EXPERTS; ++e) {
|
||||
eb[e] = below[e];
|
||||
}
|
||||
std::vector<int32_t> fill(N_EXPERTS, 0);
|
||||
for (int it = 0; it < nt; ++it) {
|
||||
for (int iex = 0; iex < neu; ++iex) {
|
||||
const int e = ids[it*si1 + iex];
|
||||
if (e < 0 || e >= N_EXPERTS) continue;
|
||||
const int64_t pos = (int64_t)below[e] + fill[e]++;
|
||||
src1[pos] = it*sis1 + iex % nchannels_y;
|
||||
dst [pos] = it*neu + iex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct leg {
|
||||
const char *name;
|
||||
int n_tokens; // assignment rows (neu=1) or tokens (neu=6)
|
||||
int neu; // n_expert_used: 1 = down shape, 6 = gate/up shape
|
||||
int invalid_pct; // % of slots with -1 (router NaN path)
|
||||
bool skew; // hot-expert distribution
|
||||
};
|
||||
|
||||
int main() {
|
||||
int smpbo = 0;
|
||||
CHECK(cudaDeviceGetAttribute(&smpbo, cudaDevAttrMaxSharedMemoryPerBlockOptin, 0));
|
||||
CHECK(cudaFuncSetAttribute((const void*)mm_ids_helper<0>, cudaFuncAttributeMaxDynamicSharedMemorySize, smpbo));
|
||||
CHECK(cudaFuncSetAttribute((const void*)mm_ids_helper<1>, cudaFuncAttributeMaxDynamicSharedMemorySize, smpbo));
|
||||
CHECK(cudaFuncSetAttribute((const void*)mm_ids_helper<6>, cudaFuncAttributeMaxDynamicSharedMemorySize, smpbo));
|
||||
const int cap_tokens = smpbo / 4;
|
||||
printf("smpbo=%d (cap %d tokens)\n", smpbo, cap_tokens);
|
||||
|
||||
std::vector<leg> legs = {
|
||||
// down shape (neu=1): n_tokens = rows*6 assignments
|
||||
{"W4096x6dn uniform", 4096*6, 1, 0, false},
|
||||
{"W4096x6dn 2%invalid", 4096*6, 1, 2, false},
|
||||
{"W4096x6dn skewed", 4096*6, 1, 1, true},
|
||||
{"W2048x6dn uniform", 2048*6, 1, 2, false},
|
||||
{"decode w1x6", 6, 1, 0, false},
|
||||
{"decode w8x6", 48, 1, 0, false},
|
||||
// past-cap down shapes (the P5 cliff: W8192 -> 8064*6 = 48384 traced, 8192*6 = 49152 max)
|
||||
{"W8192x6dn uniform", 48384, 1, 0, false},
|
||||
{"W8192x6dn 2%invalid", 48384, 1, 2, false},
|
||||
{"W8192x6dn skewed", 48384, 1, 1, true},
|
||||
{"W8192x6dn max", 49152, 1, 2, false},
|
||||
// gate/up shape (neu=6): n_tokens = rows, si1 = 6, distinct experts/token
|
||||
{"W8192gu uniform", 8064, 6, 0, false},
|
||||
{"W8192gu 2%invalid", 8064, 6, 2, false},
|
||||
{"gu past-cap", 30000, 6, 2, false},
|
||||
};
|
||||
// cap-boundary seam legs (runtime-sized)
|
||||
leg seam_at = {"cap-boundary", cap_tokens, 1, 2, false};
|
||||
leg seam_over = {"cap-boundary+1", cap_tokens + 1, 1, 2, false};
|
||||
legs.push_back(seam_at);
|
||||
legs.push_back(seam_over);
|
||||
|
||||
int failures = 0;
|
||||
|
||||
for (const leg &L : legs) {
|
||||
const int nt = L.n_tokens;
|
||||
const int neu = L.neu;
|
||||
const int si1 = neu, sis1 = 1, nchannels_y = 1;
|
||||
const int64_t n_ids = (int64_t)nt * neu;
|
||||
const size_t smem = (size_t)nt * 4;
|
||||
const bool smem_fits = smem <= (size_t)smpbo;
|
||||
|
||||
int32_t *h_ids = (int32_t*)malloc(n_ids * sizeof(int32_t));
|
||||
srand(20260708);
|
||||
if (neu == 1) {
|
||||
for (int i = 0; i < nt; i++) {
|
||||
if (L.invalid_pct && rand() % 100 < L.invalid_pct) { h_ids[i] = -1; continue; }
|
||||
h_ids[i] = L.skew ? (rand() % 100 < 60 ? rand() % 8 : rand() % N_EXPERTS)
|
||||
: rand() % N_EXPERTS;
|
||||
}
|
||||
} else {
|
||||
// router top-k semantics: distinct experts per token
|
||||
for (int it = 0; it < nt; it++) {
|
||||
int chosen[8]; int c = 0;
|
||||
while (c < neu) {
|
||||
const int e = L.skew ? (rand() % 100 < 60 ? rand() % 8 : rand() % N_EXPERTS)
|
||||
: rand() % N_EXPERTS;
|
||||
bool dup = false;
|
||||
for (int j = 0; j < c; j++) dup = dup || chosen[j] == e;
|
||||
if (!dup) chosen[c++] = e;
|
||||
}
|
||||
for (int iex = 0; iex < neu; iex++) {
|
||||
h_ids[(int64_t)it*neu + iex] =
|
||||
(L.invalid_pct && rand() % 100 < L.invalid_pct) ? -1 : chosen[iex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// host reference
|
||||
int32_t *r_src1 = (int32_t*)calloc(n_ids, sizeof(int32_t));
|
||||
int32_t *r_dst = (int32_t*)calloc(n_ids, sizeof(int32_t));
|
||||
int32_t r_eb[N_EXPERTS+1];
|
||||
cpu_ref(h_ids, nt, neu, nchannels_y, si1, sis1, r_src1, r_dst, r_eb);
|
||||
|
||||
int32_t *d_ids, *d_src1, *d_dst, *d_eb;
|
||||
CHECK(cudaMalloc(&d_ids, n_ids * sizeof(int32_t)));
|
||||
CHECK(cudaMalloc(&d_src1, n_ids * sizeof(int32_t)));
|
||||
CHECK(cudaMalloc(&d_dst, n_ids * sizeof(int32_t)));
|
||||
CHECK(cudaMalloc(&d_eb, (N_EXPERTS+1) * sizeof(int32_t)));
|
||||
CHECK(cudaMemcpy(d_ids, h_ids, n_ids * sizeof(int32_t), cudaMemcpyHostToDevice));
|
||||
|
||||
const dim3 grid(N_EXPERTS, 1, 1), block(WARP_SIZE, 1, 1);
|
||||
int32_t *h_src1 = (int32_t*)malloc(n_ids * sizeof(int32_t));
|
||||
int32_t *h_dst = (int32_t*)malloc(n_ids * sizeof(int32_t));
|
||||
int32_t h_eb[N_EXPERTS+1];
|
||||
|
||||
// candidate kernels for this leg: smem generic, smem fast, global fast
|
||||
struct cand { const char *name; int variant; }; // 0=<0> smem, 1=<neu> smem, 2=<neu> global
|
||||
std::vector<cand> cands;
|
||||
if (smem_fits) { cands.push_back({"smem<0>", 0}); cands.push_back({"smem<t>", 1}); }
|
||||
cands.push_back({"global<t>", 2});
|
||||
|
||||
bool ok = true;
|
||||
float ms[3] = {0, 0, 0};
|
||||
for (const cand &C : cands) {
|
||||
// production pre-zero of both id maps
|
||||
CHECK(cudaMemset(d_src1, 0, n_ids * sizeof(int32_t)));
|
||||
CHECK(cudaMemset(d_dst, 0, n_ids * sizeof(int32_t)));
|
||||
CHECK(cudaMemset(d_eb, 0xFF, (N_EXPERTS+1) * sizeof(int32_t)));
|
||||
|
||||
auto launch = [&](void) {
|
||||
switch (C.variant) {
|
||||
case 0: mm_ids_helper<0><<<grid, block, smem>>>(d_ids, d_src1, d_dst, d_eb, nt, neu, nchannels_y, si1, sis1); break;
|
||||
case 1:
|
||||
if (neu == 1) mm_ids_helper<1><<<grid, block, smem>>>(d_ids, d_src1, d_dst, d_eb, nt, neu, nchannels_y, si1, sis1);
|
||||
else mm_ids_helper<6><<<grid, block, smem>>>(d_ids, d_src1, d_dst, d_eb, nt, neu, nchannels_y, si1, sis1);
|
||||
break;
|
||||
case 2:
|
||||
if (neu == 1) mm_ids_helper_global<1><<<grid, block, 0>>>(d_ids, d_src1, d_dst, d_eb, nt, neu, nchannels_y, si1, sis1);
|
||||
else mm_ids_helper_global<6><<<grid, block, 0>>>(d_ids, d_src1, d_dst, d_eb, nt, neu, nchannels_y, si1, sis1);
|
||||
break;
|
||||
}
|
||||
};
|
||||
launch();
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
|
||||
CHECK(cudaMemcpy(h_src1, d_src1, n_ids*4, cudaMemcpyDeviceToHost));
|
||||
CHECK(cudaMemcpy(h_dst, d_dst, n_ids*4, cudaMemcpyDeviceToHost));
|
||||
CHECK(cudaMemcpy(h_eb, d_eb, (N_EXPERTS+1)*4, cudaMemcpyDeviceToHost));
|
||||
const bool m1 = memcmp(h_src1, r_src1, n_ids*4) == 0;
|
||||
const bool m2 = memcmp(h_dst, r_dst, n_ids*4) == 0;
|
||||
const bool m3 = memcmp(h_eb, r_eb, (N_EXPERTS+1)*4) == 0;
|
||||
if (!(m1 && m2 && m3)) {
|
||||
ok = false;
|
||||
printf(" %-18s %-9s MISMATCH src1=%d dst=%d eb=%d\n", L.name, C.name, m1, m2, m3);
|
||||
}
|
||||
|
||||
// timing (50 reps after 5 warmups)
|
||||
cudaEvent_t t0, t1;
|
||||
CHECK(cudaEventCreate(&t0)); CHECK(cudaEventCreate(&t1));
|
||||
for (int w = 0; w < 5; w++) launch();
|
||||
CHECK(cudaEventRecord(t0));
|
||||
for (int r = 0; r < 50; r++) launch();
|
||||
CHECK(cudaEventRecord(t1)); CHECK(cudaEventSynchronize(t1));
|
||||
float m = 0; CHECK(cudaEventElapsedTime(&m, t0, t1));
|
||||
ms[C.variant] = m / 50;
|
||||
CHECK(cudaEventDestroy(t0)); CHECK(cudaEventDestroy(t1));
|
||||
}
|
||||
|
||||
if (smem_fits) {
|
||||
printf("%-20s nt=%-6d neu=%d %s smem<0> %8.3f ms smem<t> %8.3f ms global<t> %8.3f ms\n",
|
||||
L.name, nt, neu, ok ? "PARITY-OK " : "MISMATCH!!", ms[0], ms[1], ms[2]);
|
||||
} else {
|
||||
printf("%-20s nt=%-6d neu=%d %s (past cap) global<t> %8.3f ms\n",
|
||||
L.name, nt, neu, ok ? "PARITY-OK " : "MISMATCH!!", ms[2]);
|
||||
}
|
||||
if (!ok) failures++;
|
||||
|
||||
free(h_ids); free(h_src1); free(h_dst); free(r_src1); free(r_dst);
|
||||
cudaFree(d_ids); cudaFree(d_src1); cudaFree(d_dst); cudaFree(d_eb);
|
||||
}
|
||||
|
||||
printf(failures ? "PROTO FAIL (%d legs)\n" : "PROTO PASS\n", failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
// proto_q8_aligned.cu — M1-Inc3 prototype (megakernel program).
|
||||
//
|
||||
// HYPOTHESIS: the dense Q8_0 decode GEMVs run below the ~200-202 GB/s kernel
|
||||
// ceiling (attrib15b: attn_q_b 1024x32768 @164, 2048x4096 @157, head
|
||||
// 4096x129280 @146) because block_q8_0 is 34 bytes, so the int8 code stream
|
||||
// is only 2-byte aligned (same misalignment class as IQ2_XXS, proven +12%
|
||||
// by proto_iq2_aligned). Repacking into an aligned SoA layout (d[] halves
|
||||
// separate, qs[] 64B-aligned 32B per block) allows int4 (16B) aligned loads
|
||||
// at identical byte count.
|
||||
//
|
||||
// A/B at the three production decode shapes (K = in_dim, M = out rows):
|
||||
// attn_q_b : K=1024, M=32768 (34.0 MiB/call, 164 GB/s baseline)
|
||||
// mid : K=2048, M=4096 ( 8.5 MiB/call, 157 GB/s baseline)
|
||||
// head : K=4096, M=129280 (536.6 MiB/call, 146 GB/s baseline)
|
||||
// baseline = ds4_mmq_q8_0_dense_vec (production decode entry, quantizes X
|
||||
// internally; bare-kernel aligned numbers exclude that ~small
|
||||
// cost — Inc1 lesson: judge the KERNEL here, entry design later)
|
||||
//
|
||||
// Build (box):
|
||||
// nvcc -O3 --use_fast_math -std=c++17 -arch=sm_121 -I.. \
|
||||
// proto_q8_aligned.cu ../*.o -lcudart -lcuda -o proto_q8_aligned
|
||||
|
||||
#include "ds4_mmq.h"
|
||||
|
||||
#include <cuda_runtime.h>
|
||||
#include <cuda_fp16.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#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)
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int QK = 32; // values per Q8_0 block
|
||||
constexpr int BLOCK_BYTES = 34; // sizeof(block_q8_0) = 2 + 32
|
||||
|
||||
// ---------------------------------------------------------------- host q8_1
|
||||
struct q8_1_block { __half2 ds; int8_t qs[32]; }; // mirrors block_q8_1
|
||||
|
||||
static void quantize_q8_1_host(const float *x, q8_1_block *out, int k) {
|
||||
for (int b = 0; b < k / 32; b++) {
|
||||
float amax = 0.0f;
|
||||
for (int i = 0; i < 32; i++) amax = fmaxf(amax, fabsf(x[b * 32 + i]));
|
||||
const float d = amax / 127.0f;
|
||||
const float id = d != 0.0f ? 1.0f / d : 0.0f;
|
||||
int sum = 0;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
int q = (int)roundf(x[b * 32 + i] * id);
|
||||
if (q > 127) q = 127;
|
||||
if (q < -127) q = -127;
|
||||
out[b].qs[i] = (int8_t)q;
|
||||
sum += q;
|
||||
}
|
||||
out[b].ds = __floats2half2_rn(d, d * (float)sum);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ device kernel
|
||||
// Aligned SoA weights: dq[blk] __half scale; qs stream 64B-aligned, 32B per
|
||||
// block (int4 x2). Warp-per-row GEMV: lane covers one block per pass.
|
||||
__global__ void q8_aligned_dense_vec_kernel(
|
||||
float *out, // [M]
|
||||
const int4 *qs, // aligned codes, 2 int4 per block
|
||||
const __half *dq, // block scales
|
||||
const q8_1_block *x8, // [K/32] quantized activation
|
||||
int M,
|
||||
int nb) // blocks per row = K/32
|
||||
{
|
||||
const int row = blockIdx.x;
|
||||
const int lane = threadIdx.x;
|
||||
const long long rbase = (long long)row * nb;
|
||||
|
||||
float acc = 0.0f;
|
||||
for (int b0 = 0; b0 < nb; b0 += 32) {
|
||||
const int b = b0 + lane;
|
||||
const int4 w0 = qs[(rbase + b) * 2 + 0]; // aligned 16B
|
||||
const int4 w1 = qs[(rbase + b) * 2 + 1];
|
||||
const int *u = (const int *)x8[b].qs;
|
||||
int sumi = 0;
|
||||
sumi = __dp4a(w0.x, u[0], sumi);
|
||||
sumi = __dp4a(w0.y, u[1], sumi);
|
||||
sumi = __dp4a(w0.z, u[2], sumi);
|
||||
sumi = __dp4a(w0.w, u[3], sumi);
|
||||
sumi = __dp4a(w1.x, u[4], sumi);
|
||||
sumi = __dp4a(w1.y, u[5], sumi);
|
||||
sumi = __dp4a(w1.z, u[6], sumi);
|
||||
sumi = __dp4a(w1.w, u[7], sumi);
|
||||
acc += __half2float(dq[rbase + b]) * __low2float(x8[b].ds) * (float)sumi;
|
||||
}
|
||||
#pragma unroll
|
||||
for (int off = 16; off > 0; off >>= 1)
|
||||
acc += __shfl_down_sync(0xffffffffu, acc, off);
|
||||
if (lane == 0) out[row] = acc;
|
||||
}
|
||||
|
||||
// Variant R2: two rows per 64-thread block (one warp each) — probes whether
|
||||
// higher occupancy per SM changes the DRAM rate at skinny K.
|
||||
__global__ void q8_aligned_dense_vec_r2_kernel(
|
||||
float *out,
|
||||
const int4 *qs,
|
||||
const __half *dq,
|
||||
const q8_1_block *x8,
|
||||
int M,
|
||||
int nb)
|
||||
{
|
||||
const int row = blockIdx.x * 2 + (threadIdx.x >> 5);
|
||||
const int lane = threadIdx.x & 31;
|
||||
if (row >= M) return;
|
||||
const long long rbase = (long long)row * nb;
|
||||
float acc = 0.0f;
|
||||
for (int b0 = 0; b0 < nb; b0 += 32) {
|
||||
const int b = b0 + lane;
|
||||
const int4 w0 = qs[(rbase + b) * 2 + 0];
|
||||
const int4 w1 = qs[(rbase + b) * 2 + 1];
|
||||
const int *u = (const int *)x8[b].qs;
|
||||
int sumi = 0;
|
||||
sumi = __dp4a(w0.x, u[0], sumi);
|
||||
sumi = __dp4a(w0.y, u[1], sumi);
|
||||
sumi = __dp4a(w0.z, u[2], sumi);
|
||||
sumi = __dp4a(w0.w, u[3], sumi);
|
||||
sumi = __dp4a(w1.x, u[4], sumi);
|
||||
sumi = __dp4a(w1.y, u[5], sumi);
|
||||
sumi = __dp4a(w1.z, u[6], sumi);
|
||||
sumi = __dp4a(w1.w, u[7], sumi);
|
||||
acc += __half2float(dq[rbase + b]) * __low2float(x8[b].ds) * (float)sumi;
|
||||
}
|
||||
#pragma unroll
|
||||
for (int off = 16; off > 0; off >>= 1)
|
||||
acc += __shfl_down_sync(0xffffffffu, acc, off);
|
||||
if (lane == 0) out[row] = acc;
|
||||
}
|
||||
|
||||
static void run_shape(const char *label, int M, int K, int iters, uint32_t seed) {
|
||||
const int nb = K / QK;
|
||||
const long long row_bytes = (long long)nb * BLOCK_BYTES;
|
||||
const double call_gb = (double)M * row_bytes / 1e9;
|
||||
|
||||
std::mt19937 rng(seed);
|
||||
|
||||
// Random bytes are valid Q8_0 content; keep block scales sane.
|
||||
std::vector<uint8_t> W((size_t)M * row_bytes);
|
||||
for (auto &b : W) b = (uint8_t)(rng() & 0xff);
|
||||
const long long nblk = (long long)M * nb;
|
||||
for (long long blk = 0; blk < nblk; blk++) {
|
||||
uint16_t h = (uint16_t)(0x2c00 | (rng() & 0xff));
|
||||
memcpy(&W[blk * BLOCK_BYTES], &h, 2);
|
||||
}
|
||||
|
||||
std::vector<float> X(K);
|
||||
for (auto &v : X) v = ((float)(rng() % 2000) - 1000.0f) / 500.0f;
|
||||
std::vector<q8_1_block> X8(K / 32);
|
||||
quantize_q8_1_host(X.data(), X8.data(), K);
|
||||
|
||||
// Aligned SoA repack: [half dq[nblk]][pad to 64B][int8 qs[nblk*32]]
|
||||
const uint64_t dq_bytes = ((uint64_t)nblk * 2u + 63u) & ~63ull;
|
||||
std::vector<uint8_t> ART(dq_bytes + (uint64_t)nblk * 32u);
|
||||
for (long long blk = 0; blk < nblk; blk++) {
|
||||
memcpy(&ART[blk * 2], &W[blk * BLOCK_BYTES], 2);
|
||||
memcpy(&ART[dq_bytes + (uint64_t)blk * 32u], &W[blk * BLOCK_BYTES + 2], 32);
|
||||
}
|
||||
|
||||
// L2 trap (proven on the iq2 pair test AND the first run of this proto:
|
||||
// "mid" printed 931 GB/s, 4x the DRAM ceiling): a weight buffer smaller
|
||||
// than ~L2 stays resident across timing iters. Rotate through enough
|
||||
// copies that consecutive iters never re-hit cache (~256 MB footprint).
|
||||
const int n_copies = (int)((256ull * 1024 * 1024) / (W.size() + 1)) + 1;
|
||||
uint8_t *dW, *dArt; float *dX, *dOutBase, *dOutAl; q8_1_block *dX8;
|
||||
CK(cudaMalloc(&dW, W.size() * n_copies));
|
||||
CK(cudaMalloc(&dArt, ART.size() * n_copies));
|
||||
CK(cudaMalloc(&dX, sizeof(float) * K));
|
||||
CK(cudaMalloc(&dOutBase, sizeof(float) * M));
|
||||
CK(cudaMalloc(&dOutAl, sizeof(float) * M));
|
||||
CK(cudaMalloc(&dX8, X8.size() * sizeof(q8_1_block)));
|
||||
for (int c = 0; c < n_copies; c++) {
|
||||
CK(cudaMemcpy(dW + (size_t)c * W.size(), W.data(), W.size(), cudaMemcpyHostToDevice));
|
||||
CK(cudaMemcpy(dArt + (size_t)c * ART.size(), ART.data(), ART.size(), cudaMemcpyHostToDevice));
|
||||
}
|
||||
CK(cudaMemcpy(dX, X.data(), sizeof(float) * K, cudaMemcpyHostToDevice));
|
||||
CK(cudaMemcpy(dX8, X8.data(), X8.size() * sizeof(q8_1_block), cudaMemcpyHostToDevice));
|
||||
|
||||
cudaStream_t stream; CK(cudaStreamCreate(&stream));
|
||||
cudaEvent_t e0, e1; CK(cudaEventCreate(&e0)); CK(cudaEventCreate(&e1));
|
||||
|
||||
// ---- baseline: production dense vec entry ------------------------------
|
||||
int rc = ds4_mmq_q8_0_dense_vec(dW, dX, dOutBase, M, 1, K, stream);
|
||||
if (rc != 0) { printf("%s: baseline dense_vec rc=%d\n", label, rc); exit(1); }
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
CK(cudaEventRecord(e0, stream));
|
||||
for (int i = 0; i < iters; i++)
|
||||
(void)ds4_mmq_q8_0_dense_vec(dW + (size_t)(i % n_copies) * W.size(),
|
||||
dX, dOutBase, M, 1, K, stream);
|
||||
CK(cudaEventRecord(e1, stream));
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
float ms_base = 0.0f; CK(cudaEventElapsedTime(&ms_base, e0, e1));
|
||||
ms_base /= iters;
|
||||
|
||||
// ---- aligned prototype (warp per row) ----------------------------------
|
||||
const int4 *qs = (const int4 *)(dArt + dq_bytes);
|
||||
const __half *dq = (const __half *)dArt;
|
||||
q8_aligned_dense_vec_kernel<<<M, 32, 0, stream>>>(dOutAl, qs, dq, dX8, M, nb);
|
||||
CK(cudaGetLastError());
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
CK(cudaEventRecord(e0, stream));
|
||||
for (int i = 0; i < iters; i++) {
|
||||
const uint8_t *art_i = dArt + (size_t)(i % n_copies) * ART.size();
|
||||
q8_aligned_dense_vec_kernel<<<M, 32, 0, stream>>>(
|
||||
dOutAl, (const int4 *)(art_i + dq_bytes), (const __half *)art_i, dX8, M, nb);
|
||||
}
|
||||
CK(cudaEventRecord(e1, stream));
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
float ms_al = 0.0f; CK(cudaEventElapsedTime(&ms_al, e0, e1));
|
||||
ms_al /= iters;
|
||||
|
||||
// ---- variant R2 ---------------------------------------------------------
|
||||
q8_aligned_dense_vec_r2_kernel<<<(M + 1) / 2, 64, 0, stream>>>(dOutAl, qs, dq, dX8, M, nb);
|
||||
CK(cudaGetLastError());
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
CK(cudaEventRecord(e0, stream));
|
||||
for (int i = 0; i < iters; i++) {
|
||||
const uint8_t *art_i = dArt + (size_t)(i % n_copies) * ART.size();
|
||||
q8_aligned_dense_vec_r2_kernel<<<(M + 1) / 2, 64, 0, stream>>>(
|
||||
dOutAl, (const int4 *)(art_i + dq_bytes), (const __half *)art_i, dX8, M, nb);
|
||||
}
|
||||
CK(cudaEventRecord(e1, stream));
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
float ms_r2 = 0.0f; CK(cudaEventElapsedTime(&ms_r2, e0, e1));
|
||||
ms_r2 /= iters;
|
||||
|
||||
// ---- correctness: both kernels vs a DOUBLE host reference --------------
|
||||
// The per-block integer dot is exact on both paths; only the float
|
||||
// accumulation order differs (256 block terms at K=8192), so
|
||||
// kernel-vs-kernel tolerance false-fails on cancellation-heavy rows.
|
||||
// Judge each kernel by its distance from the double reference instead.
|
||||
(void)ds4_mmq_q8_0_dense_vec(dW, dX, dOutBase, M, 1, K, stream);
|
||||
q8_aligned_dense_vec_kernel<<<M, 32, 0, stream>>>(dOutAl, qs, dq, dX8, M, nb);
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
std::vector<float> ob(M), oa(M);
|
||||
CK(cudaMemcpy(ob.data(), dOutBase, sizeof(float) * M, cudaMemcpyDeviceToHost));
|
||||
CK(cudaMemcpy(oa.data(), dOutAl, sizeof(float) * M, cudaMemcpyDeviceToHost));
|
||||
double err_b = 0.0, err_a = 0.0; int bad = 0;
|
||||
for (int i = 0; i < M; i++) {
|
||||
double ref = 0.0;
|
||||
const uint8_t *rw = &W[(size_t)i * row_bytes];
|
||||
for (int b = 0; b < nb; b++) {
|
||||
const uint8_t *blk = rw + (size_t)b * BLOCK_BYTES;
|
||||
uint16_t h; memcpy(&h, blk, 2);
|
||||
const float dw = __half2float(__ushort_as_half(h));
|
||||
const int8_t *q = (const int8_t *)(blk + 2);
|
||||
const int8_t *u = X8[b].qs;
|
||||
int s = 0;
|
||||
for (int j = 0; j < 32; j++) s += (int)q[j] * (int)u[j];
|
||||
ref += (double)dw * (double)__half2float(__low2half(X8[b].ds)) * (double)s;
|
||||
}
|
||||
const double eb = fabs((double)ob[i] - ref);
|
||||
const double ea = fabs((double)oa[i] - ref);
|
||||
if (eb > err_b) err_b = eb;
|
||||
if (ea > err_a) err_a = ea;
|
||||
// aligned must not be meaningfully further from ref than baseline
|
||||
if (ea > eb * 4.0 + 1e-2) bad++;
|
||||
}
|
||||
|
||||
printf("%-9s K=%-5d M=%-7d (%.1f MB/call, iters=%d)\n", label, K, M, call_gb * 1000.0, iters);
|
||||
printf(" baseline dense_vec : %.4f ms -> %6.1f GB/s\n", ms_base, call_gb / (ms_base / 1e3));
|
||||
printf(" aligned warp/row : %.4f ms -> %6.1f GB/s (%+.1f%%)\n",
|
||||
ms_al, call_gb / (ms_al / 1e3), 100.0 * (ms_base / ms_al - 1.0));
|
||||
printf(" aligned 2row/blk : %.4f ms -> %6.1f GB/s (%+.1f%%)\n",
|
||||
ms_r2, call_gb / (ms_r2 / 1e3), 100.0 * (ms_base / ms_r2 - 1.0));
|
||||
printf(" parity vs double ref: max_abs_err base=%.3e aligned=%.3e bad=%d -> %s\n",
|
||||
err_b, err_a, bad, bad == 0 ? "PASS" : "FAIL");
|
||||
if (bad != 0) exit(2);
|
||||
|
||||
CK(cudaFree(dW)); CK(cudaFree(dArt)); CK(cudaFree(dX));
|
||||
CK(cudaFree(dOutBase)); CK(cudaFree(dOutAl)); CK(cudaFree(dX8));
|
||||
CK(cudaEventDestroy(e0)); CK(cudaEventDestroy(e1));
|
||||
CK(cudaStreamDestroy(stream));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const uint32_t seed = argc > 1 ? (uint32_t)atoi(argv[1]) : 1234u;
|
||||
if (ds4_mmq_init(0) != 0) { printf("ds4_mmq_init failed\n"); return 1; }
|
||||
printf("PROTO_Q8_ALIGNED (block_q8_0 34B -> aligned SoA [dq][pad64][qs 32B])\n");
|
||||
run_shape("attn_q_b", 32768, 1024, 2000, seed);
|
||||
run_shape("mid", 4096, 2048, 2000, seed + 1);
|
||||
run_shape("out_a", 4096, 8192, 2000, seed + 3);
|
||||
run_shape("head", 129280, 4096, 200, seed + 2);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,665 @@
|
||||
// proto_q8_warp8.cu — Inc4 prototype (megakernel program).
|
||||
//
|
||||
// The nsys truth table (nsys33) put the three CUSTOM warp8 Q8_0 decode
|
||||
// kernels at 21.2 ms/step combined (hc_expand 9.09 / grouped_a 6.98 /
|
||||
// pair 5.13) running at 150-195 GB/s on the raw 34-byte block layout.
|
||||
// proto_q8_aligned proved the aligned SoA layout ([half dq[nblk]][pad64]
|
||||
// [int8 qs[nblk*32]], derived kind 5) reaches 235-245 GB/s at these K.
|
||||
//
|
||||
// This proto A/Bs each warp8 kernel VERBATIM against an aligned variant
|
||||
// with the SAME warp8 structure, prequant inputs (xq int8 + xscale float,
|
||||
// quantize_q8_0_f32 semantics), accumulation order, and epilogue — only
|
||||
// the weight load path changes (unaligned 34B-stride assembles -> two
|
||||
// aligned int4 per block). Production Flash shapes:
|
||||
// hc_expand : out=4096 in=8192 (attn_output_b) + out=4096 in=2048 (shared_down)
|
||||
// pair : in=4096 out0=1024 out1=512 (q_a+kv) + out0=out1=2048 (shared gate+up)
|
||||
// grouped : group_dim=4096 rank=1024 n_groups=8 (attn_output_a)
|
||||
//
|
||||
// L2-defeating rotation (>=256 MB of weight copies) + double host reference
|
||||
// parity, per the proto_q8_aligned method rules.
|
||||
//
|
||||
// Build (box):
|
||||
// nvcc -O3 --use_fast_math -std=c++17 -arch=sm_121 -I.. \
|
||||
// proto_q8_warp8.cu -lcudart -lcuda -o proto_q8_warp8
|
||||
|
||||
#include <cuda_runtime.h>
|
||||
#include <cuda_fp16.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#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)
|
||||
|
||||
namespace {
|
||||
|
||||
// ---- device helpers copied verbatim from ds4_cuda.cu ----------------------
|
||||
__device__ __forceinline__ int32_t load_i8x4_i32_unaligned(const int8_t *p) {
|
||||
const uint8_t *u = (const uint8_t *)p;
|
||||
return (int32_t)((uint32_t)u[0] |
|
||||
((uint32_t)u[1] << 8) |
|
||||
((uint32_t)u[2] << 16) |
|
||||
((uint32_t)u[3] << 24));
|
||||
}
|
||||
__device__ __forceinline__ int32_t load_i8x4_i32_aligned(const int8_t *p) {
|
||||
return *(const int32_t *)p;
|
||||
}
|
||||
__device__ __forceinline__ int32_t dot_i8x32_dp4a(const int8_t *a, const int8_t *b) {
|
||||
int32_t dot = 0;
|
||||
#pragma unroll
|
||||
for (uint32_t i = 0; i < 32u; i += 4u) {
|
||||
dot = __dp4a(load_i8x4_i32_unaligned(a + i), load_i8x4_i32_aligned(b + i), dot);
|
||||
}
|
||||
return dot;
|
||||
}
|
||||
__device__ __forceinline__ int32_t dot_i8_block(const int8_t *a, const int8_t *b, uint64_t n, int use_dp4a) {
|
||||
if (use_dp4a && n == 32u) return dot_i8x32_dp4a(a, b);
|
||||
int32_t dot = 0;
|
||||
for (uint64_t i = 0; i < n; i++) dot += (int32_t)a[i] * (int32_t)b[i];
|
||||
return dot;
|
||||
}
|
||||
__device__ float warp_sum_f32(float v) {
|
||||
for (int offset = 16; offset > 0; offset >>= 1) {
|
||||
v += __shfl_down_sync(0xffffffffu, v, offset);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
// Aligned per-block dot: two 16B loads + 8 dp4a, same integer result.
|
||||
__device__ __forceinline__ int32_t dot_i8x32_aligned(const int8_t *qs32, const int8_t *xqb) {
|
||||
const int4 w0 = *(const int4 *)qs32;
|
||||
const int4 w1 = *(const int4 *)(qs32 + 16);
|
||||
const int *u = (const int *)xqb;
|
||||
int sumi = 0;
|
||||
sumi = __dp4a(w0.x, u[0], sumi);
|
||||
sumi = __dp4a(w0.y, u[1], sumi);
|
||||
sumi = __dp4a(w0.z, u[2], sumi);
|
||||
sumi = __dp4a(w0.w, u[3], sumi);
|
||||
sumi = __dp4a(w1.x, u[4], sumi);
|
||||
sumi = __dp4a(w1.y, u[5], sumi);
|
||||
sumi = __dp4a(w1.z, u[6], sumi);
|
||||
sumi = __dp4a(w1.w, u[7], sumi);
|
||||
return sumi;
|
||||
}
|
||||
|
||||
// ==== 1. hc_expand ==========================================================
|
||||
// Baseline: verbatim matmul_q8_0_hc_expand_preq_warp8_kernel (ds4_cuda.cu).
|
||||
__global__ void hc_expand_base_kernel(
|
||||
float *out_hc, float *block_out, const float *block_add,
|
||||
const float *residual_hc, const float *split, const unsigned char *w,
|
||||
const int8_t *xq, const float *xscale,
|
||||
uint64_t in_dim, uint64_t out_dim, uint32_t n_embd, uint32_t n_hc,
|
||||
uint64_t blocks, int has_add, int use_dp4a) {
|
||||
const uint64_t row = (uint64_t)blockIdx.x * 8u + (threadIdx.x >> 5u);
|
||||
const uint32_t lane = threadIdx.x & 31u;
|
||||
if (row >= out_dim) return;
|
||||
const unsigned char *wr = w + row * blocks * 34;
|
||||
float acc = 0.0f;
|
||||
for (uint64_t b = lane; b < blocks; b += 32u) {
|
||||
const uint64_t i0 = b * 32;
|
||||
const uint64_t bn = in_dim - i0 < 32 ? in_dim - i0 : 32;
|
||||
const __half *scale_h = (const __half *)(wr + b * 34);
|
||||
const int8_t *qs = (const int8_t *)(wr + b * 34 + 2);
|
||||
const int8_t *xqb = xq + b * 32;
|
||||
int dot = dot_i8_block(qs, xqb, bn, use_dp4a);
|
||||
acc += __half2float(*scale_h) * xscale[b] * (float)dot;
|
||||
}
|
||||
acc = warp_sum_f32(acc);
|
||||
if (lane == 0) {
|
||||
const uint32_t d = (uint32_t)row;
|
||||
block_out[d] = acc;
|
||||
float block_v = acc;
|
||||
if (has_add) block_v += block_add[d];
|
||||
const float *post = split + n_hc;
|
||||
const float *comb = split + 2u * n_hc;
|
||||
for (uint32_t dst_hc = 0; dst_hc < n_hc; dst_hc++) {
|
||||
float hc_acc = block_v * post[dst_hc];
|
||||
for (uint32_t src_hc = 0; src_hc < n_hc; src_hc++) {
|
||||
const float comb_v = comb[dst_hc + (uint64_t)src_hc * n_hc];
|
||||
const float res_v = residual_hc[(uint64_t)src_hc * n_embd + d];
|
||||
hc_acc += comb_v * res_v;
|
||||
}
|
||||
out_hc[(uint64_t)dst_hc * n_embd + d] = hc_acc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Aligned variant: identical loop/epilogue; weight loads via [dq|qs] artifact.
|
||||
__global__ void hc_expand_aligned_kernel(
|
||||
float *out_hc, float *block_out, const float *block_add,
|
||||
const float *residual_hc, const float *split,
|
||||
const int8_t *qs_art, const __half *dq_art,
|
||||
const int8_t *xq, const float *xscale,
|
||||
uint64_t in_dim, uint64_t out_dim, uint32_t n_embd, uint32_t n_hc,
|
||||
uint64_t blocks, int has_add) {
|
||||
const uint64_t row = (uint64_t)blockIdx.x * 8u + (threadIdx.x >> 5u);
|
||||
const uint32_t lane = threadIdx.x & 31u;
|
||||
if (row >= out_dim) return;
|
||||
const uint64_t rbase = row * blocks;
|
||||
float acc = 0.0f;
|
||||
for (uint64_t b = lane; b < blocks; b += 32u) {
|
||||
const int sumi = dot_i8x32_aligned(qs_art + (rbase + b) * 32u, xq + b * 32u);
|
||||
acc += __half2float(dq_art[rbase + b]) * xscale[b] * (float)sumi;
|
||||
}
|
||||
acc = warp_sum_f32(acc);
|
||||
if (lane == 0) {
|
||||
const uint32_t d = (uint32_t)row;
|
||||
block_out[d] = acc;
|
||||
float block_v = acc;
|
||||
if (has_add) block_v += block_add[d];
|
||||
const float *post = split + n_hc;
|
||||
const float *comb = split + 2u * n_hc;
|
||||
for (uint32_t dst_hc = 0; dst_hc < n_hc; dst_hc++) {
|
||||
float hc_acc = block_v * post[dst_hc];
|
||||
for (uint32_t src_hc = 0; src_hc < n_hc; src_hc++) {
|
||||
const float comb_v = comb[dst_hc + (uint64_t)src_hc * n_hc];
|
||||
const float res_v = residual_hc[(uint64_t)src_hc * n_embd + d];
|
||||
hc_acc += comb_v * res_v;
|
||||
}
|
||||
out_hc[(uint64_t)dst_hc * n_embd + d] = hc_acc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==== 2. pair ===============================================================
|
||||
__global__ void pair_base_kernel(
|
||||
float *out0, float *out1, const unsigned char *w0, const unsigned char *w1,
|
||||
const int8_t *xq, const float *xscale,
|
||||
uint64_t in_dim, uint64_t out0_dim, uint64_t out1_dim, uint64_t blocks,
|
||||
int use_dp4a) {
|
||||
uint64_t row = (uint64_t)blockIdx.x * 8u + (threadIdx.x >> 5u);
|
||||
uint32_t lane = threadIdx.x & 31u;
|
||||
if (row >= out0_dim && row >= out1_dim) return;
|
||||
float acc0 = 0.0f;
|
||||
float acc1 = 0.0f;
|
||||
const unsigned char *wr0 = row < out0_dim ? w0 + row * blocks * 34 : NULL;
|
||||
const unsigned char *wr1 = row < out1_dim ? w1 + row * blocks * 34 : NULL;
|
||||
for (uint64_t b = lane; b < blocks; b += 32u) {
|
||||
uint64_t i0 = b * 32;
|
||||
uint64_t bn = in_dim - i0 < 32 ? in_dim - i0 : 32;
|
||||
const int8_t *xqb = xq + b * 32;
|
||||
const float xs = xscale[b];
|
||||
if (wr0) {
|
||||
const __half *scale_h = (const __half *)(wr0 + b * 34);
|
||||
const int8_t *qs = (const int8_t *)(wr0 + b * 34 + 2);
|
||||
int dot = dot_i8_block(qs, xqb, bn, use_dp4a);
|
||||
acc0 += __half2float(*scale_h) * xs * (float)dot;
|
||||
}
|
||||
if (wr1) {
|
||||
const __half *scale_h = (const __half *)(wr1 + b * 34);
|
||||
const int8_t *qs = (const int8_t *)(wr1 + b * 34 + 2);
|
||||
int dot = dot_i8_block(qs, xqb, bn, use_dp4a);
|
||||
acc1 += __half2float(*scale_h) * xs * (float)dot;
|
||||
}
|
||||
}
|
||||
acc0 = warp_sum_f32(acc0);
|
||||
acc1 = warp_sum_f32(acc1);
|
||||
if (lane == 0) {
|
||||
if (row < out0_dim) out0[row] = acc0;
|
||||
if (row < out1_dim) out1[row] = acc1;
|
||||
}
|
||||
}
|
||||
|
||||
__global__ void pair_aligned_kernel(
|
||||
float *out0, float *out1,
|
||||
const int8_t *qs0, const __half *dq0,
|
||||
const int8_t *qs1, const __half *dq1,
|
||||
const int8_t *xq, const float *xscale,
|
||||
uint64_t in_dim, uint64_t out0_dim, uint64_t out1_dim, uint64_t blocks) {
|
||||
uint64_t row = (uint64_t)blockIdx.x * 8u + (threadIdx.x >> 5u);
|
||||
uint32_t lane = threadIdx.x & 31u;
|
||||
if (row >= out0_dim && row >= out1_dim) return;
|
||||
const uint64_t rbase = row * blocks;
|
||||
const int in0 = row < out0_dim;
|
||||
const int in1 = row < out1_dim;
|
||||
float acc0 = 0.0f;
|
||||
float acc1 = 0.0f;
|
||||
for (uint64_t b = lane; b < blocks; b += 32u) {
|
||||
const int8_t *xqb = xq + b * 32;
|
||||
const float xs = xscale[b];
|
||||
if (in0) {
|
||||
const int sumi = dot_i8x32_aligned(qs0 + (rbase + b) * 32u, xqb);
|
||||
acc0 += __half2float(dq0[rbase + b]) * xs * (float)sumi;
|
||||
}
|
||||
if (in1) {
|
||||
const int sumi = dot_i8x32_aligned(qs1 + (rbase + b) * 32u, xqb);
|
||||
acc1 += __half2float(dq1[rbase + b]) * xs * (float)sumi;
|
||||
}
|
||||
}
|
||||
acc0 = warp_sum_f32(acc0);
|
||||
acc1 = warp_sum_f32(acc1);
|
||||
if (lane == 0) {
|
||||
if (row < out0_dim) out0[row] = acc0;
|
||||
if (row < out1_dim) out1[row] = acc1;
|
||||
}
|
||||
}
|
||||
|
||||
// ==== 3. grouped ============================================================
|
||||
__global__ void grouped_base_kernel(
|
||||
float *low, const unsigned char *w, const int8_t *xq, const float *xscale,
|
||||
uint64_t group_dim, uint64_t rank, uint32_t n_groups, uint32_t n_tokens,
|
||||
uint64_t blocks, int use_dp4a) {
|
||||
const uint64_t row = (uint64_t)blockIdx.x * 8u + (threadIdx.x >> 5u);
|
||||
const uint64_t tok = (uint64_t)blockIdx.y;
|
||||
const uint32_t lane = threadIdx.x & 31u;
|
||||
const uint64_t low_dim = (uint64_t)n_groups * rank;
|
||||
if (row >= low_dim || tok >= n_tokens) return;
|
||||
|
||||
const uint64_t group = row / rank;
|
||||
const uint64_t row_in_group = row - group * rank;
|
||||
const unsigned char *wr = w + (group * rank + row_in_group) * blocks * 34;
|
||||
const uint64_t xrow = tok * (uint64_t)n_groups + group;
|
||||
const int8_t *xqr = xq + xrow * blocks * 32;
|
||||
const float *xsr = xscale + xrow * blocks;
|
||||
float acc = 0.0f;
|
||||
|
||||
for (uint64_t b = lane; b < blocks; b += 32u) {
|
||||
const uint64_t i0 = b * 32;
|
||||
const uint64_t bn = group_dim - i0 < 32 ? group_dim - i0 : 32;
|
||||
const __half *scale_h = (const __half *)(wr + b * 34);
|
||||
const int8_t *qs = (const int8_t *)(wr + b * 34 + 2);
|
||||
const int8_t *xqb = xqr + b * 32;
|
||||
int dot = dot_i8_block(qs, xqb, bn, use_dp4a);
|
||||
acc += __half2float(*scale_h) * xsr[b] * (float)dot;
|
||||
}
|
||||
acc = warp_sum_f32(acc);
|
||||
if (lane == 0) low[tok * low_dim + row] = acc;
|
||||
}
|
||||
|
||||
__global__ void grouped_aligned_kernel(
|
||||
float *low, const int8_t *qs_art, const __half *dq_art,
|
||||
const int8_t *xq, const float *xscale,
|
||||
uint64_t group_dim, uint64_t rank, uint32_t n_groups, uint32_t n_tokens,
|
||||
uint64_t blocks) {
|
||||
const uint64_t row = (uint64_t)blockIdx.x * 8u + (threadIdx.x >> 5u);
|
||||
const uint64_t tok = (uint64_t)blockIdx.y;
|
||||
const uint32_t lane = threadIdx.x & 31u;
|
||||
const uint64_t low_dim = (uint64_t)n_groups * rank;
|
||||
if (row >= low_dim || tok >= n_tokens) return;
|
||||
|
||||
const uint64_t group = row / rank;
|
||||
const uint64_t rbase = row * blocks;
|
||||
const uint64_t xrow = tok * (uint64_t)n_groups + group;
|
||||
const int8_t *xqr = xq + xrow * blocks * 32;
|
||||
const float *xsr = xscale + xrow * blocks;
|
||||
float acc = 0.0f;
|
||||
|
||||
for (uint64_t b = lane; b < blocks; b += 32u) {
|
||||
const int sumi = dot_i8x32_aligned(qs_art + (rbase + b) * 32u, xqr + b * 32u);
|
||||
acc += __half2float(dq_art[rbase + b]) * xsr[b] * (float)sumi;
|
||||
}
|
||||
acc = warp_sum_f32(acc);
|
||||
if (lane == 0) low[tok * low_dim + row] = acc;
|
||||
}
|
||||
|
||||
// ==== host helpers ==========================================================
|
||||
// Mirror quantize_q8_0_f32_kernel semantics (lrintf, clamp 127/-128).
|
||||
static void quantize_q8_0_host(const float *x, int8_t *xq, float *xscale, int K) {
|
||||
for (int b = 0; b < K / 32; b++) {
|
||||
float amax = 0.0f;
|
||||
for (int i = 0; i < 32; i++) amax = fmaxf(amax, fabsf(x[b * 32 + i]));
|
||||
const float d = amax / 127.0f;
|
||||
const float id = d != 0.0f ? 1.0f / d : 0.0f;
|
||||
xscale[b] = d;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
int v = (int)lrintf(x[b * 32 + i] * id);
|
||||
v = v > 127 ? 127 : (v < -128 ? -128 : v);
|
||||
xq[b * 32 + i] = (int8_t)v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RawArt {
|
||||
std::vector<uint8_t> W; // raw 34B-block rows
|
||||
std::vector<uint8_t> ART; // [dq halves][pad64][qs 32B]
|
||||
uint64_t dq_bytes;
|
||||
};
|
||||
|
||||
static RawArt make_weights(std::mt19937 &rng, long long rows, int blocks) {
|
||||
RawArt r;
|
||||
const long long nblk = rows * blocks;
|
||||
r.W.resize((size_t)nblk * 34);
|
||||
for (auto &b : r.W) b = (uint8_t)(rng() & 0xff);
|
||||
for (long long blk = 0; blk < nblk; blk++) {
|
||||
uint16_t h = (uint16_t)(0x2c00 | (rng() & 0xff));
|
||||
memcpy(&r.W[blk * 34], &h, 2);
|
||||
}
|
||||
r.dq_bytes = ((uint64_t)nblk * 2u + 63u) & ~63ull;
|
||||
r.ART.resize(r.dq_bytes + (uint64_t)nblk * 32u);
|
||||
for (long long blk = 0; blk < nblk; blk++) {
|
||||
memcpy(&r.ART[blk * 2], &r.W[blk * 34], 2);
|
||||
memcpy(&r.ART[r.dq_bytes + (uint64_t)blk * 32u], &r.W[blk * 34 + 2], 32);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// Double reference GEMV row: sum_b dq[b] * xscale[b] * (int dot)
|
||||
static double ref_row(const uint8_t *wrow, const int8_t *xq, const float *xs, int blocks) {
|
||||
double ref = 0.0;
|
||||
for (int b = 0; b < blocks; b++) {
|
||||
const uint8_t *blk = wrow + (size_t)b * 34;
|
||||
uint16_t h; memcpy(&h, blk, 2);
|
||||
const float dw = __half2float(__ushort_as_half(h));
|
||||
const int8_t *q = (const int8_t *)(blk + 2);
|
||||
const int8_t *u = xq + (size_t)b * 32;
|
||||
int s = 0;
|
||||
for (int j = 0; j < 32; j++) s += (int)q[j] * (int)u[j];
|
||||
ref += (double)dw * (double)xs[b] * (double)s;
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
struct DevCopies {
|
||||
uint8_t *dW, *dArt;
|
||||
int n_copies;
|
||||
size_t w_sz, art_sz;
|
||||
const uint8_t *w(int i) const { return dW + (size_t)(i % n_copies) * w_sz; }
|
||||
const uint8_t *art(int i) const { return dArt + (size_t)(i % n_copies) * art_sz; }
|
||||
const int8_t *art_qs(int i, uint64_t dqb) const { return (const int8_t *)(art(i) + dqb); }
|
||||
const __half *art_dq(int i) const { return (const __half *)art(i); }
|
||||
};
|
||||
|
||||
static DevCopies upload_rotating(const RawArt &r, size_t min_footprint = 256ull << 20) {
|
||||
DevCopies d;
|
||||
d.w_sz = r.W.size(); d.art_sz = r.ART.size();
|
||||
d.n_copies = (int)(min_footprint / (d.w_sz + 1)) + 1;
|
||||
CK(cudaMalloc(&d.dW, d.w_sz * d.n_copies));
|
||||
CK(cudaMalloc(&d.dArt, d.art_sz * d.n_copies));
|
||||
for (int c = 0; c < d.n_copies; c++) {
|
||||
CK(cudaMemcpy(d.dW + (size_t)c * d.w_sz, r.W.data(), d.w_sz, cudaMemcpyHostToDevice));
|
||||
CK(cudaMemcpy(d.dArt + (size_t)c * d.art_sz, r.ART.data(), d.art_sz, cudaMemcpyHostToDevice));
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
static float time_loop(cudaStream_t stream, cudaEvent_t e0, cudaEvent_t e1, int iters,
|
||||
void (*launch)(int, void *), void *ud) {
|
||||
launch(0, ud);
|
||||
CK(cudaGetLastError());
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
CK(cudaEventRecord(e0, stream));
|
||||
for (int i = 0; i < iters; i++) launch(i, ud);
|
||||
CK(cudaEventRecord(e1, stream));
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
float ms = 0.0f; CK(cudaEventElapsedTime(&ms, e0, e1));
|
||||
return ms / iters;
|
||||
}
|
||||
|
||||
static int check_pair_tol(const std::vector<float> &base, const std::vector<float> &al,
|
||||
const std::vector<double> &ref, const char *tag) {
|
||||
double eb_max = 0.0, ea_max = 0.0; int bad = 0;
|
||||
for (size_t i = 0; i < ref.size(); i++) {
|
||||
const double eb = fabs((double)base[i] - ref[i]);
|
||||
const double ea = fabs((double)al[i] - ref[i]);
|
||||
if (eb > eb_max) eb_max = eb;
|
||||
if (ea > ea_max) ea_max = ea;
|
||||
if (ea > eb * 4.0 + 1e-2) bad++;
|
||||
}
|
||||
printf(" parity(%s): max_abs_err base=%.3e aligned=%.3e bad=%d -> %s\n",
|
||||
tag, eb_max, ea_max, bad, bad == 0 ? "PASS" : "FAIL");
|
||||
return bad;
|
||||
}
|
||||
|
||||
// ==== section runners =======================================================
|
||||
struct HcCtx {
|
||||
cudaStream_t stream; DevCopies dev; uint64_t dq_bytes;
|
||||
float *out_hc, *block_out; const float *block_add, *residual, *split;
|
||||
const int8_t *xq; const float *xs;
|
||||
uint64_t in_dim, out_dim, blocks; uint32_t n_embd, n_hc; int base;
|
||||
};
|
||||
static void hc_launch(int i, void *p) {
|
||||
HcCtx *c = (HcCtx *)p;
|
||||
const unsigned grid = ((unsigned)c->out_dim + 7u) / 8u;
|
||||
if (c->base) {
|
||||
hc_expand_base_kernel<<<grid, 256, 0, c->stream>>>(
|
||||
c->out_hc, c->block_out, c->block_add, c->residual, c->split,
|
||||
c->dev.w(i), c->xq, c->xs, c->in_dim, c->out_dim, c->n_embd, c->n_hc,
|
||||
c->blocks, 1, 1);
|
||||
} else {
|
||||
hc_expand_aligned_kernel<<<grid, 256, 0, c->stream>>>(
|
||||
c->out_hc, c->block_out, c->block_add, c->residual, c->split,
|
||||
c->dev.art_qs(i, c->dq_bytes), c->dev.art_dq(i), c->xq, c->xs,
|
||||
c->in_dim, c->out_dim, c->n_embd, c->n_hc, c->blocks, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static int run_hc_expand(const char *label, int out_dim, int in_dim, int iters, uint32_t seed) {
|
||||
const int blocks = in_dim / 32, n_hc = 4;
|
||||
std::mt19937 rng(seed);
|
||||
RawArt r = make_weights(rng, out_dim, blocks);
|
||||
std::vector<float> X(in_dim);
|
||||
for (auto &v : X) v = ((float)(rng() % 2000) - 1000.0f) / 500.0f;
|
||||
std::vector<int8_t> XQ(in_dim); std::vector<float> XS(blocks);
|
||||
quantize_q8_0_host(X.data(), XQ.data(), XS.data(), in_dim);
|
||||
std::vector<float> ADD(out_dim), RES((size_t)n_hc * out_dim), SPL(2 * n_hc + n_hc * n_hc);
|
||||
for (auto &v : ADD) v = ((float)(rng() % 2000) - 1000.0f) / 1000.0f;
|
||||
for (auto &v : RES) v = ((float)(rng() % 2000) - 1000.0f) / 1000.0f;
|
||||
for (auto &v : SPL) v = ((float)(rng() % 2000) - 1000.0f) / 2000.0f;
|
||||
|
||||
DevCopies dev = upload_rotating(r);
|
||||
float *dOutHc, *dBlk, *dAdd, *dRes, *dSpl, *dXs; int8_t *dXq;
|
||||
CK(cudaMalloc(&dOutHc, sizeof(float) * n_hc * out_dim));
|
||||
CK(cudaMalloc(&dBlk, sizeof(float) * out_dim));
|
||||
CK(cudaMalloc(&dAdd, sizeof(float) * out_dim));
|
||||
CK(cudaMalloc(&dRes, sizeof(float) * RES.size()));
|
||||
CK(cudaMalloc(&dSpl, sizeof(float) * SPL.size()));
|
||||
CK(cudaMalloc(&dXq, XQ.size()));
|
||||
CK(cudaMalloc(&dXs, sizeof(float) * XS.size()));
|
||||
CK(cudaMemcpy(dAdd, ADD.data(), sizeof(float) * ADD.size(), cudaMemcpyHostToDevice));
|
||||
CK(cudaMemcpy(dRes, RES.data(), sizeof(float) * RES.size(), cudaMemcpyHostToDevice));
|
||||
CK(cudaMemcpy(dSpl, SPL.data(), sizeof(float) * SPL.size(), cudaMemcpyHostToDevice));
|
||||
CK(cudaMemcpy(dXq, XQ.data(), XQ.size(), cudaMemcpyHostToDevice));
|
||||
CK(cudaMemcpy(dXs, XS.data(), sizeof(float) * XS.size(), cudaMemcpyHostToDevice));
|
||||
|
||||
cudaStream_t stream; CK(cudaStreamCreate(&stream));
|
||||
cudaEvent_t e0, e1; CK(cudaEventCreate(&e0)); CK(cudaEventCreate(&e1));
|
||||
|
||||
HcCtx c = {stream, dev, r.dq_bytes, dOutHc, dBlk, dAdd, dRes, dSpl,
|
||||
dXq, dXs, (uint64_t)in_dim, (uint64_t)out_dim, (uint64_t)blocks,
|
||||
(uint32_t)out_dim, (uint32_t)n_hc, 1};
|
||||
const float ms_base = time_loop(stream, e0, e1, iters, hc_launch, &c);
|
||||
std::vector<float> bBlk(out_dim), bHc((size_t)n_hc * out_dim);
|
||||
CK(cudaMemcpy(bBlk.data(), dBlk, sizeof(float) * out_dim, cudaMemcpyDeviceToHost));
|
||||
CK(cudaMemcpy(bHc.data(), dOutHc, sizeof(float) * bHc.size(), cudaMemcpyDeviceToHost));
|
||||
c.base = 0;
|
||||
const float ms_al = time_loop(stream, e0, e1, iters, hc_launch, &c);
|
||||
std::vector<float> aBlk(out_dim), aHc((size_t)n_hc * out_dim);
|
||||
CK(cudaMemcpy(aBlk.data(), dBlk, sizeof(float) * out_dim, cudaMemcpyDeviceToHost));
|
||||
CK(cudaMemcpy(aHc.data(), dOutHc, sizeof(float) * aHc.size(), cudaMemcpyDeviceToHost));
|
||||
|
||||
// double refs
|
||||
std::vector<double> rBlk(out_dim), rHc((size_t)n_hc * out_dim);
|
||||
for (int d = 0; d < out_dim; d++) {
|
||||
rBlk[d] = ref_row(&r.W[(size_t)d * blocks * 34], XQ.data(), XS.data(), blocks);
|
||||
const double bv = rBlk[d] + (double)ADD[d];
|
||||
for (int dst = 0; dst < n_hc; dst++) {
|
||||
double hc = bv * (double)SPL[n_hc + dst];
|
||||
for (int src = 0; src < n_hc; src++)
|
||||
hc += (double)SPL[2 * n_hc + dst + src * n_hc] * (double)RES[(size_t)src * out_dim + d];
|
||||
rHc[(size_t)dst * out_dim + d] = hc;
|
||||
}
|
||||
}
|
||||
const double gb = (double)out_dim * blocks * 34 / 1e9;
|
||||
printf("%-22s out=%d in=%d (%.1f MB/call, iters=%d)\n", label, out_dim, in_dim, gb * 1000, iters);
|
||||
printf(" baseline warp8 : %.4f ms -> %6.1f GB/s\n", ms_base, gb / (ms_base / 1e3));
|
||||
printf(" aligned warp8 : %.4f ms -> %6.1f GB/s (%+.1f%%)\n",
|
||||
ms_al, gb / (ms_al / 1e3), 100.0 * (ms_base / ms_al - 1.0));
|
||||
int bad = check_pair_tol(bBlk, aBlk, rBlk, "block_out");
|
||||
bad += check_pair_tol(bHc, aHc, rHc, "out_hc");
|
||||
|
||||
CK(cudaFree(dev.dW)); CK(cudaFree(dev.dArt));
|
||||
CK(cudaFree(dOutHc)); CK(cudaFree(dBlk)); CK(cudaFree(dAdd)); CK(cudaFree(dRes));
|
||||
CK(cudaFree(dSpl)); CK(cudaFree(dXq)); CK(cudaFree(dXs));
|
||||
CK(cudaEventDestroy(e0)); CK(cudaEventDestroy(e1)); CK(cudaStreamDestroy(stream));
|
||||
return bad;
|
||||
}
|
||||
|
||||
struct PairCtx {
|
||||
cudaStream_t stream; DevCopies d0, d1; uint64_t dqb0, dqb1;
|
||||
float *out0, *out1; const int8_t *xq; const float *xs;
|
||||
uint64_t in_dim, out0_dim, out1_dim, blocks; int base;
|
||||
};
|
||||
static void pair_launch(int i, void *p) {
|
||||
PairCtx *c = (PairCtx *)p;
|
||||
const uint64_t max_out = c->out0_dim > c->out1_dim ? c->out0_dim : c->out1_dim;
|
||||
const unsigned grid = ((unsigned)max_out + 7u) / 8u;
|
||||
if (c->base) {
|
||||
pair_base_kernel<<<grid, 256, 0, c->stream>>>(
|
||||
c->out0, c->out1, c->d0.w(i), c->d1.w(i), c->xq, c->xs,
|
||||
c->in_dim, c->out0_dim, c->out1_dim, c->blocks, 1);
|
||||
} else {
|
||||
pair_aligned_kernel<<<grid, 256, 0, c->stream>>>(
|
||||
c->out0, c->out1,
|
||||
c->d0.art_qs(i, c->dqb0), c->d0.art_dq(i),
|
||||
c->d1.art_qs(i, c->dqb1), c->d1.art_dq(i),
|
||||
c->xq, c->xs, c->in_dim, c->out0_dim, c->out1_dim, c->blocks);
|
||||
}
|
||||
}
|
||||
|
||||
static int run_pair(const char *label, int out0, int out1, int in_dim, int iters, uint32_t seed) {
|
||||
const int blocks = in_dim / 32;
|
||||
std::mt19937 rng(seed);
|
||||
RawArt r0 = make_weights(rng, out0, blocks);
|
||||
RawArt r1 = make_weights(rng, out1, blocks);
|
||||
std::vector<float> X(in_dim);
|
||||
for (auto &v : X) v = ((float)(rng() % 2000) - 1000.0f) / 500.0f;
|
||||
std::vector<int8_t> XQ(in_dim); std::vector<float> XS(blocks);
|
||||
quantize_q8_0_host(X.data(), XQ.data(), XS.data(), in_dim);
|
||||
|
||||
DevCopies d0 = upload_rotating(r0, 128ull << 20);
|
||||
DevCopies d1 = upload_rotating(r1, 128ull << 20);
|
||||
float *dOut0, *dOut1, *dXs; int8_t *dXq;
|
||||
CK(cudaMalloc(&dOut0, sizeof(float) * out0));
|
||||
CK(cudaMalloc(&dOut1, sizeof(float) * out1));
|
||||
CK(cudaMalloc(&dXq, XQ.size()));
|
||||
CK(cudaMalloc(&dXs, sizeof(float) * XS.size()));
|
||||
CK(cudaMemcpy(dXq, XQ.data(), XQ.size(), cudaMemcpyHostToDevice));
|
||||
CK(cudaMemcpy(dXs, XS.data(), sizeof(float) * XS.size(), cudaMemcpyHostToDevice));
|
||||
|
||||
cudaStream_t stream; CK(cudaStreamCreate(&stream));
|
||||
cudaEvent_t e0, e1; CK(cudaEventCreate(&e0)); CK(cudaEventCreate(&e1));
|
||||
|
||||
PairCtx c = {stream, d0, d1, r0.dq_bytes, r1.dq_bytes, dOut0, dOut1,
|
||||
dXq, dXs, (uint64_t)in_dim, (uint64_t)out0, (uint64_t)out1, (uint64_t)blocks, 1};
|
||||
const float ms_base = time_loop(stream, e0, e1, iters, pair_launch, &c);
|
||||
std::vector<float> b0(out0), b1(out1);
|
||||
CK(cudaMemcpy(b0.data(), dOut0, sizeof(float) * out0, cudaMemcpyDeviceToHost));
|
||||
CK(cudaMemcpy(b1.data(), dOut1, sizeof(float) * out1, cudaMemcpyDeviceToHost));
|
||||
c.base = 0;
|
||||
const float ms_al = time_loop(stream, e0, e1, iters, pair_launch, &c);
|
||||
std::vector<float> a0(out0), a1(out1);
|
||||
CK(cudaMemcpy(a0.data(), dOut0, sizeof(float) * out0, cudaMemcpyDeviceToHost));
|
||||
CK(cudaMemcpy(a1.data(), dOut1, sizeof(float) * out1, cudaMemcpyDeviceToHost));
|
||||
|
||||
std::vector<double> ref0(out0), ref1(out1);
|
||||
for (int d = 0; d < out0; d++) ref0[d] = ref_row(&r0.W[(size_t)d * blocks * 34], XQ.data(), XS.data(), blocks);
|
||||
for (int d = 0; d < out1; d++) ref1[d] = ref_row(&r1.W[(size_t)d * blocks * 34], XQ.data(), XS.data(), blocks);
|
||||
|
||||
const double gb = ((double)out0 + out1) * blocks * 34 / 1e9;
|
||||
printf("%-22s out0=%d out1=%d in=%d (%.1f MB/call, iters=%d)\n", label, out0, out1, in_dim, gb * 1000, iters);
|
||||
printf(" baseline warp8 : %.4f ms -> %6.1f GB/s\n", ms_base, gb / (ms_base / 1e3));
|
||||
printf(" aligned warp8 : %.4f ms -> %6.1f GB/s (%+.1f%%)\n",
|
||||
ms_al, gb / (ms_al / 1e3), 100.0 * (ms_base / ms_al - 1.0));
|
||||
int bad = check_pair_tol(b0, a0, ref0, "out0");
|
||||
bad += check_pair_tol(b1, a1, ref1, "out1");
|
||||
|
||||
CK(cudaFree(d0.dW)); CK(cudaFree(d0.dArt)); CK(cudaFree(d1.dW)); CK(cudaFree(d1.dArt));
|
||||
CK(cudaFree(dOut0)); CK(cudaFree(dOut1)); CK(cudaFree(dXq)); CK(cudaFree(dXs));
|
||||
CK(cudaEventDestroy(e0)); CK(cudaEventDestroy(e1)); CK(cudaStreamDestroy(stream));
|
||||
return bad;
|
||||
}
|
||||
|
||||
struct GroupCtx {
|
||||
cudaStream_t stream; DevCopies dev; uint64_t dqb;
|
||||
float *low; const int8_t *xq; const float *xs;
|
||||
uint64_t group_dim, rank; uint32_t n_groups; uint64_t blocks; int base;
|
||||
};
|
||||
static void grouped_launch(int i, void *p) {
|
||||
GroupCtx *c = (GroupCtx *)p;
|
||||
const uint64_t low_dim = (uint64_t)c->n_groups * c->rank;
|
||||
const unsigned grid = ((unsigned)low_dim + 7u) / 8u;
|
||||
if (c->base) {
|
||||
grouped_base_kernel<<<grid, 256, 0, c->stream>>>(
|
||||
c->low, c->dev.w(i), c->xq, c->xs, c->group_dim, c->rank,
|
||||
c->n_groups, 1u, c->blocks, 1);
|
||||
} else {
|
||||
grouped_aligned_kernel<<<grid, 256, 0, c->stream>>>(
|
||||
c->low, c->dev.art_qs(i, c->dqb), c->dev.art_dq(i), c->xq, c->xs,
|
||||
c->group_dim, c->rank, c->n_groups, 1u, c->blocks);
|
||||
}
|
||||
}
|
||||
|
||||
static int run_grouped(const char *label, int group_dim, int rank, int n_groups, int iters, uint32_t seed) {
|
||||
const int blocks = group_dim / 32;
|
||||
const long long low_dim = (long long)n_groups * rank;
|
||||
std::mt19937 rng(seed);
|
||||
RawArt r = make_weights(rng, low_dim, blocks);
|
||||
// per-group activation rows
|
||||
std::vector<float> X((size_t)n_groups * group_dim);
|
||||
for (auto &v : X) v = ((float)(rng() % 2000) - 1000.0f) / 500.0f;
|
||||
std::vector<int8_t> XQ(X.size()); std::vector<float> XS((size_t)n_groups * blocks);
|
||||
for (int g = 0; g < n_groups; g++)
|
||||
quantize_q8_0_host(&X[(size_t)g * group_dim], &XQ[(size_t)g * group_dim],
|
||||
&XS[(size_t)g * blocks], group_dim);
|
||||
|
||||
DevCopies dev = upload_rotating(r);
|
||||
float *dLow, *dXs; int8_t *dXq;
|
||||
CK(cudaMalloc(&dLow, sizeof(float) * low_dim));
|
||||
CK(cudaMalloc(&dXq, XQ.size()));
|
||||
CK(cudaMalloc(&dXs, sizeof(float) * XS.size()));
|
||||
CK(cudaMemcpy(dXq, XQ.data(), XQ.size(), cudaMemcpyHostToDevice));
|
||||
CK(cudaMemcpy(dXs, XS.data(), sizeof(float) * XS.size(), cudaMemcpyHostToDevice));
|
||||
|
||||
cudaStream_t stream; CK(cudaStreamCreate(&stream));
|
||||
cudaEvent_t e0, e1; CK(cudaEventCreate(&e0)); CK(cudaEventCreate(&e1));
|
||||
|
||||
GroupCtx c = {stream, dev, r.dq_bytes, dLow, dXq, dXs,
|
||||
(uint64_t)group_dim, (uint64_t)rank, (uint32_t)n_groups, (uint64_t)blocks, 1};
|
||||
const float ms_base = time_loop(stream, e0, e1, iters, grouped_launch, &c);
|
||||
std::vector<float> bl(low_dim);
|
||||
CK(cudaMemcpy(bl.data(), dLow, sizeof(float) * low_dim, cudaMemcpyDeviceToHost));
|
||||
c.base = 0;
|
||||
const float ms_al = time_loop(stream, e0, e1, iters, grouped_launch, &c);
|
||||
std::vector<float> al(low_dim);
|
||||
CK(cudaMemcpy(al.data(), dLow, sizeof(float) * low_dim, cudaMemcpyDeviceToHost));
|
||||
|
||||
std::vector<double> ref(low_dim);
|
||||
for (long long d = 0; d < low_dim; d++) {
|
||||
const int g = (int)(d / rank);
|
||||
ref[d] = ref_row(&r.W[(size_t)d * blocks * 34], &XQ[(size_t)g * group_dim],
|
||||
&XS[(size_t)g * blocks], blocks);
|
||||
}
|
||||
|
||||
const double gb = (double)low_dim * blocks * 34 / 1e9;
|
||||
printf("%-22s gdim=%d rank=%d groups=%d (%.1f MB/call, iters=%d)\n",
|
||||
label, group_dim, rank, n_groups, gb * 1000, iters);
|
||||
printf(" baseline warp8 : %.4f ms -> %6.1f GB/s\n", ms_base, gb / (ms_base / 1e3));
|
||||
printf(" aligned warp8 : %.4f ms -> %6.1f GB/s (%+.1f%%)\n",
|
||||
ms_al, gb / (ms_al / 1e3), 100.0 * (ms_base / ms_al - 1.0));
|
||||
int bad = check_pair_tol(bl, al, ref, "low");
|
||||
|
||||
CK(cudaFree(dev.dW)); CK(cudaFree(dev.dArt));
|
||||
CK(cudaFree(dLow)); CK(cudaFree(dXq)); CK(cudaFree(dXs));
|
||||
CK(cudaEventDestroy(e0)); CK(cudaEventDestroy(e1)); CK(cudaStreamDestroy(stream));
|
||||
return bad;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const uint32_t seed = argc > 1 ? (uint32_t)atoi(argv[1]) : 1234u;
|
||||
printf("PROTO_Q8_WARP8 (Inc4: aligned-SoA variants of the custom warp8 q8_0 kernels)\n");
|
||||
int bad = 0;
|
||||
bad += run_hc_expand("hc_expand(output_b)", 4096, 8192, 1500, seed);
|
||||
bad += run_hc_expand("hc_expand(shr_down)", 4096, 2048, 2000, seed + 1);
|
||||
bad += run_pair("pair(q_a+kv)", 1024, 512, 4096, 2000, seed + 2);
|
||||
bad += run_pair("pair(shr_gate+up)", 2048, 2048, 4096, 2000, seed + 3);
|
||||
bad += run_grouped("grouped(output_a)", 4096, 1024, 8, 1500, seed + 4);
|
||||
printf(bad == 0 ? "ALL PASS\n" : "FAILURES: %d\n", bad);
|
||||
return bad == 0 ? 0 : 2;
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
/* P3.3 proto (2026-07-07, CLOSED "cuBLAS knows best"): are the cutlass_80_*
|
||||
* (Ampere-tile) f16 GEMMs on sm_121 leaving throughput on the table? NO:
|
||||
* - The 6.25 ms cutlass_80_64x256 launch (129x/12k admission) is the
|
||||
* ATTENTION OUTPUT projection (M=8192=8 groups x 1024 rank, K=4096,
|
||||
* N=4096 -> 47 TFLOPS, near roof). The "11 TFLOPS" that chartered this
|
||||
* recon assumed it was the indexer q_b (K=1024) -- wrong dims. The real
|
||||
* indexer q_b GEMM is producer-gated (n_comp > top-k; last chunk only at
|
||||
* 12k) and runs cutlass_80_128x256 at 1.3 ms / 53 TFLOPS.
|
||||
* - cublasLt heuristics beat GemmEx by only ~6-10% on these shapes (not
|
||||
* worth the plumbing); TF32 math mode, operand alignment (A/B/C +16/+32B),
|
||||
* L2 warmth (8-set rotation) and host-vs-device A were all bracketed:
|
||||
* only host-mapped A costs real time (2x), and production weights already
|
||||
* resolve to device-resident copies (HBM cache / WS import) -- verified
|
||||
* in-server via DS4_F16_PTR_PROBE (recon-only, not in tree).
|
||||
* - hc mix (m=24) is latency-floor at any tile; comp_proj m=256 is fine.
|
||||
*
|
||||
* Shapes measured (production call form: A=weights f16 OP_T lda=k, B=acts
|
||||
* f16 OP_N ldb=k, C f32 ldc=m, computeType F32, 32 MiB handle workspace):
|
||||
*
|
||||
* Build (on a GB10 box):
|
||||
* /usr/local/cuda/bin/nvcc -O3 -arch=sm_121 -std=c++17 \
|
||||
* -o /home/ent/dspark_work/test_f16_gemm_lt cuda/mmq/test/test_f16_gemm_lt.cu \
|
||||
* -lcublasLt -lcublas
|
||||
* Run: ./test_f16_gemm_lt (all shapes, 100 iters each)
|
||||
* Kernel names: nsys profile -o /tmp/lt ./test_f16_gemm_lt && inspect sqlite.
|
||||
*/
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cuda_fp16.h>
|
||||
#include <cublas_v2.h>
|
||||
#include <cublasLt.h>
|
||||
|
||||
#define CK(x) do { auto _e = (x); if (_e != cudaSuccess) { \
|
||||
fprintf(stderr, "CUDA ERR %s @%d: %s\n", #x, __LINE__, cudaGetErrorString(_e)); exit(1); } } while (0)
|
||||
#define CB(x) do { auto _e = (x); if (_e != CUBLAS_STATUS_SUCCESS) { \
|
||||
fprintf(stderr, "CUBLAS ERR %s @%d: %d\n", #x, __LINE__, (int)_e); exit(1); } } while (0)
|
||||
|
||||
static void fill_half(__half *p, size_t nelem, unsigned seed) {
|
||||
__half *h = (__half *)malloc(nelem * sizeof(__half));
|
||||
unsigned s = seed;
|
||||
for (size_t i = 0; i < nelem; i++) {
|
||||
s = s * 1664525u + 1013904223u;
|
||||
h[i] = __float2half(((float)(s >> 8) / (float)(1u << 24) - 0.5f) * 0.25f);
|
||||
}
|
||||
CK(cudaMemcpy(p, h, nelem * sizeof(__half), cudaMemcpyHostToDevice));
|
||||
free(h);
|
||||
}
|
||||
|
||||
static float time_iters(cudaStream_t st, int iters, void (*fn)(void *), void *ud) {
|
||||
cudaEvent_t e0, e1;
|
||||
CK(cudaEventCreate(&e0));
|
||||
CK(cudaEventCreate(&e1));
|
||||
for (int i = 0; i < 10; i++) fn(ud); /* warmup */
|
||||
CK(cudaStreamSynchronize(st));
|
||||
CK(cudaEventRecord(e0, st));
|
||||
for (int i = 0; i < iters; i++) fn(ud);
|
||||
CK(cudaEventRecord(e1, st));
|
||||
CK(cudaEventSynchronize(e1));
|
||||
float ms = 0.f;
|
||||
CK(cudaEventElapsedTime(&ms, e0, e1));
|
||||
cudaEventDestroy(e0);
|
||||
cudaEventDestroy(e1);
|
||||
return ms / iters;
|
||||
}
|
||||
|
||||
struct GemmCase {
|
||||
int m, n, k;
|
||||
const __half *A, *B;
|
||||
float *C;
|
||||
cublasHandle_t bl;
|
||||
cublasLtHandle_t lt;
|
||||
void *ws;
|
||||
size_t ws_bytes;
|
||||
cublasLtMatmulDesc_t op;
|
||||
cublasLtMatrixLayout_t la, lb, lc;
|
||||
cublasLtMatmulAlgo_t algo;
|
||||
int has_algo;
|
||||
};
|
||||
|
||||
static void run_gemmex(void *ud) {
|
||||
GemmCase *g = (GemmCase *)ud;
|
||||
const float alpha = 1.f, beta = 0.f;
|
||||
CB(cublasGemmEx(g->bl, CUBLAS_OP_T, CUBLAS_OP_N, g->m, g->n, g->k,
|
||||
&alpha, g->A, CUDA_R_16F, g->k, g->B, CUDA_R_16F, g->k,
|
||||
&beta, g->C, CUDA_R_32F, g->m, CUDA_R_32F, CUBLAS_GEMM_DEFAULT));
|
||||
}
|
||||
|
||||
static void run_lt(void *ud) {
|
||||
GemmCase *g = (GemmCase *)ud;
|
||||
const float alpha = 1.f, beta = 0.f;
|
||||
CB(cublasLtMatmul(g->lt, g->op, &alpha, g->A, g->la, g->B, g->lb,
|
||||
&beta, g->C, g->lc, g->C, g->lc,
|
||||
g->has_algo ? &g->algo : NULL, g->ws, g->ws_bytes, 0));
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct { const char *name; int m, n, k; } shapes[] = {
|
||||
{ "indexer_q_b m=8192 n=4096 k=1024", 8192, 4096, 1024 },
|
||||
{ "hc_mix m=24 n=4096 k=16384", 24, 4096, 16384 },
|
||||
{ "comp_proj m=256 n=4096 k=4096", 256, 4096, 4096 },
|
||||
};
|
||||
cublasHandle_t bl;
|
||||
CB(cublasCreate(&bl));
|
||||
void *hws = NULL;
|
||||
const size_t hws_bytes = 32u * 1024u * 1024u;
|
||||
CK(cudaMalloc(&hws, hws_bytes));
|
||||
CB(cublasSetWorkspace(bl, hws, hws_bytes));
|
||||
cublasLtHandle_t lt;
|
||||
CB(cublasLtCreate(<));
|
||||
void *ltws = NULL;
|
||||
const size_t ltws_bytes = 32u * 1024u * 1024u;
|
||||
CK(cudaMalloc(<ws, ltws_bytes));
|
||||
|
||||
for (auto &s : shapes) {
|
||||
__half *A, *B;
|
||||
float *C;
|
||||
CK(cudaMalloc(&A, (size_t)s.m * s.k * sizeof(__half)));
|
||||
CK(cudaMalloc(&B, (size_t)s.n * s.k * sizeof(__half)));
|
||||
CK(cudaMalloc(&C, (size_t)s.m * s.n * sizeof(float)));
|
||||
fill_half(A, (size_t)s.m * s.k, 1234);
|
||||
fill_half(B, (size_t)s.n * s.k, 5678);
|
||||
|
||||
GemmCase g = {};
|
||||
g.m = s.m; g.n = s.n; g.k = s.k;
|
||||
g.A = A; g.B = B; g.C = C;
|
||||
g.bl = bl; g.lt = lt; g.ws = ltws; g.ws_bytes = ltws_bytes;
|
||||
|
||||
const double flops = 2.0 * s.m * s.n * s.k;
|
||||
const float ms_ex = time_iters(0, 100, run_gemmex, &g);
|
||||
printf("%-36s GemmEx DEFAULT : %8.3f ms %6.1f TFLOPS\n",
|
||||
s.name, ms_ex, flops / (ms_ex * 1e9));
|
||||
/* Production handle state (ds4_gpu_init): deprecated TF32 math mode.
|
||||
* On CUDA 13 this may shunt the heuristic onto a legacy kernel table
|
||||
* -- the suspected source of the 64x256 cutlass_80 pick. */
|
||||
CB(cublasSetMathMode(bl, CUBLAS_TF32_TENSOR_OP_MATH));
|
||||
const float ms_tf = time_iters(0, 100, run_gemmex, &g);
|
||||
printf("%-36s GemmEx TF32-mode : %8.3f ms %6.1f TFLOPS\n",
|
||||
s.name, ms_tf, flops / (ms_tf * 1e9));
|
||||
CB(cublasSetMathMode(bl, CUBLAS_DEFAULT_MATH));
|
||||
/* Production pointer classes: A = gguf tensor offset inside a mapped
|
||||
* model (32B alignment class, not cudaMalloc's 256B); B = cuda_tmp
|
||||
* suballocation. Heuristics gate kernels on operand alignment. */
|
||||
{
|
||||
__half *A32, *B32;
|
||||
CK(cudaMalloc(&A32, (size_t)s.m * s.k * sizeof(__half) + 256));
|
||||
CK(cudaMalloc(&B32, (size_t)s.n * s.k * sizeof(__half) + 256));
|
||||
CK(cudaMemcpy((char *)A32 + 32, A, (size_t)s.m * s.k * sizeof(__half), cudaMemcpyDeviceToDevice));
|
||||
CK(cudaMemcpy((char *)B32 + 32, B, (size_t)s.n * s.k * sizeof(__half), cudaMemcpyDeviceToDevice));
|
||||
GemmCase g2 = g;
|
||||
g2.A = (const __half *)((const char *)A32 + 32);
|
||||
const float ms_a = time_iters(0, 100, run_gemmex, &g2);
|
||||
printf("%-36s GemmEx A+32B : %8.3f ms %6.1f TFLOPS\n",
|
||||
s.name, ms_a, flops / (ms_a * 1e9));
|
||||
GemmCase g3 = g;
|
||||
g3.B = (const __half *)((const char *)B32 + 32);
|
||||
const float ms_b = time_iters(0, 100, run_gemmex, &g3);
|
||||
printf("%-36s GemmEx B+32B : %8.3f ms %6.1f TFLOPS\n",
|
||||
s.name, ms_b, flops / (ms_b * 1e9));
|
||||
GemmCase g4 = g2;
|
||||
g4.B = g3.B;
|
||||
const float ms_ab = time_iters(0, 100, run_gemmex, &g4);
|
||||
printf("%-36s GemmEx A+32B B+32B : %8.3f ms %6.1f TFLOPS\n",
|
||||
s.name, ms_ab, flops / (ms_ab * 1e9));
|
||||
float *C32;
|
||||
CK(cudaMalloc(&C32, (size_t)s.m * s.n * sizeof(float) + 256));
|
||||
GemmCase g5 = g;
|
||||
g5.C = (float *)((char *)C32 + 32);
|
||||
const float ms_c = time_iters(0, 100, run_gemmex, &g5);
|
||||
printf("%-36s GemmEx C+32B : %8.3f ms %6.1f TFLOPS\n",
|
||||
s.name, ms_c, flops / (ms_c * 1e9));
|
||||
GemmCase g6 = g;
|
||||
g6.C = (float *)((char *)C32 + 16);
|
||||
const float ms_c16 = time_iters(0, 100, run_gemmex, &g6);
|
||||
printf("%-36s GemmEx C+16B : %8.3f ms %6.1f TFLOPS\n",
|
||||
s.name, ms_c16, flops / (ms_c16 * 1e9));
|
||||
CK(cudaFree(C32));
|
||||
CK(cudaFree(A32));
|
||||
CK(cudaFree(B32));
|
||||
}
|
||||
|
||||
/* Lt setup: same TN f16/f16/f32-compute layout */
|
||||
CB(cublasLtMatmulDescCreate(&g.op, CUBLAS_COMPUTE_32F, CUDA_R_32F));
|
||||
cublasOperation_t ta = CUBLAS_OP_T, tb = CUBLAS_OP_N;
|
||||
CB(cublasLtMatmulDescSetAttribute(g.op, CUBLASLT_MATMUL_DESC_TRANSA, &ta, sizeof(ta)));
|
||||
CB(cublasLtMatmulDescSetAttribute(g.op, CUBLASLT_MATMUL_DESC_TRANSB, &tb, sizeof(tb)));
|
||||
CB(cublasLtMatrixLayoutCreate(&g.la, CUDA_R_16F, s.k, s.m, s.k)); /* A: k x m, lda=k (OP_T) */
|
||||
CB(cublasLtMatrixLayoutCreate(&g.lb, CUDA_R_16F, s.k, s.n, s.k));
|
||||
CB(cublasLtMatrixLayoutCreate(&g.lc, CUDA_R_32F, s.m, s.n, s.m));
|
||||
|
||||
/* Substrate x L2-warmth matrix. Production weight pointers are
|
||||
* host-mapped (client mmap-registered / WS IPC import -- cmtp3d
|
||||
* measured both at the same speed); the proto default is cudaMalloc
|
||||
* A reused every iteration (L2-warm). 8-set rotation defeats L2
|
||||
* reuse; host-alloc A reproduces the mapped-weight class. */
|
||||
{
|
||||
enum { NSETS = 8 };
|
||||
static __half *As[NSETS], *Bs[NSETS];
|
||||
static float *Cs[NSETS];
|
||||
static GemmCase gsets;
|
||||
static int set_idx;
|
||||
struct Rot {
|
||||
static void run(void *ud) {
|
||||
GemmCase *g = (GemmCase *)ud;
|
||||
(void)g;
|
||||
GemmCase tmp = gsets;
|
||||
tmp.A = As[set_idx % NSETS];
|
||||
tmp.B = Bs[set_idx % NSETS];
|
||||
tmp.C = Cs[set_idx % NSETS];
|
||||
set_idx++;
|
||||
run_gemmex(&tmp);
|
||||
}
|
||||
};
|
||||
gsets = g;
|
||||
/* dev-cold */
|
||||
for (int i = 0; i < NSETS; i++) {
|
||||
CK(cudaMalloc(&As[i], (size_t)s.m * s.k * sizeof(__half)));
|
||||
CK(cudaMalloc(&Bs[i], (size_t)s.n * s.k * sizeof(__half)));
|
||||
CK(cudaMalloc(&Cs[i], (size_t)s.m * s.n * sizeof(float)));
|
||||
fill_half(As[i], (size_t)s.m * s.k, 100 + i);
|
||||
fill_half(Bs[i], (size_t)s.n * s.k, 200 + i);
|
||||
}
|
||||
set_idx = 0;
|
||||
const float ms_dc = time_iters(0, 100, Rot::run, &g);
|
||||
printf("%-36s GemmEx devA cold : %8.3f ms %6.1f TFLOPS\n",
|
||||
s.name, ms_dc, flops / (ms_dc * 1e9));
|
||||
for (int i = 0; i < NSETS; i++) { CK(cudaFree(As[i])); CK(cudaFree(Bs[i])); CK(cudaFree(Cs[i])); }
|
||||
/* hostA warm */
|
||||
__half *Ah;
|
||||
CK(cudaHostAlloc(&Ah, (size_t)s.m * s.k * sizeof(__half), cudaHostAllocMapped));
|
||||
{
|
||||
unsigned sd = 42;
|
||||
for (size_t i = 0; i < (size_t)s.m * s.k; i++) {
|
||||
sd = sd * 1664525u + 1013904223u;
|
||||
Ah[i] = __float2half(((float)(sd >> 8) / (float)(1u << 24) - 0.5f) * 0.25f);
|
||||
}
|
||||
}
|
||||
GemmCase gh = g;
|
||||
gh.A = Ah;
|
||||
const float ms_hw = time_iters(0, 100, run_gemmex, &gh);
|
||||
printf("%-36s GemmEx hostA warm : %8.3f ms %6.1f TFLOPS\n",
|
||||
s.name, ms_hw, flops / (ms_hw * 1e9));
|
||||
/* hostA cold: rotate device B/C, host A stays (weights are the
|
||||
* suspect; hostA likely bypasses L2 anyway) */
|
||||
for (int i = 0; i < NSETS; i++) {
|
||||
CK(cudaMalloc(&Bs[i], (size_t)s.n * s.k * sizeof(__half)));
|
||||
CK(cudaMalloc(&Cs[i], (size_t)s.m * s.n * sizeof(float)));
|
||||
fill_half(Bs[i], (size_t)s.n * s.k, 300 + i);
|
||||
As[i] = Ah;
|
||||
}
|
||||
gsets = gh;
|
||||
set_idx = 0;
|
||||
const float ms_hc = time_iters(0, 100, Rot::run, &g);
|
||||
printf("%-36s GemmEx hostA cold : %8.3f ms %6.1f TFLOPS\n",
|
||||
s.name, ms_hc, flops / (ms_hc * 1e9));
|
||||
for (int i = 0; i < NSETS; i++) { CK(cudaFree(Bs[i])); CK(cudaFree(Cs[i])); }
|
||||
CK(cudaFreeHost(Ah));
|
||||
}
|
||||
cublasLtMatmulPreference_t pref;
|
||||
CB(cublasLtMatmulPreferenceCreate(&pref));
|
||||
CB(cublasLtMatmulPreferenceSetAttribute(pref, CUBLASLT_MATMUL_PREF_MAX_WORKSPACE_BYTES,
|
||||
<ws_bytes, sizeof(ltws_bytes)));
|
||||
cublasLtMatmulHeuristicResult_t res[8];
|
||||
int nres = 0;
|
||||
CB(cublasLtMatmulAlgoGetHeuristic(lt, g.op, g.la, g.lb, g.lc, g.lc, pref, 8, res, &nres));
|
||||
printf("%-36s Lt heuristics: %d candidates\n", s.name, nres);
|
||||
float best_ms = 1e30f;
|
||||
int best_i = -1;
|
||||
for (int i = 0; i < nres; i++) {
|
||||
g.algo = res[i].algo;
|
||||
g.has_algo = 1;
|
||||
const float ms = time_iters(0, 100, run_lt, &g);
|
||||
int algo_id = -1, tile = -1, splitk = -1;
|
||||
size_t sz;
|
||||
cublasLtMatmulAlgoConfigGetAttribute(&g.algo, CUBLASLT_ALGO_CONFIG_ID, &algo_id, sizeof(algo_id), &sz);
|
||||
cublasLtMatmulAlgoConfigGetAttribute(&g.algo, CUBLASLT_ALGO_CONFIG_TILE_ID, &tile, sizeof(tile), &sz);
|
||||
cublasLtMatmulAlgoConfigGetAttribute(&g.algo, CUBLASLT_ALGO_CONFIG_SPLITK_NUM, &splitk, sizeof(splitk), &sz);
|
||||
printf("%-36s Lt[%d] algo=%d tile=%d splitk=%d ws=%zu: %8.3f ms %6.1f TFLOPS\n",
|
||||
s.name, i, algo_id, tile, splitk, res[i].workspaceSize,
|
||||
ms, flops / (ms * 1e9));
|
||||
if (ms < best_ms) { best_ms = ms; best_i = i; }
|
||||
}
|
||||
if (best_i >= 0)
|
||||
printf("%-36s Lt BEST[%d] : %8.3f ms %6.1f TFLOPS (GemmEx/Lt = %.2fx)\n",
|
||||
s.name, best_i, best_ms, flops / (best_ms * 1e9), ms_ex / best_ms);
|
||||
cublasLtMatmulPreferenceDestroy(pref);
|
||||
cublasLtMatrixLayoutDestroy(g.la);
|
||||
cublasLtMatrixLayoutDestroy(g.lb);
|
||||
cublasLtMatrixLayoutDestroy(g.lc);
|
||||
cublasLtMatmulDescDestroy(g.op);
|
||||
CK(cudaFree(A)); CK(cudaFree(B)); CK(cudaFree(C));
|
||||
}
|
||||
printf("LT-GEMM DONE\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
// 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"
|
||||
|
||||
#include <cuda_runtime.h>
|
||||
#include <cuda_fp16.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#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 = 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 && !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);
|
||||
for (auto &b : W) b = (uint8_t)(rng() & 0xff);
|
||||
const long long nblk = (long long)n_experts * M * nb;
|
||||
for (long long blk = 0; blk < nblk; blk++) {
|
||||
uint16_t h = (uint16_t)(0x2c00 | (rng() & 0xff));
|
||||
memcpy(&W[blk * 66], &h, 2);
|
||||
}
|
||||
|
||||
std::vector<float> X(K);
|
||||
for (auto &v : X) v = ((float)(rng() % 2000) - 1000.0f) / 500.0f;
|
||||
|
||||
std::vector<int32_t> ids(n_idsets * n_slots);
|
||||
for (int s = 0; s < n_idsets; s++)
|
||||
for (int j = 0; j < n_slots; j++)
|
||||
ids[s * n_slots + j] = (s * n_slots + j) % n_experts;
|
||||
|
||||
// Artifact layout, exactly as the weight server repack kernel builds it:
|
||||
// [__half dq[nblk]][pad to 64B][uint2 qs[nblk*8]]
|
||||
const uint64_t expect_bytes = ds4_mmq_iq2_xxs_aligned_bytes(M, K, n_experts);
|
||||
const uint64_t dq_bytes = ((uint64_t)nblk * 2u + 63u) & ~63ull;
|
||||
const uint64_t art_bytes = dq_bytes + (uint64_t)nblk * 64u;
|
||||
if (expect_bytes != art_bytes) {
|
||||
printf("aligned_bytes mismatch: helper=%llu local=%llu\n",
|
||||
(unsigned long long)expect_bytes, (unsigned long long)art_bytes);
|
||||
return 1;
|
||||
}
|
||||
std::vector<uint8_t> ART(art_bytes);
|
||||
for (long long blk = 0; blk < nblk; blk++) {
|
||||
memcpy(&ART[blk * 2], &W[blk * 66], 2);
|
||||
memcpy(&ART[dq_bytes + (uint64_t)blk * 64u], &W[blk * 66 + 2], 64);
|
||||
}
|
||||
|
||||
// dArt2: identical bytes at a DIFFERENT address, used as the "up" stream
|
||||
// in the pair/fused entries. Passing dArt twice lets the second stream's
|
||||
// reads ride the L2 lines the first stream just fetched, inflating the
|
||||
// effective rate past the DRAM ceiling; production gate/up are distinct
|
||||
// 553 MB artifacts.
|
||||
uint8_t *dW, *dArt, *dArt2; float *dX, *dOutBase, *dOutAl; int32_t *dIds;
|
||||
CK(cudaMalloc(&dW, W.size()));
|
||||
CK(cudaMalloc(&dArt, ART.size()));
|
||||
CK(cudaMalloc(&dArt2, ART.size()));
|
||||
CK(cudaMalloc(&dX, sizeof(float) * K));
|
||||
CK(cudaMalloc(&dOutBase, sizeof(float) * n_slots * M));
|
||||
CK(cudaMalloc(&dOutAl, sizeof(float) * n_slots * M));
|
||||
CK(cudaMalloc(&dIds, sizeof(int32_t) * ids.size()));
|
||||
CK(cudaMemcpy(dW, W.data(), W.size(), cudaMemcpyHostToDevice));
|
||||
CK(cudaMemcpy(dArt, ART.data(), ART.size(), cudaMemcpyHostToDevice));
|
||||
CK(cudaMemcpy(dArt2, dArt, ART.size(), cudaMemcpyDeviceToDevice));
|
||||
CK(cudaMemcpy(dX, X.data(), sizeof(float) * K, cudaMemcpyHostToDevice));
|
||||
CK(cudaMemcpy(dIds, ids.data(), sizeof(int32_t) * ids.size(), cudaMemcpyHostToDevice));
|
||||
|
||||
cudaStream_t stream; CK(cudaStreamCreate(&stream));
|
||||
cudaEvent_t e0, e1; CK(cudaEventCreate(&e0)); CK(cudaEventCreate(&e1));
|
||||
|
||||
// ---- de-repack round-trip (M1-Inc2b): artifact -> raw must be byte-exact
|
||||
{
|
||||
uint8_t *dRaw2; CK(cudaMalloc(&dRaw2, W.size()));
|
||||
CK(cudaMemset(dRaw2, 0xAB, W.size()));
|
||||
const int drc = ds4_mmq_iq2_xxs_aligned_derepack(dArt, dRaw2, M, K, n_experts, stream);
|
||||
if (drc != 0) { printf("derepack rc=%d\n", drc); return 1; }
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
std::vector<uint8_t> RT(W.size());
|
||||
CK(cudaMemcpy(RT.data(), dRaw2, W.size(), cudaMemcpyDeviceToHost));
|
||||
if (memcmp(RT.data(), W.data(), W.size()) != 0) {
|
||||
size_t off = 0; while (off < W.size() && RT[off] == W[off]) off++;
|
||||
printf("derepack round-trip MISMATCH at byte %zu\n", off);
|
||||
return 1;
|
||||
}
|
||||
CK(cudaEventRecord(e0, stream));
|
||||
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));
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
float ms_dr = 0.0f; CK(cudaEventElapsedTime(&ms_dr, e0, e1));
|
||||
ms_dr /= dr_iters;
|
||||
printf(" derepack round-trip: byte-exact PASS, %.3f ms/fill -> %.1f GB/s (%.1f MB tensor)\n",
|
||||
ms_dr, (double)(ART.size() + W.size()) / ms_dr / 1e6,
|
||||
(double)W.size() / 1e6);
|
||||
CK(cudaFree(dRaw2));
|
||||
}
|
||||
|
||||
// ---- baseline: raw-layout production vec entry -------------------------
|
||||
int rc = ds4_mmq_iq2_xxs_moe_vec(dW, dX, dIds, dOutBase, M, K, 1, n_experts, n_slots, stream);
|
||||
if (rc != 0) { printf("baseline moe_vec rc=%d\n", rc); return 1; }
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
CK(cudaEventRecord(e0, stream));
|
||||
for (int i = 0; i < iters; i++)
|
||||
(void)ds4_mmq_iq2_xxs_moe_vec(dW, dX, dIds + (i % n_idsets) * n_slots, dOutBase,
|
||||
M, K, 1, n_experts, n_slots, stream);
|
||||
CK(cudaEventRecord(e1, stream));
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
float ms_base = 0.0f; CK(cudaEventElapsedTime(&ms_base, e0, e1));
|
||||
ms_base /= iters;
|
||||
|
||||
// ---- aligned production entry ------------------------------------------
|
||||
rc = ds4_mmq_iq2_xxs_aligned_moe_vec(dArt, dX, dIds, dOutAl, M, K, 1, n_experts, n_slots, stream);
|
||||
if (rc != 0) { printf("aligned entry rc=%d\n", rc); return 1; }
|
||||
// width > 16 (beyond the vec-tier envelope) must be rejected so the
|
||||
// caller can fall back; 1..16 are accepted since the multi-token fix.
|
||||
if (ds4_mmq_iq2_xxs_aligned_moe_vec(dArt, dX, dIds, dOutAl, M, K, 17, n_experts, n_slots, stream) == 0) {
|
||||
printf("aligned entry accepted n_tokens=17 (must reject)\n");
|
||||
return 1;
|
||||
}
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
CK(cudaEventRecord(e0, stream));
|
||||
for (int i = 0; i < iters; i++)
|
||||
(void)ds4_mmq_iq2_xxs_aligned_moe_vec(dArt, dX, dIds + (i % n_idsets) * n_slots, dOutAl,
|
||||
M, K, 1, n_experts, n_slots, stream);
|
||||
CK(cudaEventRecord(e1, stream));
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
float ms_al = 0.0f; CK(cudaEventElapsedTime(&ms_al, e0, e1));
|
||||
ms_al /= iters;
|
||||
|
||||
// ---- Inc2 variant P: pair (one quantize, one z=2 launch) ----------------
|
||||
float *dOutUpP; CK(cudaMalloc(&dOutUpP, sizeof(float) * n_slots * M));
|
||||
rc = ds4_mmq_iq2_xxs_aligned_moe_pair_vec(dArt, dArt2, dX, dIds, dOutAl, dOutUpP,
|
||||
M, K, 1, n_experts, n_slots, stream);
|
||||
if (rc != 0) { printf("pair entry rc=%d\n", rc); return 1; }
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
CK(cudaEventRecord(e0, stream));
|
||||
for (int i = 0; i < iters; i++)
|
||||
(void)ds4_mmq_iq2_xxs_aligned_moe_pair_vec(dArt, dArt2, dX,
|
||||
dIds + (i % n_idsets) * n_slots,
|
||||
dOutAl, dOutUpP, M, K, 1, n_experts, n_slots, stream);
|
||||
CK(cudaEventRecord(e1, stream));
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
float ms_pair = 0.0f; CK(cudaEventElapsedTime(&ms_pair, e0, e1));
|
||||
ms_pair /= iters;
|
||||
|
||||
// ---- Inc2 variant F: fused gate+up+swiglu -> mid ------------------------
|
||||
float *dMid, *dSlotW; CK(cudaMalloc(&dMid, sizeof(float) * n_slots * M));
|
||||
CK(cudaMalloc(&dSlotW, sizeof(float) * n_slots));
|
||||
std::vector<float> slotw(n_slots);
|
||||
for (int j = 0; j < n_slots; j++) slotw[j] = 0.5f + 0.1f * j;
|
||||
CK(cudaMemcpy(dSlotW, slotw.data(), sizeof(float) * n_slots, cudaMemcpyHostToDevice));
|
||||
const float clampv = 10.0f;
|
||||
rc = ds4_mmq_iq2_xxs_aligned_moe_gate_up_mid_vec(dArt, dArt2, dX, dIds, dSlotW, dMid,
|
||||
M, K, 1, n_experts, n_slots, clampv, stream);
|
||||
if (rc != 0) { printf("fused entry rc=%d\n", rc); return 1; }
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
CK(cudaEventRecord(e0, stream));
|
||||
for (int i = 0; i < iters; i++)
|
||||
(void)ds4_mmq_iq2_xxs_aligned_moe_gate_up_mid_vec(dArt, dArt2, dX,
|
||||
dIds + (i % n_idsets) * n_slots,
|
||||
dSlotW, dMid, M, K, 1, n_experts, n_slots,
|
||||
clampv, stream);
|
||||
CK(cudaEventRecord(e1, stream));
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
float ms_fused = 0.0f; CK(cudaEventElapsedTime(&ms_fused, e0, e1));
|
||||
ms_fused /= iters;
|
||||
|
||||
// NOTE: pair/fused per-iter weight bytes are 2x slot_gb (gate + up); the
|
||||
// baseline-equivalent cost of one iter is 2x the single-entry time.
|
||||
|
||||
// ---- parity on idset 0 --------------------------------------------------
|
||||
(void)ds4_mmq_iq2_xxs_moe_vec(dW, dX, dIds, dOutBase, M, K, 1, n_experts, n_slots, stream);
|
||||
(void)ds4_mmq_iq2_xxs_aligned_moe_vec(dArt, dX, dIds, dOutAl, M, K, 1, n_experts, n_slots, stream);
|
||||
float *dPairG; CK(cudaMalloc(&dPairG, sizeof(float) * n_slots * M));
|
||||
(void)ds4_mmq_iq2_xxs_aligned_moe_pair_vec(dArt, dArt2, dX, dIds, dPairG, dOutUpP,
|
||||
M, K, 1, n_experts, n_slots, stream);
|
||||
(void)ds4_mmq_iq2_xxs_aligned_moe_gate_up_mid_vec(dArt, dArt2, dX, dIds, dSlotW, dMid,
|
||||
M, K, 1, n_experts, n_slots, clampv, stream);
|
||||
CK(cudaStreamSynchronize(stream));
|
||||
std::vector<float> ob(n_slots * M), oa(n_slots * M), opg(n_slots * M), opu(n_slots * M), om(n_slots * M);
|
||||
CK(cudaMemcpy(ob.data(), dOutBase, sizeof(float) * ob.size(), cudaMemcpyDeviceToHost));
|
||||
CK(cudaMemcpy(oa.data(), dOutAl, sizeof(float) * oa.size(), cudaMemcpyDeviceToHost));
|
||||
CK(cudaMemcpy(opg.data(), dPairG, sizeof(float) * opg.size(), cudaMemcpyDeviceToHost));
|
||||
CK(cudaMemcpy(opu.data(), dOutUpP, sizeof(float) * opu.size(), cudaMemcpyDeviceToHost));
|
||||
CK(cudaMemcpy(om.data(), dMid, sizeof(float) * om.size(), cudaMemcpyDeviceToHost));
|
||||
|
||||
auto count_bad = [](const std::vector<float> &ref, const std::vector<float> &got,
|
||||
double *mr, double *ma) {
|
||||
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;
|
||||
if (rd > max_rel) max_rel = rd;
|
||||
if (rd > 2e-2 && ad > 1e-2) bad++;
|
||||
}
|
||||
if (mr) *mr = max_rel;
|
||||
if (ma) *ma = max_abs;
|
||||
return bad;
|
||||
};
|
||||
double max_rel = 0.0, max_abs = 0.0;
|
||||
int bad = count_bad(ob, oa, &max_rel, &max_abs);
|
||||
// pair: gate and up both computed from dArt, so both must match the
|
||||
// aligned single-entry output exactly (same kernel math, same inputs).
|
||||
int bad_pair = count_bad(ob, opg, nullptr, nullptr) + count_bad(ob, opu, nullptr, nullptr);
|
||||
// fused: expected mid from the baseline outputs + the exact epilogue.
|
||||
std::vector<float> em(n_slots * M);
|
||||
for (int s = 0; s < n_slots; s++) {
|
||||
for (int r = 0; r < M; r++) {
|
||||
float g = ob[(size_t)s * M + r], u = g;
|
||||
if (!std::isfinite(g)) g = 0.0f;
|
||||
if (!std::isfinite(u)) u = 0.0f;
|
||||
if (clampv > 1.0e-6f) {
|
||||
if (g > clampv) g = clampv;
|
||||
if (u > clampv) u = clampv;
|
||||
if (u < -clampv) u = -clampv;
|
||||
}
|
||||
const float silu = g / (1.0f + expf(-g));
|
||||
em[(size_t)s * M + r] = silu * u * slotw[s];
|
||||
}
|
||||
}
|
||||
int bad_mid = count_bad(em, om, nullptr, nullptr);
|
||||
|
||||
// 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] = 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, *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));
|
||||
CK(cudaMemcpy(dX4, X4.data(), sizeof(float) * X4.size(), cudaMemcpyHostToDevice));
|
||||
CK(cudaMemcpy(dW4, w4.data(), sizeof(float) * n_asg, cudaMemcpyHostToDevice));
|
||||
CK(cudaMemcpy(dIds4, ids4.data(), sizeof(int32_t) * n_asg, cudaMemcpyHostToDevice));
|
||||
int r1 = ds4_mmq_iq2_xxs_moe_vec(dW, dX4, dIds4, dOut4B, M, K, n_tok, n_experts, n_slots, stream);
|
||||
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);
|
||||
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));
|
||||
std::vector<float> ob4((size_t)n_asg * M), oa4((size_t)n_asg * M), om4((size_t)n_asg * M);
|
||||
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));
|
||||
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 = 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) {
|
||||
if (g > clampv) g = clampv;
|
||||
if (u > clampv) u = clampv;
|
||||
if (u < -clampv) u = -clampv;
|
||||
}
|
||||
const float silu = g / (1.0f + expf(-g));
|
||||
em4[(size_t)a * M + r] = silu * u * w4[a];
|
||||
}
|
||||
}
|
||||
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(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);
|
||||
printf(" baseline moe_vec : %.4f ms -> %6.1f GB/s (x2 for gate+up: %.4f ms)\n",
|
||||
ms_base, slot_gb / (ms_base / 1e3), 2.0f * ms_base);
|
||||
printf(" aligned entry : %.4f ms -> %6.1f GB/s (%+.1f%%; x2: %.4f ms)\n",
|
||||
ms_al, slot_gb / (ms_al / 1e3), 100.0 * (ms_base / ms_al - 1.0), 2.0f * ms_al);
|
||||
printf(" pair entry : %.4f ms -> %6.1f GB/s (vs 2x aligned %+.1f%%)\n",
|
||||
ms_pair, 2.0 * slot_gb / (ms_pair / 1e3), 100.0 * (2.0f * ms_al / ms_pair - 1.0));
|
||||
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"
|
||||
" | 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;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,290 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// test_mmq_soa_tiles.cu - P4 Inc3 decision gate: SoA-direct mmq tile loaders
|
||||
// vs the raw-block path, at the production V4-Flash MoE shapes.
|
||||
//
|
||||
// Method: generate a RANDOM aligned-SoA artifact (the weight-server format),
|
||||
// derive the raw block stream from it with the production derepack kernel
|
||||
// (ds4_mmq_*_aligned_derepack -- byte-exactly what the scratch path serves),
|
||||
// then run the same MoE matmul through both entries and require bit-identical
|
||||
// outputs:
|
||||
//
|
||||
// Q2_K down leg: ds4_mmq_q2_K_moe(raw) vs ds4_mmq_q2_K_moe_soa(soa)
|
||||
// IQ2_XXS gate/up leg: ds4_mmq_iq2_xxs_moe_pair(raw) vs ds4_mmq_iq2_xxs_moe_pair_soa(soa)
|
||||
//
|
||||
// Shapes are the V4-Flash production calls at a W4096 prefill chunk:
|
||||
// down: M=4096 (n_embd), K=2048 (n_ff_exp), 24576 assignments x 1 expert
|
||||
// gate/up: M=2048, K=4096, 4096 tokens x 6 experts
|
||||
// with 256 experts and uniform routing (~96 columns/expert, the trace shape).
|
||||
//
|
||||
// Timing: cudaEvent over REPS calls of each entry (identical quantize +
|
||||
// mm_ids overhead on both sides, so the delta is the tile-load effect), plus
|
||||
// the standalone derepack kernel cost (what the SoA path deletes per layer
|
||||
// switch).
|
||||
//
|
||||
// Build (on the box, after `make -j6 cuda-spark` built the mmq objects):
|
||||
// ar rcs libds4mmq.a cuda/mmq/ds4_ggml_stubs.o cuda/mmq/ds4_mmq.o \
|
||||
// cuda/mmq/quantize.o cuda/mmq/mmid.o cuda/mmq/mmvq.o
|
||||
// /usr/local/cuda/bin/nvcc -O3 --use_fast_math -std=c++17 -arch=sm_121 \
|
||||
// -Icuda/mmq cuda/mmq/test/test_mmq_soa_tiles.cu libds4mmq.a \
|
||||
// -lcudart -lcublas -lcuda -o test_mmq_soa_tiles
|
||||
|
||||
#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"
|
||||
// stub satisfies the link.
|
||||
extern "C" int ds4_cuda_q8_fold_take_q81(const void *src, uint64_t in_dim, void *out) {
|
||||
(void)src; (void)in_dim; (void)out;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#define CHECK(call) do { \
|
||||
cudaError_t err_ = (call); \
|
||||
if (err_ != cudaSuccess) { \
|
||||
fprintf(stderr, "CUDA error %s at %s:%d: %s\n", #call, __FILE__, __LINE__, \
|
||||
cudaGetErrorString(err_)); \
|
||||
exit(1); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
namespace {
|
||||
|
||||
int g_failures = 0;
|
||||
|
||||
// Random bytes with half-precision words nudged into a sane exponent range
|
||||
// (bit-parity would hold for any bits -- both paths read the same words --
|
||||
// but normal-range scales keep the timing legs representative).
|
||||
uint16_t sane_half(std::mt19937 &rng) {
|
||||
const uint16_t mant = (uint16_t)(rng() & 0x03FFu);
|
||||
const uint16_t sign = (uint16_t)((rng() & 1u) << 15);
|
||||
return (uint16_t)(sign | (14u << 10) | mant); // +/- [0.25, 0.5)
|
||||
}
|
||||
|
||||
void fill_random(std::mt19937 &rng, void *dst, size_t bytes) {
|
||||
uint32_t *p = (uint32_t *)dst;
|
||||
for (size_t i = 0; i < bytes / 4; ++i) p[i] = rng();
|
||||
}
|
||||
|
||||
float *device_random_floats(std::mt19937 &rng, size_t n) {
|
||||
std::vector<float> h(n);
|
||||
std::uniform_real_distribution<float> dist(-1.0f, 1.0f);
|
||||
for (size_t i = 0; i < n; ++i) h[i] = dist(rng);
|
||||
float *d = nullptr;
|
||||
CHECK(cudaMalloc(&d, n * sizeof(float)));
|
||||
CHECK(cudaMemcpy(d, h.data(), n * sizeof(float), cudaMemcpyHostToDevice));
|
||||
return d;
|
||||
}
|
||||
|
||||
bool compare_outputs(const char *what, const float *d_a, const float *d_b, size_t n) {
|
||||
std::vector<float> a(n), b(n);
|
||||
CHECK(cudaMemcpy(a.data(), d_a, n * sizeof(float), cudaMemcpyDeviceToHost));
|
||||
CHECK(cudaMemcpy(b.data(), d_b, n * sizeof(float), cudaMemcpyDeviceToHost));
|
||||
if (memcmp(a.data(), b.data(), n * sizeof(float)) == 0) {
|
||||
printf(" %-28s PARITY-OK (bit-identical, %zu floats)\n", what, n);
|
||||
return true;
|
||||
}
|
||||
size_t first = 0, count = 0;
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
if (memcmp(&a[i], &b[i], 4) != 0) { if (!count) first = i; ++count; }
|
||||
}
|
||||
printf(" %-28s PARITY-FAIL: %zu/%zu differ, first at %zu (raw=%.9g soa=%.9g)\n",
|
||||
what, count, n, first, a[first], b[first]);
|
||||
++g_failures;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
float time_ms(F &&fn, int reps) {
|
||||
cudaEvent_t e0, e1;
|
||||
CHECK(cudaEventCreate(&e0));
|
||||
CHECK(cudaEventCreate(&e1));
|
||||
fn(); // warmup
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
CHECK(cudaEventRecord(e0));
|
||||
for (int r = 0; r < reps; ++r) fn();
|
||||
CHECK(cudaEventRecord(e1));
|
||||
CHECK(cudaEventSynchronize(e1));
|
||||
float ms = 0.0f;
|
||||
CHECK(cudaEventElapsedTime(&ms, e0, e1));
|
||||
CHECK(cudaEventDestroy(e0));
|
||||
CHECK(cudaEventDestroy(e1));
|
||||
return ms / reps;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Q2_K down leg.
|
||||
// ---------------------------------------------------------------------------
|
||||
void run_q2k_leg(std::mt19937 &rng, int reps, int prompt_tokens) {
|
||||
const int E = 256, M = 4096, K = 2048;
|
||||
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;
|
||||
const uint64_t soa_bytes = ds4_mmq_q2_k_aligned_bytes(M, K, E);
|
||||
const uint64_t raw_bytes = nblk * 84ull;
|
||||
printf("Q2_K down leg: E=%d M=%d K=%d cols=%d (soa %.1f MiB, raw %.1f MiB)\n",
|
||||
E, M, K, n_tokens, soa_bytes / 1048576.0, raw_bytes / 1048576.0);
|
||||
|
||||
// Random SoA artifact; dm halves get sane exponents.
|
||||
std::vector<uint8_t> h_soa(soa_bytes);
|
||||
fill_random(rng, h_soa.data(), soa_bytes);
|
||||
uint16_t *dm = (uint16_t *)h_soa.data();
|
||||
for (uint64_t i = 0; i < npair * 4; ++i) dm[i] = sane_half(rng);
|
||||
|
||||
void *d_soa = nullptr, *d_raw = nullptr;
|
||||
CHECK(cudaMalloc(&d_soa, soa_bytes));
|
||||
CHECK(cudaMalloc(&d_raw, raw_bytes));
|
||||
CHECK(cudaMemcpy(d_soa, h_soa.data(), soa_bytes, cudaMemcpyHostToDevice));
|
||||
|
||||
// Raw twin = production derepack of the artifact.
|
||||
if (ds4_mmq_q2_K_aligned_derepack(d_soa, d_raw, M, K, E, 0) != 0) {
|
||||
fprintf(stderr, "q2k derepack failed\n"); exit(1);
|
||||
}
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
|
||||
float *d_x = device_random_floats(rng, (size_t)n_tokens * K);
|
||||
std::vector<int32_t> h_ids(n_tokens);
|
||||
for (int i = 0; i < n_tokens; ++i) h_ids[i] = (int32_t)(rng() % E);
|
||||
int32_t *d_ids = nullptr;
|
||||
CHECK(cudaMalloc(&d_ids, n_tokens * sizeof(int32_t)));
|
||||
CHECK(cudaMemcpy(d_ids, h_ids.data(), n_tokens * sizeof(int32_t), cudaMemcpyHostToDevice));
|
||||
|
||||
const size_t out_n = (size_t)M * n_tokens;
|
||||
float *d_out_raw = nullptr, *d_out_soa = nullptr;
|
||||
CHECK(cudaMalloc(&d_out_raw, out_n * sizeof(float)));
|
||||
CHECK(cudaMalloc(&d_out_soa, out_n * sizeof(float)));
|
||||
|
||||
if (ds4_mmq_q2_K_moe(d_raw, d_x, d_ids, d_out_raw, M, K, n_tokens, E, 1, 0) != 0) {
|
||||
fprintf(stderr, "q2k raw moe failed\n"); exit(1);
|
||||
}
|
||||
if (ds4_mmq_q2_K_moe_soa(d_soa, d_x, d_ids, d_out_soa, M, K, n_tokens, E, 1, 0) != 0) {
|
||||
fprintf(stderr, "q2k soa moe failed\n"); exit(1);
|
||||
}
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
compare_outputs("q2k down W4096", d_out_raw, d_out_soa, out_n);
|
||||
|
||||
const float ms_raw = time_ms([&] {
|
||||
ds4_mmq_q2_K_moe(d_raw, d_x, d_ids, d_out_raw, M, K, n_tokens, E, 1, 0);
|
||||
}, reps);
|
||||
const float ms_soa = time_ms([&] {
|
||||
ds4_mmq_q2_K_moe_soa(d_soa, d_x, d_ids, d_out_soa, M, K, n_tokens, E, 1, 0);
|
||||
}, reps);
|
||||
const float ms_dr = time_ms([&] {
|
||||
ds4_mmq_q2_K_aligned_derepack(d_soa, d_raw, M, K, E, 0);
|
||||
}, reps);
|
||||
printf(" q2k down: raw %.3f ms soa %.3f ms (%+.1f%%) [derepack alone %.3f ms/launch]\n",
|
||||
ms_raw, ms_soa, 100.0 * (ms_soa - ms_raw) / ms_raw, ms_dr);
|
||||
|
||||
CHECK(cudaFree(d_soa)); CHECK(cudaFree(d_raw));
|
||||
CHECK(cudaFree(d_x)); CHECK(cudaFree(d_ids));
|
||||
CHECK(cudaFree(d_out_raw)); CHECK(cudaFree(d_out_soa));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// IQ2_XXS gate/up pair leg.
|
||||
// ---------------------------------------------------------------------------
|
||||
void run_iq2_leg(std::mt19937 &rng, int reps, int prompt_tokens) {
|
||||
const int E = 256, M = 2048, K = 4096;
|
||||
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);
|
||||
const uint64_t raw_bytes = nblk * 66ull;
|
||||
printf("IQ2_XXS gate/up leg: E=%d M=%d K=%d tokens=%dx%d (soa %.1f MiB x2, raw %.1f MiB x2)\n",
|
||||
E, M, K, n_tokens, n_used, soa_bytes / 1048576.0, raw_bytes / 1048576.0);
|
||||
|
||||
void *d_soa[2] = {nullptr, nullptr}, *d_raw[2] = {nullptr, nullptr};
|
||||
for (int t = 0; t < 2; ++t) {
|
||||
std::vector<uint8_t> h_soa(soa_bytes);
|
||||
fill_random(rng, h_soa.data(), soa_bytes);
|
||||
uint16_t *dq = (uint16_t *)h_soa.data();
|
||||
for (uint64_t i = 0; i < nblk; ++i) dq[i] = sane_half(rng);
|
||||
CHECK(cudaMalloc(&d_soa[t], soa_bytes));
|
||||
CHECK(cudaMalloc(&d_raw[t], raw_bytes));
|
||||
CHECK(cudaMemcpy(d_soa[t], h_soa.data(), soa_bytes, cudaMemcpyHostToDevice));
|
||||
if (ds4_mmq_iq2_xxs_aligned_derepack(d_soa[t], d_raw[t], M, K, E, 0) != 0) {
|
||||
fprintf(stderr, "iq2 derepack failed\n"); exit(1);
|
||||
}
|
||||
}
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
|
||||
float *d_x = device_random_floats(rng, (size_t)n_tokens * K);
|
||||
// Production routing: n_used DISTINCT experts per token.
|
||||
std::vector<int32_t> h_ids((size_t)n_tokens * n_used);
|
||||
for (int i = 0; i < n_tokens; ++i) {
|
||||
int picked[6];
|
||||
for (int j = 0; j < n_used; ++j) {
|
||||
int e;
|
||||
bool dup;
|
||||
do {
|
||||
e = (int)(rng() % E);
|
||||
dup = false;
|
||||
for (int q = 0; q < j; ++q) dup |= (picked[q] == e);
|
||||
} while (dup);
|
||||
picked[j] = e;
|
||||
h_ids[(size_t)i * n_used + j] = e;
|
||||
}
|
||||
}
|
||||
int32_t *d_ids = nullptr;
|
||||
CHECK(cudaMalloc(&d_ids, h_ids.size() * sizeof(int32_t)));
|
||||
CHECK(cudaMemcpy(d_ids, h_ids.data(), h_ids.size() * sizeof(int32_t), cudaMemcpyHostToDevice));
|
||||
|
||||
const size_t out_n = (size_t)M * n_tokens * n_used;
|
||||
float *d_out[4] = {nullptr, nullptr, nullptr, nullptr}; // raw a/b, soa a/b
|
||||
for (int i = 0; i < 4; ++i) CHECK(cudaMalloc(&d_out[i], out_n * sizeof(float)));
|
||||
|
||||
if (ds4_mmq_iq2_xxs_moe_pair(d_raw[0], d_raw[1], d_x, d_ids, d_out[0], d_out[1],
|
||||
M, K, n_tokens, E, n_used, 0) != 0) {
|
||||
fprintf(stderr, "iq2 raw pair failed\n"); exit(1);
|
||||
}
|
||||
if (ds4_mmq_iq2_xxs_moe_pair_soa(d_soa[0], d_soa[1], d_x, d_ids, d_out[2], d_out[3],
|
||||
M, K, n_tokens, E, n_used, 0) != 0) {
|
||||
fprintf(stderr, "iq2 soa pair failed\n"); exit(1);
|
||||
}
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
compare_outputs("iq2 gate W4096", d_out[0], d_out[2], out_n);
|
||||
compare_outputs("iq2 up W4096", d_out[1], d_out[3], out_n);
|
||||
|
||||
const float ms_raw = time_ms([&] {
|
||||
ds4_mmq_iq2_xxs_moe_pair(d_raw[0], d_raw[1], d_x, d_ids, d_out[0], d_out[1],
|
||||
M, K, n_tokens, E, n_used, 0);
|
||||
}, reps);
|
||||
const float ms_soa = time_ms([&] {
|
||||
ds4_mmq_iq2_xxs_moe_pair_soa(d_soa[0], d_soa[1], d_x, d_ids, d_out[2], d_out[3],
|
||||
M, K, n_tokens, E, n_used, 0);
|
||||
}, reps);
|
||||
const float ms_dr = time_ms([&] {
|
||||
ds4_mmq_iq2_xxs_aligned_derepack(d_soa[0], d_raw[0], M, K, E, 0);
|
||||
}, reps);
|
||||
printf(" iq2 pair: raw %.3f ms soa %.3f ms (%+.1f%%) [derepack alone %.3f ms/launch, x2 tensors]\n",
|
||||
ms_raw, ms_soa, 100.0 * (ms_soa - ms_raw) / ms_raw, ms_dr);
|
||||
|
||||
for (int t = 0; t < 2; ++t) { CHECK(cudaFree(d_soa[t])); CHECK(cudaFree(d_raw[t])); }
|
||||
CHECK(cudaFree(d_x)); CHECK(cudaFree(d_ids));
|
||||
for (int i = 0; i < 4; ++i) CHECK(cudaFree(d_out[i]));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
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, 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;
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
#pragma once
|
||||
#include "common.cuh"
|
||||
|
||||
#define CUDA_NEG_BLOCK_SIZE 256
|
||||
#define CUDA_STEP_BLOCK_SIZE 256
|
||||
#define CUDA_GELU_BLOCK_SIZE 256
|
||||
#define CUDA_SILU_BLOCK_SIZE 256
|
||||
#define CUDA_SILU_BACK_BLOCK_SIZE 256
|
||||
#define CUDA_TANH_BLOCK_SIZE 256
|
||||
#define CUDA_RELU_BLOCK_SIZE 256
|
||||
#define CUDA_SIGMOID_BLOCK_SIZE 256
|
||||
#define CUDA_HARDSIGMOID_BLOCK_SIZE 256
|
||||
#define CUDA_EXP_BLOCK_SIZE 256
|
||||
#define CUDA_HARDSWISH_BLOCK_SIZE 256
|
||||
#define CUDA_SQR_BLOCK_SIZE 256
|
||||
#define CUDA_SQRT_BLOCK_SIZE 256
|
||||
#define CUDA_SIN_BLOCK_SIZE 256
|
||||
#define CUDA_COS_BLOCK_SIZE 256
|
||||
#define CUDA_GLU_BLOCK_SIZE 256
|
||||
#define CUDA_XIELU_BLOCK_SIZE 256
|
||||
|
||||
void ggml_cuda_op_abs(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_sgn(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_neg(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_step(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_gelu(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_silu(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_silu_back(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_gelu_erf(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_gelu_quick(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_tanh(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_relu(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_sigmoid(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_hardsigmoid(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_exp(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_hardswish(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_leaky_relu(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_sqr(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_sqrt(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_sin(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_cos(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_log(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_expm1(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_softplus(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_elu(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_floor(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_ceil(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_round(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_trunc(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_reglu(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_geglu(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_swiglu(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_swiglu_oai(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_geglu_erf(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_geglu_quick(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_xielu(ggml_backend_cuda_context & ctx, ggml_tensor * dst);
|
||||
|
||||
void ggml_cuda_op_unary_mul(ggml_backend_cuda_context & ctx, ggml_tensor * unary_node, ggml_tensor * mul_node);
|
||||
|
||||
void ggml_cuda_op_relu_sqr(ggml_backend_cuda_context & ctx, ggml_tensor * relu_node, ggml_tensor * sqr_node);
|
||||
|
||||
__device__ __forceinline__ float ggml_cuda_op_silu_single(float x) {
|
||||
return x / (1.0f + expf(-x));
|
||||
}
|
||||
|
||||
__device__ __forceinline__ float ggml_cuda_op_gelu_single(float x) {
|
||||
const float GELU_COEF_A = 0.044715f;
|
||||
const float SQRT_2_OVER_PI = 0.79788456080286535587989211986876f;
|
||||
|
||||
return 0.5f * x * (1.0f + tanhf(SQRT_2_OVER_PI * x * (1.0f + GELU_COEF_A * x * x)));
|
||||
}
|
||||
|
||||
__device__ __forceinline__ float ggml_cuda_op_swiglu_oai_single(float x, float g, float alpha = 1.702f, float limit = 7.0f) {
|
||||
x = fminf(x, limit);
|
||||
g = fmaxf(fminf(g, limit), -limit);
|
||||
|
||||
float out_glu = x / (1.0f + expf(-x * alpha));
|
||||
out_glu = out_glu * (1.0f + g);
|
||||
return out_glu;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <cuda_runtime.h>
|
||||
#include <cuda.h>
|
||||
#include <cublas_v2.h>
|
||||
#include <cuda_bf16.h>
|
||||
#include <cuda_fp16.h>
|
||||
|
||||
#ifdef GGML_USE_NCCL
|
||||
#include <nccl.h>
|
||||
#endif // GGML_USE_NCCL
|
||||
|
||||
#if CUDART_VERSION >= 11080
|
||||
#include <cuda_fp8.h>
|
||||
#define FP8_AVAILABLE
|
||||
#endif // CUDART_VERSION >= 11080
|
||||
|
||||
#if CUDART_VERSION >= 12080
|
||||
#include <cuda_fp4.h>
|
||||
#endif // CUDART_VERSION >= 12080
|
||||
|
||||
#if CUDART_VERSION < 11020
|
||||
#define CU_DEVICE_ATTRIBUTE_VIRTUAL_MEMORY_MANAGEMENT_SUPPORTED CU_DEVICE_ATTRIBUTE_VIRTUAL_ADDRESS_MANAGEMENT_SUPPORTED
|
||||
#define CUBLAS_TF32_TENSOR_OP_MATH CUBLAS_TENSOR_OP_MATH
|
||||
#define CUBLAS_COMPUTE_16F CUDA_R_16F
|
||||
#define CUBLAS_COMPUTE_32F CUDA_R_32F
|
||||
#define cublasComputeType_t cudaDataType_t
|
||||
#endif // CUDART_VERSION < 11020
|
||||
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;
|
||||
}
|
||||
+61
-3
@@ -1,8 +1,9 @@
|
||||
# Directional Steering
|
||||
|
||||
Directional steering is a runtime activation edit for DS4. A steering file is a
|
||||
flat `f32` matrix with one normalized 4096-wide direction per layer. During
|
||||
inference, ds4 can apply the edit after attention outputs, FFN outputs, or both:
|
||||
flat `f32` matrix with one normalized hidden-width direction per normal
|
||||
transformer layer. During inference, ds4 can apply the edit after attention
|
||||
outputs, FFN outputs, or both:
|
||||
|
||||
```text
|
||||
y = y - scale * direction[layer] * dot(direction[layer], y)
|
||||
@@ -11,10 +12,20 @@ y = y - scale * direction[layer] * dot(direction[layer], y)
|
||||
Positive scale removes the represented direction. Negative scale amplifies it.
|
||||
With no steering file or zero scales, ds4 follows the normal inference path.
|
||||
|
||||
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.
|
||||
|
||||
## Runtime Options
|
||||
|
||||
```text
|
||||
--dir-steering-file FILE load a 43 x 4096 f32 direction file
|
||||
--dir-steering-file FILE load one f32 direction per normal model layer
|
||||
--dir-steering-ffn F apply steering after FFN outputs; default is 1 when a file is provided
|
||||
--dir-steering-attn F apply steering after attention outputs; default is 0
|
||||
```
|
||||
@@ -23,6 +34,27 @@ The FFN output is usually the best first target because it is late enough in
|
||||
each layer to represent behavior, style, and topic signals. Attention steering
|
||||
is available for experiments, but it can be more fragile.
|
||||
|
||||
## GLM 5.3 Example
|
||||
|
||||
Build a GLM 5.3 direction from paired target and control prompt lists:
|
||||
|
||||
```sh
|
||||
python3 dir-steering/tools/build_direction.py \
|
||||
--profile glm-5.3-flash \
|
||||
--ds4 ./ds4 \
|
||||
--model gguf/GLM-5.3-Flash-Q2.gguf \
|
||||
--good-file /path/to/target-prompts.txt \
|
||||
--bad-file /path/to/control-prompts.txt \
|
||||
--out dir-steering/out/glm53-direction.json \
|
||||
--component ffn_out \
|
||||
--ctx 512
|
||||
```
|
||||
|
||||
Generated `.f32` vectors are local artifacts and are not stored in the
|
||||
repository. GLM 5.3 steering works with `--mtp`, `ds4-server`, native session
|
||||
batching, and two-Mac tensor parallelism. For tensor parallelism, pass the same
|
||||
steering file and scales to both the worker and coordinator.
|
||||
|
||||
## Verbosity Example
|
||||
|
||||
The bundled example builds a style direction from 100 paired prompts. Each pair
|
||||
@@ -39,6 +71,7 @@ Build the vector:
|
||||
|
||||
```sh
|
||||
python3 dir-steering/tools/build_direction.py \
|
||||
--profile deepseek-v4-flash \
|
||||
--ds4 ./ds4 \
|
||||
--model ds4flash.gguf \
|
||||
--good-file dir-steering/examples/succinct.txt \
|
||||
@@ -149,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.
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build a DS4 directional-steering vector from paired prompt sets.
|
||||
|
||||
The extractor asks ds4 to dump one 4096-wide activation row per layer, averages
|
||||
the target and control rows, and writes a flat f32 file with 43 layer vectors.
|
||||
The extractor asks ds4 to dump one hidden activation row per normal transformer
|
||||
layer, averages the target and control rows, and writes a flat f32 file.
|
||||
At runtime ds4 applies:
|
||||
|
||||
y = y - scale * direction[layer] * dot(direction[layer], y)
|
||||
@@ -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
|
||||
@@ -20,15 +22,10 @@ import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
N_LAYER = 43
|
||||
N_EMBD = 4096
|
||||
|
||||
SPECIALS = {
|
||||
"bos": "<|begin▁of▁sentence|>",
|
||||
"user": "<|User|>",
|
||||
"assistant": "<|Assistant|>",
|
||||
"think": "<think>",
|
||||
"nothink": "</think>",
|
||||
MODEL_PROFILES = {
|
||||
"deepseek-v4-flash": (43, 4096),
|
||||
"glm-5.3-flash": (45, 4096),
|
||||
"qwen3.8-flash-next": (48, 2560),
|
||||
}
|
||||
|
||||
|
||||
@@ -45,20 +42,6 @@ def read_prompt_file(path: Path) -> list[str]:
|
||||
return prompts
|
||||
|
||||
|
||||
def render_ds4_prompt(system: str, user: str, think: bool) -> str:
|
||||
"""Render the minimal DS4 chat prefix used for activation capture."""
|
||||
pieces = [SPECIALS["bos"]]
|
||||
if system:
|
||||
pieces.append(system)
|
||||
pieces += [
|
||||
SPECIALS["user"],
|
||||
user,
|
||||
SPECIALS["assistant"],
|
||||
SPECIALS["think"] if think else SPECIALS["nothink"],
|
||||
]
|
||||
return "".join(pieces)
|
||||
|
||||
|
||||
def normalize(v: list[float]) -> list[float]:
|
||||
n2 = sum(x * x for x in v)
|
||||
if n2 <= 0.0:
|
||||
@@ -79,17 +62,20 @@ def run_capture(
|
||||
think: bool,
|
||||
ctx: int,
|
||||
component: str,
|
||||
n_layer: int,
|
||||
n_embd: int,
|
||||
work: Path,
|
||||
) -> list[list[float]]:
|
||||
"""Run ds4 once and return the last prompt-row dump for every layer."""
|
||||
prompt_path = work / "prompt.txt"
|
||||
prompt_path.write_text(render_ds4_prompt(system, prompt, think), encoding="utf-8")
|
||||
prompt_path.write_text(prompt, encoding="utf-8")
|
||||
dump_prefix = work / "dump"
|
||||
|
||||
env = os.environ.copy()
|
||||
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),
|
||||
@@ -97,24 +83,31 @@ def run_capture(
|
||||
"--ctx", str(ctx),
|
||||
"--prompt-file", str(prompt_path),
|
||||
"-n", "1",
|
||||
"--prefill-chunk", str(max(ctx, 1024)),
|
||||
]
|
||||
subprocess.run(cmd, cwd=ds4.parent, env=env, check=True,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
||||
if system:
|
||||
cmd += ["--system", system]
|
||||
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:
|
||||
error = result.stderr.decode("utf-8", errors="replace")
|
||||
raise RuntimeError(f"ds4 activation capture failed:\n{error}")
|
||||
|
||||
rows: list[list[float]] = []
|
||||
for layer in range(N_LAYER):
|
||||
for layer in range(n_layer):
|
||||
path = work / f"dump_{component}-{layer}_pos0.bin"
|
||||
data = array.array("f")
|
||||
with path.open("rb") as f:
|
||||
data.fromfile(f, path.stat().st_size // 4)
|
||||
if len(data) < N_EMBD or len(data) % N_EMBD != 0:
|
||||
if len(data) < n_embd or len(data) % n_embd != 0:
|
||||
raise RuntimeError(f"bad dump shape for {path}: {len(data)} floats")
|
||||
rows.append(list(data[-N_EMBD:]))
|
||||
rows.append(list(data[-n_embd:]))
|
||||
return rows
|
||||
|
||||
|
||||
def add_rows(total: list[list[float]], rows: list[list[float]]) -> None:
|
||||
for layer in range(N_LAYER):
|
||||
def add_rows(total: list[list[float]], rows: list[list[float]], n_layer: int) -> None:
|
||||
for layer in range(n_layer):
|
||||
dst = total[layer]
|
||||
src = rows[layer]
|
||||
for i, value in enumerate(src):
|
||||
@@ -125,17 +118,22 @@ def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--ds4", default="./ds4", help="path to the ds4 CLI")
|
||||
ap.add_argument("--model", default="ds4flash.gguf", help="GGUF model path")
|
||||
ap.add_argument("--profile", choices=MODEL_PROFILES,
|
||||
default="deepseek-v4-flash",
|
||||
help="model shape and chat-template profile")
|
||||
ap.add_argument("--good-file", required=True,
|
||||
help="desired/target prompts, one per line")
|
||||
ap.add_argument("--bad-file", required=True,
|
||||
help="contrast/control prompts, one per line")
|
||||
ap.add_argument("--out", default="dir-steering/out/direction.json",
|
||||
help="metadata JSON path; .f32 is written next to it")
|
||||
ap.add_argument("--source", default="",
|
||||
help="optional corpus source or provenance note")
|
||||
ap.add_argument("--ctx", type=int, default=512)
|
||||
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",
|
||||
@@ -145,16 +143,18 @@ def main() -> None:
|
||||
args = ap.parse_args()
|
||||
|
||||
ds4 = Path(args.ds4).resolve()
|
||||
model = Path(args.model).resolve()
|
||||
model_arg = Path(args.model)
|
||||
model = model_arg.resolve()
|
||||
n_layer, n_embd = MODEL_PROFILES[args.profile]
|
||||
good_prompts = read_prompt_file(Path(args.good_file))
|
||||
bad_prompts = read_prompt_file(Path(args.bad_file))
|
||||
n = min(len(good_prompts), len(bad_prompts))
|
||||
good_prompts = good_prompts[:n]
|
||||
bad_prompts = bad_prompts[:n]
|
||||
|
||||
good_sum = [[0.0] * N_EMBD for _ in range(N_LAYER)]
|
||||
bad_sum = [[0.0] * N_EMBD for _ in range(N_LAYER)]
|
||||
pair_sum = [[0.0] * N_EMBD for _ in range(N_LAYER)]
|
||||
good_sum = [[0.0] * n_embd for _ in range(n_layer)]
|
||||
bad_sum = [[0.0] * n_embd for _ in range(n_layer)]
|
||||
pair_sum = [[0.0] * n_embd for _ in range(n_layer)]
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="ds4-dir-steer-") as td:
|
||||
root = Path(td)
|
||||
@@ -165,22 +165,22 @@ def main() -> None:
|
||||
gw.mkdir()
|
||||
bw.mkdir()
|
||||
good_rows = run_capture(ds4, model, good, args.system, args.think,
|
||||
args.ctx, args.component, gw)
|
||||
args.ctx, args.component, n_layer, n_embd, gw)
|
||||
bad_rows = run_capture(ds4, model, bad, args.system, args.think,
|
||||
args.ctx, args.component, bw)
|
||||
add_rows(good_sum, good_rows)
|
||||
add_rows(bad_sum, bad_rows)
|
||||
args.ctx, args.component, n_layer, n_embd, bw)
|
||||
add_rows(good_sum, good_rows, n_layer)
|
||||
add_rows(bad_sum, bad_rows, n_layer)
|
||||
if args.pair_normalize:
|
||||
for layer in range(N_LAYER):
|
||||
for layer in range(n_layer):
|
||||
diff = normalize([
|
||||
good_rows[layer][j] - bad_rows[layer][j]
|
||||
for j in range(N_EMBD)
|
||||
for j in range(n_embd)
|
||||
])
|
||||
for j, value in enumerate(diff):
|
||||
pair_sum[layer][j] += value
|
||||
|
||||
layers = []
|
||||
for layer in range(N_LAYER):
|
||||
for layer in range(n_layer):
|
||||
good_mean = [x / n for x in good_sum[layer]]
|
||||
bad_mean = [x / n for x in bad_sum[layer]]
|
||||
if args.pair_normalize:
|
||||
@@ -188,14 +188,14 @@ def main() -> None:
|
||||
else:
|
||||
direction = normalize([
|
||||
good_mean[i] - bad_mean[i]
|
||||
for i in range(N_EMBD)
|
||||
for i in range(n_embd)
|
||||
])
|
||||
if not args.no_orthogonalize:
|
||||
base = normalize(bad_mean)
|
||||
projection = dot(direction, base)
|
||||
direction = normalize([
|
||||
direction[i] - projection * base[i]
|
||||
for i in range(N_EMBD)
|
||||
for i in range(n_embd)
|
||||
])
|
||||
layers.append(direction)
|
||||
|
||||
@@ -203,14 +203,16 @@ def main() -> None:
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
payload = {
|
||||
"format": "ds4-directional-steering-v1",
|
||||
"shape": [N_LAYER, N_EMBD],
|
||||
"shape": [n_layer, n_embd],
|
||||
"profile": args.profile,
|
||||
"component": args.component,
|
||||
"thinking": bool(args.think),
|
||||
"pair_normalize": bool(args.pair_normalize),
|
||||
"orthogonalize_control_mean": not args.no_orthogonalize,
|
||||
"good_file": str(Path(args.good_file)),
|
||||
"bad_file": str(Path(args.bad_file)),
|
||||
"model": str(model),
|
||||
"model": str(model_arg),
|
||||
"source": args.source,
|
||||
"note": "runtime positive scale suppresses this direction; negative scale amplifies it",
|
||||
}
|
||||
out.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
||||
|
||||
+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.
|
||||
+503
-34
@@ -1,23 +1,45 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
DEEPSEEK_REPO="antirez/deepseek-v4-gguf"
|
||||
GLM_UNSLOTH_REPO="unsloth/GLM-5.2-GGUF"
|
||||
GLM_ANTIREZ_REPO="antirez/GLM-5.2-GGUF"
|
||||
REPO=$DEEPSEEK_REPO
|
||||
Q2_IMATRIX_FILE="DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix.gguf"
|
||||
Q4_IMATRIX_FILE="DeepSeek-V4-Flash-Q4KExperts-F16HC-F16Compressor-F16Indexer-Q8Attn-Q8Shared-Q8Out-chat-v2-imatrix.gguf"
|
||||
Q2_Q4_IMATRIX_FILE="DeepSeek-V4-Flash-Layers37-42Q4KExperts-OtherExpertLayersIQ2XXSGateUp-Q2KDown-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix-fixed.gguf"
|
||||
PRO_Q2_IMATRIX_FILE="DeepSeek-V4-Pro-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-Instruct-imatrix.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"
|
||||
DS4F_Q2_Q4_FILE="DeepSeek-V4-Flash-Layers37-42Q4KExperts-OtherExpertLayersIQ2XXSGateUp-Q2KDown-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix-fixed-0731.gguf"
|
||||
PRO_Q2_IMATRIX_FILE="DeepSeek-V4-Pro-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-Instruct-imatrix-0813.gguf"
|
||||
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"
|
||||
MTP_FILE="DeepSeek-V4-Flash-MTP-Q4K-Q8_0-F32.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"}
|
||||
@@ -32,38 +54,106 @@ usage() {
|
||||
DwarfStar GGUF downloader
|
||||
|
||||
Usage:
|
||||
./download_model.sh q2-imatrix [--token TOKEN]
|
||||
./download_model.sh q2-q4-imatrix [--token TOKEN]
|
||||
./download_model.sh q4-imatrix [--token TOKEN]
|
||||
./download_model.sh ds4f-q2 [--token TOKEN]
|
||||
./download_model.sh ds4f-q2-q4 [--token TOKEN]
|
||||
./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]
|
||||
./download_model.sh pro-q4-split [--token TOKEN]
|
||||
./download_model.sh mtp [--token TOKEN]
|
||||
./download_model.sh glm-unsloth-q4 [--token TOKEN]
|
||||
./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:
|
||||
|
||||
q2-imatrix
|
||||
ds4f-q2
|
||||
2-bit routed experts, about 81 GB on disk.
|
||||
Recommended model for 96 and 128 GB RAM machines.
|
||||
|
||||
q2-q4-imatrix
|
||||
ds4f-q2-q4
|
||||
Mixed Flash quant: mostly q2 routed experts, with the last 6 layers
|
||||
using q4 routed experts. About 98 GB on disk. Good for higher
|
||||
quality inference for 128 GB MacBooks. Works on DGX Spark but loading
|
||||
may struggle compared to q2-imatrix.
|
||||
may struggle compared to ds4f-q2.
|
||||
|
||||
q4-imatrix
|
||||
ds4f-q4
|
||||
4-bit routed experts, about 153 GB on disk.
|
||||
Recommended model for machines with 256 GB RAM or more.
|
||||
|
||||
ds4f-mxfp4
|
||||
Native DeepSeek V4 Flash MXFP4 routed experts, about 156 GB on disk.
|
||||
Supported by Metal and CUDA; Blackwell uses FP4 tensor cores for batched
|
||||
expert work, while CUDA decode keeps Q8 activations.
|
||||
|
||||
ds4f-dspark
|
||||
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 q2 imatrix quant, as a single GGUF file. About 430 GB
|
||||
on disk; intended for 512 GB RAM machines.
|
||||
DeepSeek V4 PRO 0813 q2 imatrix quant, as a single GGUF file. About
|
||||
430 GB on disk; intended for 512 GB RAM machines.
|
||||
|
||||
pro-q4-layers00-30
|
||||
First half of the DeepSeek V4 PRO Q4 routed-expert quant, layers 0..30.
|
||||
@@ -78,10 +168,6 @@ Targets:
|
||||
Downloads both PRO Q4 split files into the download directory. About
|
||||
838 GB total. This target does not update ./ds4flash.gguf.
|
||||
|
||||
mtp Optional speculative decoding component, about 3.5 GB on disk.
|
||||
It is useful with q2-imatrix, q2-q4-imatrix, and q4-imatrix, but must be
|
||||
enabled explicitly with --mtp when running ds4 or ds4-server.
|
||||
|
||||
glm-unsloth-q4
|
||||
GLM 5.2 Unsloth UD-Q4_K_XL quant from unsloth/GLM-5.2-GGUF.
|
||||
Downloads all 11 shards and links ./ds4flash.gguf to the first shard.
|
||||
@@ -98,6 +184,46 @@ 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.
|
||||
|
||||
glm53-q4
|
||||
GLM 5.3 Flash Q4_K GGUF, about 178 GiB on disk. Intended for two-Mac
|
||||
tensor parallelism or single-Mac SSD streaming.
|
||||
|
||||
glm53-fp8
|
||||
Text-only GLM 5.3 Flash native FP8 GGUF, about 305 GiB on disk. It
|
||||
preserves the released weights without requantization. DwarfStar
|
||||
inference support for this paired FP8-code/scale format is pending.
|
||||
|
||||
glm53-vision
|
||||
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.
|
||||
@@ -113,10 +239,13 @@ Then the default commands work:
|
||||
./ds4 -p "Hello"
|
||||
./ds4-server --ctx 100000
|
||||
|
||||
After downloading mtp, enable it explicitly, for example:
|
||||
./ds4 --mtp <download directory>/$MTP_FILE --mtp-draft 2
|
||||
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
|
||||
@@ -135,9 +264,60 @@ FORCE_HF_DOWNLOAD=0
|
||||
FLATTEN_DOWNLOADS=0
|
||||
|
||||
case "$MODEL" in
|
||||
q2-imatrix) MODEL_FILE=$Q2_IMATRIX_FILE ;;
|
||||
q2-q4-imatrix) MODEL_FILE=$Q2_Q4_IMATRIX_FILE ;;
|
||||
q4-imatrix) MODEL_FILE=$Q4_IMATRIX_FILE ;;
|
||||
ds4f-q2) MODEL_FILE=$DS4F_Q2_FILE ;;
|
||||
ds4f-q2-q4) MODEL_FILE=$DS4F_Q2_Q4_FILE ;;
|
||||
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 ;;
|
||||
@@ -145,7 +325,6 @@ case "$MODEL" in
|
||||
MODEL_FILES="$PRO_Q4_LAYERS00_30_FILE $PRO_Q4_LAYERS31_OUTPUT_FILE"
|
||||
LINK_MODEL=0
|
||||
;;
|
||||
mtp) MODEL_FILE=$MTP_FILE; LINK_MODEL=0 ;;
|
||||
glm-unsloth-q4)
|
||||
REPO=$GLM_UNSLOTH_REPO
|
||||
MODEL_FILE=$GLM_UNSLOTH_Q4_FIRST_FILE
|
||||
@@ -171,6 +350,49 @@ 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
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
;;
|
||||
glm53-q4)
|
||||
REPO=$GLM53_REPO
|
||||
MODEL_FILE=$GLM53_Q4_FILE
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
;;
|
||||
glm53-fp8)
|
||||
REPO=$GLM53_REPO
|
||||
MODEL_FILE=$GLM53_FP8_FILE
|
||||
FORCE_HF_DOWNLOAD=1
|
||||
LINK_MODEL=0
|
||||
;;
|
||||
glm53-vision)
|
||||
REPO=$GLM53_REPO
|
||||
MODEL_FILE=$GLM53_VISION_FILE
|
||||
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
|
||||
@@ -224,6 +446,12 @@ find_hf_command() {
|
||||
printf '%s\n' hf
|
||||
return 0
|
||||
fi
|
||||
for dir in "$HOME"/Library/Python/*/bin "$HOME"/.local/bin; do
|
||||
if [ -x "$dir/hf" ]; then
|
||||
printf '%s\n' "$dir/hf"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -235,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")
|
||||
@@ -245,6 +566,7 @@ download_one_hf() {
|
||||
mkdir -p "$(dirname "$out")"
|
||||
|
||||
if [ -s "$out" ]; then
|
||||
verify_download "$file" "$out"
|
||||
echo "Already downloaded: $out"
|
||||
return
|
||||
fi
|
||||
@@ -252,7 +574,7 @@ download_one_hf() {
|
||||
if [ -e "$part" ]; then
|
||||
echo "Found curl partial download: $part" >&2
|
||||
echo "The Hugging Face downloader cannot resume curl .part files." >&2
|
||||
echo "Move or remove that partial download before retrying this PRO target." >&2
|
||||
echo "Move or remove that partial download before retrying this target." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -284,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() {
|
||||
@@ -325,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
|
||||
@@ -333,11 +787,14 @@ else
|
||||
download_one "$MODEL_FILE"
|
||||
fi
|
||||
|
||||
if [ "$MODEL" = "mtp" ]; then
|
||||
if [ "$MODEL" = "ds4f-dspark" ]; then
|
||||
echo
|
||||
echo "MTP is an optional component for q2-imatrix, q2-q4-imatrix, and q4-imatrix."
|
||||
echo "Enable it explicitly, for example:"
|
||||
echo " ./ds4 --mtp $OUT_DIR/$MTP_FILE --mtp-draft 2"
|
||||
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,"
|
||||
@@ -350,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,
|
||||
@@ -91,15 +95,50 @@ typedef struct {
|
||||
bool debug;
|
||||
} ds4_distributed_options;
|
||||
|
||||
/* Tensor parallelism: two identical machines run the model in lockstep and
|
||||
* split the heavy per-layer matvecs, exchanging partial sums at gates inside
|
||||
* the graph (see misc/METAL_TENSOR_PARALLELISM.md). Each rank keeps one
|
||||
* contiguous half of the routed experts resident; dense and shared weights
|
||||
* remain replicated. The leader owns prompt/sampling and listens; the worker
|
||||
* dials in and mirrors every session sync/eval. */
|
||||
typedef enum {
|
||||
DS4_TP_NONE = 0,
|
||||
DS4_TP_LEADER,
|
||||
DS4_TP_WORKER,
|
||||
} ds4_tp_role;
|
||||
|
||||
typedef enum {
|
||||
DS4_TP_TRANSPORT_AUTO = 0,
|
||||
DS4_TP_TRANSPORT_RDMA,
|
||||
DS4_TP_TRANSPORT_TCP,
|
||||
} ds4_tp_transport;
|
||||
|
||||
typedef struct {
|
||||
ds4_tp_role role;
|
||||
bool requested; /* --tensor-parallel with shared role options */
|
||||
const char *listen_host; /* leader listens here for the worker */
|
||||
int listen_port;
|
||||
const char *leader_host; /* worker dials the leader */
|
||||
int leader_port;
|
||||
ds4_tp_transport transport;
|
||||
const char *rdma_device;
|
||||
int rdma_gid_index;
|
||||
bool rdma_gid_index_set;
|
||||
bool glm_token_prefill;
|
||||
int debug_hash; /* cross-check hidden state every N tokens */
|
||||
} ds4_tp_options;
|
||||
|
||||
typedef struct {
|
||||
const char *model_path;
|
||||
const char *mtp_path;
|
||||
const char *vision_path;
|
||||
ds4_backend backend;
|
||||
int n_threads;
|
||||
int context_size;
|
||||
uint32_t prefill_chunk;
|
||||
int mtp_draft_tokens;
|
||||
float mtp_margin;
|
||||
float dspark_confidence_threshold;
|
||||
const char *directional_steering_file;
|
||||
const char *expert_profile_path;
|
||||
float directional_steering_attn;
|
||||
@@ -112,10 +151,23 @@ typedef struct {
|
||||
uint64_t simulate_used_memory_bytes;
|
||||
bool warm_weights;
|
||||
bool quality;
|
||||
bool glm_mtp;
|
||||
bool glm_mtp_timing;
|
||||
bool dspark;
|
||||
bool dspark_strict;
|
||||
bool dspark_exact_sampling;
|
||||
bool dspark_confidence_threshold_set;
|
||||
bool cuda_tensor_parallel;
|
||||
bool ssd_streaming;
|
||||
bool ssd_streaming_cold;
|
||||
bool ssd_streaming_full_layers_set;
|
||||
bool inspect_only;
|
||||
/* Multi-GPU placement uses this to price per-layer KV storage. */
|
||||
int placement_ctx_hint;
|
||||
/* Number of independently allocated session graphs/caches to reserve. */
|
||||
int placement_session_count_hint;
|
||||
/* Server batch mode serializes execution and can share prefill scratch. */
|
||||
bool share_session_prefill_workspace;
|
||||
bool first_token_test;
|
||||
bool metal_graph_test;
|
||||
bool load_slice;
|
||||
@@ -123,8 +175,27 @@ typedef struct {
|
||||
uint32_t load_layer_end;
|
||||
bool load_output;
|
||||
ds4_distributed_options distributed;
|
||||
ds4_tp_options tp;
|
||||
} ds4_engine_options;
|
||||
|
||||
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;
|
||||
uint32_t content_height;
|
||||
uint8_t fingerprint[32];
|
||||
} ds4_vision_embedding;
|
||||
|
||||
typedef struct {
|
||||
uint32_t token_start;
|
||||
ds4_vision_embedding embedding;
|
||||
} ds4_vision_span;
|
||||
|
||||
typedef void (*ds4_token_emit_fn)(void *ud, int token);
|
||||
typedef void (*ds4_generation_done_fn)(void *ud);
|
||||
|
||||
@@ -150,15 +221,78 @@ typedef struct {
|
||||
} ds4_session_payload_file;
|
||||
|
||||
int ds4_engine_open(ds4_engine **out, const ds4_engine_options *opt);
|
||||
|
||||
/* Multi-GPU pipeline-parallel entry point (wave 2).
|
||||
*
|
||||
* Accepts an optional ds4_gpu_config (defined in ds4_gpu_mgpu.h) that
|
||||
* lets callers describe a multi-GPU placement target. Passing NULL is
|
||||
* back-compatible with ds4_engine_open and produces identical engine
|
||||
* state — bit-equivalent execution at runtime.
|
||||
*
|
||||
* When a non-NULL config is supplied AND the computed placement spans
|
||||
* more than one tier (either multiple GPUs or any CPU-spill), this
|
||||
* wave-2 implementation prints the layout and refuses to open: full
|
||||
* multi-tier execution wiring lands in a follow-up task
|
||||
* (mgpu-graph-session-execution). Callers receive a non-zero return
|
||||
* and a documented stderr notice. */
|
||||
/* ds4_gpu_config is declared in ds4_gpu_mgpu.h, which callers should
|
||||
* include separately. We forward-declare it here so this header can be
|
||||
* used as-is (callers passing NULL don't need the struct definition). */
|
||||
struct ds4_gpu_config;
|
||||
int ds4_engine_create_with_gpu_config(ds4_engine **out,
|
||||
const ds4_engine_options *opt,
|
||||
const struct ds4_gpu_config *gpu_cfg);
|
||||
void ds4_engine_close(ds4_engine *e);
|
||||
void ds4_engine_summary(ds4_engine *e);
|
||||
int ds4_engine_vocab_size(ds4_engine *e);
|
||||
uint32_t ds4_engine_prefill_chunk(ds4_engine *e);
|
||||
int ds4_engine_power(ds4_engine *e);
|
||||
int ds4_engine_set_power(ds4_engine *e, int power_percent);
|
||||
const char *ds4_engine_model_name(ds4_engine *e);
|
||||
int ds4_engine_layer_count(ds4_engine *e);
|
||||
/* Decode gate schedule for the TP transport; see ds4_tp_identity. */
|
||||
enum { DS4_TP_GATE_MASK_WORDS = 3 };
|
||||
void ds4_engine_tp_gate_schedule(ds4_engine *e,
|
||||
uint32_t *start,
|
||||
uint32_t *step,
|
||||
uint32_t *per_token,
|
||||
uint64_t mask[DS4_TP_GATE_MASK_WORDS]);
|
||||
uint32_t ds4_engine_layer_compress_ratio(ds4_engine *e, uint32_t layer);
|
||||
uint64_t ds4_engine_hidden_f32_values(ds4_engine *e);
|
||||
int ds4_engine_embd_dim(ds4_engine *e);
|
||||
uint64_t ds4_engine_model_bytes(ds4_engine *e);
|
||||
bool ds4_engine_has_vision(ds4_engine *e);
|
||||
int ds4_engine_vision_encode_file(ds4_engine *e,
|
||||
const char *path,
|
||||
ds4_vision_embedding *out,
|
||||
char *error,
|
||||
size_t error_cap);
|
||||
int ds4_engine_vision_encode_memory(ds4_engine *e,
|
||||
const uint8_t *encoded,
|
||||
size_t encoded_len,
|
||||
ds4_vision_embedding *out,
|
||||
char *error,
|
||||
size_t error_cap);
|
||||
void ds4_vision_embedding_free(ds4_vision_embedding *embedding);
|
||||
int ds4_prompt_append_vision(ds4_engine *e,
|
||||
ds4_tokens *tokens,
|
||||
ds4_vision_span *span,
|
||||
ds4_vision_embedding *embedding,
|
||||
char *error,
|
||||
size_t error_cap);
|
||||
/* Append one user or tool message whose text parts alternate with images.
|
||||
* text_parts must contain image_count + 1 entries. On success ownership of
|
||||
* each embedding is transferred to the corresponding output span. */
|
||||
int ds4_chat_append_multimodal_message(ds4_engine *e,
|
||||
ds4_tokens *tokens,
|
||||
const char *role,
|
||||
const char *const *text_parts,
|
||||
ds4_vision_embedding *embeddings,
|
||||
size_t image_count,
|
||||
ds4_vision_span *spans,
|
||||
char *error,
|
||||
size_t error_cap);
|
||||
int ds4_engine_tp_vocab_split(ds4_engine *e);
|
||||
bool ds4_engine_glm_layer_payload_bytes(ds4_engine *e,
|
||||
uint32_t layer,
|
||||
uint32_t full_live,
|
||||
@@ -172,8 +306,14 @@ bool ds4_engine_glm_layer_payload_bytes(ds4_engine *e,
|
||||
* Pro and later shapes must use nonzero ids. */
|
||||
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);
|
||||
@@ -205,10 +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);
|
||||
@@ -229,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);
|
||||
|
||||
@@ -242,18 +393,33 @@ bool ds4_token_is_stop_for_think_mode(ds4_engine *e,
|
||||
int ds4_token_user(ds4_engine *e);
|
||||
int ds4_token_assistant(ds4_engine *e);
|
||||
|
||||
/* Tensor-parallel binding: allocates the GPU gate slab, registers it with
|
||||
* the transport and arms the per-layer gate machinery. Call once, after
|
||||
* ds4_tp_create() and before any session work. Transport lifecycle stays
|
||||
* 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);
|
||||
int ds4_session_power(ds4_session *s);
|
||||
int ds4_session_set_power(ds4_session *s, int power_percent);
|
||||
float ds4_session_directional_steering_ffn(ds4_session *s);
|
||||
/* Change steering for future evaluation without rebuilding the existing KV
|
||||
* state. Live changes are currently limited to non-distributed sessions. */
|
||||
int ds4_session_set_directional_steering_ffn(ds4_session *s, float scale);
|
||||
bool ds4_session_is_distributed(ds4_session *s);
|
||||
void ds4_session_set_progress(ds4_session *s, ds4_session_progress_fn fn, void *ud);
|
||||
/* 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
|
||||
@@ -271,27 +437,175 @@ typedef enum {
|
||||
/* Synchronize the live session to a full prompt token prefix. If the current
|
||||
* checkpoint is a prefix, only the suffix is evaluated; otherwise the backend
|
||||
* state is refilled from scratch. */
|
||||
#define DS4_SESSION_SYNC_INTERRUPTED 2
|
||||
int ds4_session_sync(ds4_session *s, const ds4_tokens *prompt, char *err, size_t errlen);
|
||||
int ds4_session_sync_multimodal(ds4_session *s,
|
||||
const ds4_tokens *prompt,
|
||||
const ds4_vision_span *images,
|
||||
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,
|
||||
float *prob_scratch);
|
||||
int ds4_test_sampling_probabilities(const float *logits, uint32_t n_vocab,
|
||||
float temperature, int top_k,
|
||||
float top_p, float min_p, float *probs);
|
||||
int ds4_test_speculative_sample(const float *target_logits,
|
||||
const float *draft_logits,
|
||||
uint32_t n_vocab,
|
||||
float temperature,
|
||||
int top_k,
|
||||
float top_p,
|
||||
float min_p,
|
||||
uint64_t *rng,
|
||||
float *target_probs,
|
||||
float *draft_probs);
|
||||
int ds4_test_speculative_delta_sample(const float *target_logits,
|
||||
uint32_t n_vocab,
|
||||
int draft_token,
|
||||
float temperature,
|
||||
int top_k,
|
||||
float top_p,
|
||||
float min_p,
|
||||
uint64_t *rng,
|
||||
float *target_probs);
|
||||
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);
|
||||
int ds4_session_copy_logits(ds4_session *s, float *out, int cap);
|
||||
int ds4_session_set_logits(ds4_session *s, const float *logits, int n);
|
||||
/* Pay the one-time first-submission GPU cost outside any measured window;
|
||||
* used by the TP worker right after session create (no-op on CPU/GLM). */
|
||||
void ds4_session_gpu_warmup(ds4_session *s);
|
||||
int ds4_session_eval(ds4_session *s, int token, char *err, size_t errlen);
|
||||
|
||||
typedef struct {
|
||||
ds4_session *session;
|
||||
int token;
|
||||
} ds4_decode_item;
|
||||
|
||||
/* Advance independent sessions by one token each. Batch size one is exactly
|
||||
* ds4_session_eval(). Backends without native batching use a correctness-first
|
||||
* 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. */
|
||||
int ds4_sessions_eval_batch_with_prefill(
|
||||
ds4_decode_item *items, int count,
|
||||
ds4_session *prefill_session, const ds4_tokens *prefill_prompt,
|
||||
char *err, size_t errlen);
|
||||
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
|
||||
* DSpark or an internal GLM MTP block. */
|
||||
int ds4_session_eval_speculative(ds4_session *s, int first_token,
|
||||
int max_tokens, int eos_token,
|
||||
float temperature, int top_k,
|
||||
float top_p, float min_p, uint64_t *rng,
|
||||
int *accepted, int accepted_cap,
|
||||
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). 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,
|
||||
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);
|
||||
@@ -300,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
|
||||
|
||||
+3233
-674
File diff suppressed because it is too large.
Load diff
+331
-24
@@ -1,6 +1,8 @@
|
||||
#include "ds4.h"
|
||||
#include "ds4_distributed.h"
|
||||
#include "ds4_gpu_args.h"
|
||||
#include "ds4_help.h"
|
||||
#include "ds4_tp.h"
|
||||
|
||||
/* Purpose-built throughput benchmark.
|
||||
*
|
||||
@@ -22,15 +24,26 @@
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
/* CUDA builds resolve these weak symbols through libcudart; other builds
|
||||
* remain independent of CUDA headers and libraries. */
|
||||
#if defined(__GNUC__) && !defined(__APPLE__)
|
||||
#define DS4_BENCH_HAVE_CUDA_PROFILER 1
|
||||
extern int cudaProfilerStart(void) __attribute__((weak));
|
||||
extern int cudaProfilerStop(void) __attribute__((weak));
|
||||
#endif
|
||||
|
||||
#define DS4_BENCH_DEFAULT_SNAPSHOT_MAX_BYTES (UINT64_C(1) << 30)
|
||||
|
||||
typedef struct {
|
||||
const char *model_path;
|
||||
const char *mtp_path;
|
||||
const char *prompt_path;
|
||||
const char *chat_prompt_path;
|
||||
const char *system;
|
||||
const char *csv_path;
|
||||
const char *expert_profile_path;
|
||||
const char *gpu_vram_arg;
|
||||
const char *gpu_devices_arg;
|
||||
ds4_backend backend;
|
||||
int threads;
|
||||
int ctx_start;
|
||||
@@ -48,11 +61,18 @@ typedef struct {
|
||||
double step_mul;
|
||||
const char *dump_frontier_logits_dir;
|
||||
ds4_dist_options dist;
|
||||
ds4_tp_options tp;
|
||||
bool warm_weights;
|
||||
bool quality;
|
||||
bool ssd_streaming;
|
||||
bool ssd_streaming_cold;
|
||||
bool ssd_streaming_full_layers_set;
|
||||
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) {
|
||||
@@ -229,8 +249,38 @@ static bench_config parse_options(int argc, char **argv) {
|
||||
}
|
||||
if (dist_parse == DS4_DIST_CLI_MATCHED) continue;
|
||||
|
||||
char tp_parse_err[256] = {0};
|
||||
ds4_tp_cli_parse_result tp_parse =
|
||||
ds4_tp_parse_cli_arg(arg,
|
||||
&i,
|
||||
argc,
|
||||
argv,
|
||||
&c.tp,
|
||||
tp_parse_err,
|
||||
sizeof(tp_parse_err));
|
||||
if (tp_parse == DS4_TP_CLI_ERROR) {
|
||||
fprintf(stderr,
|
||||
"ds4-bench: %s\n",
|
||||
tp_parse_err[0] ? tp_parse_err : "invalid tensor-parallel option");
|
||||
exit(2);
|
||||
}
|
||||
if (tp_parse == DS4_TP_CLI_MATCHED) continue;
|
||||
|
||||
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")) {
|
||||
@@ -268,6 +318,12 @@ static bench_config parse_options(int argc, char **argv) {
|
||||
} else if (!strcmp(arg, "--cuda")) {
|
||||
c.backend = DS4_BACKEND_CUDA;
|
||||
#endif
|
||||
} else if (!strcmp(arg, "--gpu-vram")) {
|
||||
c.gpu_vram_arg = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--gpu-devices")) {
|
||||
c.gpu_devices_arg = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--cuda-tensor-parallel")) {
|
||||
c.cuda_tensor_parallel = true;
|
||||
} else if (!strcmp(arg, "--cpu")) {
|
||||
c.backend = DS4_BACKEND_CPU;
|
||||
} else if (!strcmp(arg, "--quality")) {
|
||||
@@ -315,6 +371,10 @@ static bench_config parse_options(int argc, char **argv) {
|
||||
}
|
||||
} else if (!strcmp(arg, "--warm-weights")) {
|
||||
c.warm_weights = true;
|
||||
} else if (!strcmp(arg, "--show-output")) {
|
||||
c.show_output = true;
|
||||
} else if (!strcmp(arg, "--teacher-forced-decode")) {
|
||||
c.teacher_forced_decode = true;
|
||||
} else {
|
||||
fprintf(stderr, "ds4-bench: unknown option: %s\n", arg);
|
||||
usage(stderr, NULL);
|
||||
@@ -326,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);
|
||||
@@ -347,12 +417,20 @@ static bench_config parse_options(int argc, char **argv) {
|
||||
fprintf(stderr, "ds4-bench: --ctx-alloc must be greater than ctx-max + gen-tokens\n");
|
||||
exit(2);
|
||||
}
|
||||
char tp_err[256];
|
||||
if (!ds4_tp_adopt_distributed_options(&c.tp,
|
||||
&c.dist,
|
||||
tp_err,
|
||||
sizeof(tp_err))) {
|
||||
fprintf(stderr, "ds4-bench: %s\n", tp_err);
|
||||
exit(2);
|
||||
}
|
||||
char dist_err[256];
|
||||
if (ds4_dist_prepare_engine_options(&c.dist, NULL, dist_err, sizeof(dist_err)) != 0) {
|
||||
fprintf(stderr, "ds4-bench: %s\n", dist_err);
|
||||
exit(2);
|
||||
}
|
||||
if (c.dist.role == DS4_DISTRIBUTED_WORKER) {
|
||||
if (c.dist.role == DS4_DISTRIBUTED_WORKER || c.tp.role == DS4_TP_WORKER) {
|
||||
fprintf(stderr, "ds4-bench: --role worker is a serving mode; start workers with ./ds4\n");
|
||||
exit(2);
|
||||
}
|
||||
@@ -538,11 +616,38 @@ static void maybe_warn_distributed_step_shape(const bench_config *cfg, ds4_sessi
|
||||
}
|
||||
}
|
||||
|
||||
static void close_engine(ds4_engine *engine, ds4_tp *tp) {
|
||||
if (tp) ds4_tp_send_stop(tp);
|
||||
ds4_engine_close(engine);
|
||||
ds4_tp_free(tp);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
bench_config cfg = parse_options(argc, argv);
|
||||
|
||||
/* Hint the packer at the largest ctx this bench run will exercise
|
||||
* so per-layer KV bytes are priced for the real session size, not
|
||||
* a stale 4096 default. Single-tier and CPU paths ignore this. */
|
||||
int placement_ctx_hint = cfg.ctx_max;
|
||||
if (cfg.ctx_alloc > placement_ctx_hint) placement_ctx_hint = cfg.ctx_alloc;
|
||||
|
||||
ds4_gpu_config gpu_cfg = {0};
|
||||
bool skip_cuda = false;
|
||||
const bool have_gpu_config = cfg.gpu_vram_arg || cfg.gpu_devices_arg;
|
||||
if (have_gpu_config) {
|
||||
char gpu_err[256];
|
||||
if (parse_gpu_vram_arg(cfg.gpu_vram_arg, cfg.gpu_devices_arg,
|
||||
&gpu_cfg, &skip_cuda,
|
||||
gpu_err, sizeof(gpu_err)) != 0) {
|
||||
fprintf(stderr, "ds4-bench: %s\n", gpu_err);
|
||||
return 2;
|
||||
}
|
||||
cfg.backend = skip_cuda ? DS4_BACKEND_CPU : DS4_BACKEND_CUDA;
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -555,22 +660,73 @@ 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,
|
||||
.ssd_streaming_full_layers_set = cfg.ssd_streaming_full_layers_set,
|
||||
.expert_profile_path = cfg.expert_profile_path,
|
||||
.distributed = cfg.dist,
|
||||
.tp = cfg.tp,
|
||||
};
|
||||
char dist_err[256];
|
||||
if (ds4_dist_prepare_engine_options(&cfg.dist, &opt, dist_err, sizeof(dist_err)) != 0) {
|
||||
fprintf(stderr, "ds4-bench: %s\n", dist_err);
|
||||
return 2;
|
||||
}
|
||||
char tp_err[256];
|
||||
if (!ds4_tp_validate_engine_options(&opt, tp_err, sizeof(tp_err))) {
|
||||
fprintf(stderr, "ds4-bench: %s\n", tp_err);
|
||||
return 2;
|
||||
}
|
||||
ds4_engine *engine = NULL;
|
||||
if (ds4_engine_open(&engine, &opt) != 0) return 1;
|
||||
log_context_memory(cfg.backend,
|
||||
if (have_gpu_config && !skip_cuda) {
|
||||
const bool was_auto =
|
||||
(cfg.gpu_vram_arg && !strcmp(cfg.gpu_vram_arg, "auto")) ||
|
||||
(!cfg.gpu_vram_arg && cfg.gpu_devices_arg);
|
||||
char layout[256];
|
||||
if (format_gpu_layout_line(&gpu_cfg, was_auto,
|
||||
layout, sizeof(layout)) > 0) {
|
||||
fprintf(stdout, "%s\n", layout);
|
||||
fflush(stdout);
|
||||
}
|
||||
if (ds4_engine_create_with_gpu_config(
|
||||
&engine, &opt, &gpu_cfg) != 0) return 1;
|
||||
} else if (ds4_engine_open(&engine, &opt) != 0) {
|
||||
return 1;
|
||||
}
|
||||
ds4_tp *tp_leader = NULL;
|
||||
if (cfg.tp.role == DS4_TP_LEADER) {
|
||||
ds4_tp_identity tp_id = {
|
||||
.gguf_bytes = ds4_engine_model_bytes(engine),
|
||||
.model_id = (uint32_t)ds4_engine_model_id(engine),
|
||||
.n_layer = (uint32_t)ds4_engine_layer_count(engine),
|
||||
.n_embd = (uint32_t)ds4_engine_embd_dim(engine),
|
||||
.n_vocab = (uint32_t)ds4_engine_vocab_size(engine),
|
||||
.quant_bits = (uint32_t)ds4_engine_routed_quant_bits(engine),
|
||||
.ctx_size = (uint32_t)cfg.ctx_alloc,
|
||||
};
|
||||
ds4_engine_tp_gate_schedule(engine,
|
||||
&tp_id.gate_slot_start,
|
||||
&tp_id.gate_slot_step,
|
||||
&tp_id.gates_per_token,
|
||||
tp_id.gate_slot_mask);
|
||||
if (!ds4_tp_create(&tp_leader,
|
||||
&cfg.tp,
|
||||
&tp_id,
|
||||
tp_err,
|
||||
sizeof(tp_err)) ||
|
||||
!ds4_engine_tp_bind(engine, tp_leader, tp_err, sizeof(tp_err))) {
|
||||
fprintf(stderr, "ds4-bench: %s\n", tp_err);
|
||||
close_engine(engine, tp_leader);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
log_context_memory(opt.backend,
|
||||
cfg.ctx_alloc,
|
||||
cfg.prefill_chunk,
|
||||
ds4_engine_prefill_chunk(engine),
|
||||
cfg.ssd_streaming);
|
||||
|
||||
char *text = read_file(cfg.prompt_path ? cfg.prompt_path : cfg.chat_prompt_path);
|
||||
@@ -582,13 +738,16 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
free(text);
|
||||
|
||||
if (prompt.len < cfg.ctx_max) {
|
||||
const int prompt_tokens_needed = cfg.ctx_max +
|
||||
(cfg.teacher_forced_decode ? cfg.gen_tokens : 0);
|
||||
if (prompt.len < prompt_tokens_needed) {
|
||||
fprintf(stderr,
|
||||
"ds4-bench: prompt has %d tokens, need at least --ctx-max=%d\n",
|
||||
"ds4-bench: prompt has %d tokens, need at least %d%s\n",
|
||||
prompt.len,
|
||||
cfg.ctx_max);
|
||||
prompt_tokens_needed,
|
||||
cfg.teacher_forced_decode ? " for teacher-forced decode" : "");
|
||||
ds4_tokens_free(&prompt);
|
||||
ds4_engine_close(engine);
|
||||
close_engine(engine, tp_leader);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -596,7 +755,7 @@ int main(int argc, char **argv) {
|
||||
if (ds4_session_create(&session, engine, cfg.ctx_alloc) != 0) {
|
||||
fprintf(stderr, "ds4-bench: failed to create session\n");
|
||||
ds4_tokens_free(&prompt);
|
||||
ds4_engine_close(engine);
|
||||
close_engine(engine, tp_leader);
|
||||
return 1;
|
||||
}
|
||||
if (cfg.dist.role == DS4_DISTRIBUTED_COORDINATOR &&
|
||||
@@ -604,7 +763,7 @@ int main(int argc, char **argv) {
|
||||
{
|
||||
ds4_session_free(session);
|
||||
ds4_tokens_free(&prompt);
|
||||
ds4_engine_close(engine);
|
||||
close_engine(engine, tp_leader);
|
||||
return 1;
|
||||
}
|
||||
maybe_warn_distributed_step_shape(&cfg, session);
|
||||
@@ -616,7 +775,7 @@ int main(int argc, char **argv) {
|
||||
fprintf(stderr, "ds4-bench: failed to open %s: %s\n", cfg.csv_path, strerror(errno));
|
||||
ds4_session_free(session);
|
||||
ds4_tokens_free(&prompt);
|
||||
ds4_engine_close(engine);
|
||||
close_engine(engine, tp_leader);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -624,7 +783,23 @@ int main(int argc, char **argv) {
|
||||
fflush(out);
|
||||
|
||||
const int eos = ds4_token_eos(engine);
|
||||
const bool distributed = cfg.dist.role == DS4_DISTRIBUTED_COORDINATOR;
|
||||
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;
|
||||
@@ -640,13 +815,27 @@ int main(int argc, char **argv) {
|
||||
};
|
||||
|
||||
const double prefill_t0 = bench_now_sec();
|
||||
if (ds4_session_sync(session, &prefix, err, sizeof(err)) != 0) {
|
||||
#if defined(DS4_BENCH_HAVE_CUDA_PROFILER)
|
||||
const bool cuda_profile_prefill =
|
||||
getenv("DS4_BENCH_CUDA_PROFILE_PREFILL") != NULL &&
|
||||
cudaProfilerStart && cudaProfilerStop;
|
||||
if (cuda_profile_prefill) (void)cudaProfilerStart();
|
||||
#endif
|
||||
const int prefill_rc =
|
||||
ds4_session_sync(session, &prefix, err, sizeof(err));
|
||||
#if defined(DS4_BENCH_HAVE_CUDA_PROFILER)
|
||||
if (cuda_profile_prefill) (void)cudaProfilerStop();
|
||||
#endif
|
||||
if (prefill_rc != 0) {
|
||||
fprintf(stderr, "ds4-bench: prefill to %d failed: %s\n", frontier, err);
|
||||
rc = 1;
|
||||
break;
|
||||
}
|
||||
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) {
|
||||
@@ -684,31 +873,148 @@ 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;
|
||||
for (int i = 0; i < cfg.gen_tokens; i++) {
|
||||
int *gen_token_buf = cfg.show_output && cfg.gen_tokens > 0
|
||||
? malloc((size_t)cfg.gen_tokens * sizeof(gen_token_buf[0]))
|
||||
: NULL;
|
||||
int gen_token_count = 0;
|
||||
int cuda_profile_start = -1;
|
||||
int cuda_profile_tokens = 0;
|
||||
const char *cuda_profile_range =
|
||||
getenv("DS4_BENCH_CUDA_PROFILE_RANGE");
|
||||
if (cuda_profile_range &&
|
||||
(sscanf(cuda_profile_range, "%d:%d",
|
||||
&cuda_profile_start, &cuda_profile_tokens) != 2 ||
|
||||
cuda_profile_start < 0 || cuda_profile_tokens <= 0)) {
|
||||
fprintf(stderr,
|
||||
"ds4-bench: invalid DS4_BENCH_CUDA_PROFILE_RANGE=%s "
|
||||
"(expected START_TOKEN:TOKEN_COUNT)\n",
|
||||
cuda_profile_range);
|
||||
cuda_profile_start = -1;
|
||||
cuda_profile_tokens = 0;
|
||||
}
|
||||
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 = ds4_session_argmax_excluding(session, eos);
|
||||
const int token = cfg.teacher_forced_decode
|
||||
? 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);
|
||||
rc = 1;
|
||||
break;
|
||||
}
|
||||
const double token_t0 = bench_now_sec();
|
||||
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 (gen_done == cuda_profile_start && cudaProfilerStart) {
|
||||
(void)cudaProfilerStart();
|
||||
}
|
||||
#endif
|
||||
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 &&
|
||||
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;
|
||||
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) {
|
||||
fprintf(stderr, "ds4-bench: gen[ctx=%d] decoded text: \"", frontier);
|
||||
for (int i = 0; i < gen_token_count; i++) {
|
||||
size_t tlen = 0;
|
||||
char *txt = ds4_token_text(engine, gen_token_buf[i], &tlen);
|
||||
if (txt) {
|
||||
fwrite(txt, 1, tlen, stderr);
|
||||
free(txt);
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "\"\n");
|
||||
fflush(stderr);
|
||||
}
|
||||
free(gen_token_buf);
|
||||
if (rc != 0) break;
|
||||
|
||||
if (!need_restore_after_generation) {
|
||||
@@ -728,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,
|
||||
@@ -750,6 +1057,6 @@ int main(int argc, char **argv) {
|
||||
ds4_session_snapshot_free(&snap);
|
||||
ds4_session_free(session);
|
||||
ds4_tokens_free(&prompt);
|
||||
ds4_engine_close(engine);
|
||||
close_engine(engine, tp_leader);
|
||||
return rc;
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "ds4.h"
|
||||
#include "ds4_distributed.h"
|
||||
#include "ds4_gpu_args.h"
|
||||
#include "ds4_tp.h"
|
||||
#include "ds4_help.h"
|
||||
#include "ds4_prompt_prefix.h"
|
||||
#include "linenoise.h"
|
||||
|
||||
/* ds4 CLI.
|
||||
@@ -25,9 +28,41 @@
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static bool cli_env_flag_enabled(const char *name, bool defval) {
|
||||
const char *v = getenv(name);
|
||||
if (!v || !v[0]) return defval;
|
||||
return strcmp(v, "0") != 0;
|
||||
}
|
||||
|
||||
static bool cli_splitkv_spec_requested(void) {
|
||||
if (cli_env_flag_enabled("DS4_CUDA_NO_SPLITKV_SPEC", false)) return false;
|
||||
return cli_env_flag_enabled("DS4_CUDA_SPLITKV_SPEC", false);
|
||||
}
|
||||
|
||||
static bool cli_greedy_fast_attention_requested(void) {
|
||||
if (!cli_env_flag_enabled("DS4_CUDA_NO_GREEDY_SPLITKV", false) &&
|
||||
cli_env_flag_enabled("DS4_CUDA_GREEDY_SPLITKV", false))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!cli_env_flag_enabled("DS4_CUDA_NO_GREEDY_VEC4", false) &&
|
||||
cli_env_flag_enabled("DS4_CUDA_GREEDY_VEC4", false))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool cli_greedy_argmax_requested(bool speculative_requested) {
|
||||
if (cli_greedy_fast_attention_requested()) return true;
|
||||
if (speculative_requested) return false;
|
||||
return cli_env_flag_enabled("DS4_CUDA_GREEDY_TOP1", true);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const char *prompt;
|
||||
const char *system;
|
||||
ds4_prompt_prefix prefix;
|
||||
bool raw_prompt;
|
||||
int n_predict;
|
||||
int ctx_size;
|
||||
@@ -48,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;
|
||||
@@ -62,6 +98,10 @@ typedef struct {
|
||||
cli_generation_options gen;
|
||||
char *prompt_owned;
|
||||
bool inspect;
|
||||
/* CLI flag wiring: raw argv values for --gpu-vram and --gpu-devices.
|
||||
* Resolved post-parse via parse_gpu_vram_arg(). */
|
||||
const char *gpu_vram_arg;
|
||||
const char *gpu_devices_arg;
|
||||
} cli_config;
|
||||
|
||||
static volatile sig_atomic_t cli_interrupted;
|
||||
@@ -460,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,6 +546,9 @@ static int run_sampled_generation(ds4_engine *engine, const cli_config *cfg, con
|
||||
ds4_session_free(session);
|
||||
return 1;
|
||||
}
|
||||
/* Pay the one-time first-submission GPU cost before the prefill timer
|
||||
* starts (matches the TP worker's startup warmup). */
|
||||
ds4_session_gpu_warmup(session);
|
||||
|
||||
char err[160];
|
||||
ds4_think_mode think_mode = cli_effective_think_mode(&cfg->gen);
|
||||
@@ -535,25 +593,37 @@ static int run_sampled_generation(ds4_engine *engine, const cli_config *cfg, con
|
||||
uint64_t rng = cfg->gen.seed ? cfg->gen.seed :
|
||||
((uint64_t)time(NULL) ^ ((uint64_t)getpid() << 32) ^ (uint64_t)clock());
|
||||
int generated = 0;
|
||||
const bool speculative_argmax = cfg->gen.temperature <= 0.0f &&
|
||||
((ds4_engine_mtp_draft_tokens(engine) > 1 &&
|
||||
getenv("DS4_MTP_SPEC_DISABLE") == NULL) ||
|
||||
cli_splitkv_spec_requested());
|
||||
const bool greedy_argmax = cfg->gen.temperature <= 0.0f &&
|
||||
cli_greedy_argmax_requested(speculative_argmax);
|
||||
bool have_greedy_next = false;
|
||||
int greedy_next = -1;
|
||||
const double t_decode0 = cli_now_sec();
|
||||
while (generated < max_tokens && !cli_interrupt_requested()) {
|
||||
int token = ds4_session_sample(session, cfg->gen.temperature, 0,
|
||||
int token;
|
||||
if (greedy_argmax && have_greedy_next) {
|
||||
token = greedy_next;
|
||||
have_greedy_next = false;
|
||||
} else {
|
||||
token = ds4_session_sample(session, cfg->gen.temperature, 0,
|
||||
cfg->gen.top_p, cfg->gen.min_p, &rng);
|
||||
}
|
||||
if (ds4_token_is_stop_for_think_mode(engine, token, think_mode)) break;
|
||||
|
||||
int toks[17];
|
||||
int ntok = 0;
|
||||
if (cfg->gen.temperature <= 0.0f && ds4_engine_mtp_draft_tokens(engine) > 1 &&
|
||||
if (ds4_engine_mtp_draft_tokens(engine) > 1 &&
|
||||
getenv("DS4_MTP_SPEC_DISABLE") == NULL) {
|
||||
cli_dist_busy_set(cfg, true);
|
||||
ntok = ds4_session_eval_speculative_argmax(session,
|
||||
token,
|
||||
max_tokens - generated,
|
||||
ds4_token_eos(engine),
|
||||
toks,
|
||||
(int)(sizeof(toks) / sizeof(toks[0])),
|
||||
err,
|
||||
sizeof(err));
|
||||
ntok = ds4_session_eval_speculative(
|
||||
session, token, max_tokens - generated,
|
||||
ds4_token_eos(engine), cfg->gen.temperature, 0,
|
||||
cfg->gen.top_p, cfg->gen.min_p, &rng,
|
||||
toks, (int)(sizeof(toks) / sizeof(toks[0])),
|
||||
err, sizeof(err));
|
||||
cli_dist_busy_set(cfg, false);
|
||||
if (ntok < 0) {
|
||||
fprintf(stderr, "ds4: decode failed: %s\n", err);
|
||||
@@ -1168,8 +1238,14 @@ static int run_generation(ds4_engine *engine, const cli_config *cfg) {
|
||||
}
|
||||
} else {
|
||||
if (cfg->engine.distributed.role == DS4_DISTRIBUTED_COORDINATOR ||
|
||||
cfg->engine.tp.role == DS4_TP_LEADER ||
|
||||
getenv("DS4_CLI_FORCE_SESSION") != NULL ||
|
||||
cfg->gen.temperature > 0.0f ||
|
||||
ds4_engine_mtp_draft_tokens(engine) > 1) {
|
||||
/* TP leaders always drive the session path: the sync/eval
|
||||
* mirroring that keeps the worker in lockstep lives there.
|
||||
* The env override exists so TP-vs-single-node validation
|
||||
* compares like with like. */
|
||||
rc = run_sampled_generation(engine, cfg, &prompt);
|
||||
} else {
|
||||
token_printer printer = {
|
||||
@@ -1210,12 +1286,13 @@ 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.");
|
||||
puts(" /read FILE Read a prompt from FILE and run it.");
|
||||
puts(" /steer F Set FFN steering for subsequent tokens; no value shows it.");
|
||||
puts(" /read FILE Submit a text file, PNG, or JPEG.");
|
||||
puts(" /quit, /exit Leave the prompt.");
|
||||
puts(" Ctrl+C Stop generation and return to the prompt.");
|
||||
}
|
||||
@@ -1228,6 +1305,18 @@ static bool parse_power_percent(const char *arg, int *out) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_steering_level(const char *arg, float *out) {
|
||||
char *end = NULL;
|
||||
errno = 0;
|
||||
float v = strtof(arg, &end);
|
||||
if (!arg[0] || *end != '\0' || errno == ERANGE || !isfinite(v) ||
|
||||
v < -100.0f || v > 100.0f) {
|
||||
return false;
|
||||
}
|
||||
*out = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void history_file_path(char *buf, size_t len) {
|
||||
const char *home = getenv("HOME");
|
||||
if (!home || !home[0]) home = ".";
|
||||
@@ -1237,11 +1326,38 @@ static void history_file_path(char *buf, size_t len) {
|
||||
typedef struct {
|
||||
ds4_session *session;
|
||||
ds4_tokens transcript;
|
||||
ds4_vision_span *images;
|
||||
size_t image_count;
|
||||
size_t image_cap;
|
||||
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--;
|
||||
ds4_vision_embedding_free(&chat->images[chat->image_count].embedding);
|
||||
}
|
||||
}
|
||||
|
||||
static ds4_vision_span *repl_chat_add_image(repl_chat *chat) {
|
||||
if (chat->image_count == chat->image_cap) {
|
||||
size_t cap = chat->image_cap ? chat->image_cap * 2u : 4u;
|
||||
if (cap > SIZE_MAX / sizeof(chat->images[0])) return NULL;
|
||||
void *next = realloc(chat->images, cap * sizeof(chat->images[0]));
|
||||
if (!next) return NULL;
|
||||
chat->images = next;
|
||||
chat->image_cap = cap;
|
||||
}
|
||||
ds4_vision_span *span = &chat->images[chat->image_count++];
|
||||
memset(span, 0, sizeof(*span));
|
||||
return span;
|
||||
}
|
||||
|
||||
static void tokens_insert(ds4_tokens *dst, int pos, const ds4_tokens *src) {
|
||||
if (!src || src->len <= 0) return;
|
||||
if (pos < 0) pos = 0;
|
||||
@@ -1269,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) {
|
||||
@@ -1305,13 +1409,25 @@ static void repl_chat_apply_think_prefix(ds4_engine *engine,
|
||||
(size_t)prefix.len * sizeof(prefix.v[0]));
|
||||
}
|
||||
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;
|
||||
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);
|
||||
}
|
||||
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) {
|
||||
@@ -1328,19 +1444,55 @@ 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) {
|
||||
if (!chat) return;
|
||||
ds4_session_free(chat->session);
|
||||
ds4_tokens_free(&chat->transcript);
|
||||
repl_chat_trim_images(chat, 0);
|
||||
free(chat->images);
|
||||
memset(chat, 0, sizeof(*chat));
|
||||
}
|
||||
|
||||
@@ -1359,7 +1511,9 @@ static bool repl_chat_assistant_turn_uses_eos(ds4_engine *engine) {
|
||||
* and assistant markers, then ds4_session_sync() decides whether this is a KV
|
||||
* continuation. If prompt processing fails, the transcript rolls back before
|
||||
* returning to the prompt. */
|
||||
static int run_chat_turn(ds4_engine *engine, cli_config *cfg, repl_chat *chat, const char *user_text) {
|
||||
static int run_chat_turn(ds4_engine *engine, cli_config *cfg, repl_chat *chat,
|
||||
const char *user_text,
|
||||
ds4_vision_embedding *image) {
|
||||
if (!chat->session) {
|
||||
fprintf(stderr, "ds4: no active interactive KV cache\n");
|
||||
return 1;
|
||||
@@ -1367,9 +1521,25 @@ static int run_chat_turn(ds4_engine *engine, cli_config *cfg, repl_chat *chat, c
|
||||
|
||||
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;
|
||||
ds4_chat_append_message(engine, &chat->transcript, "user", user_text);
|
||||
const size_t rollback_images = chat->image_count;
|
||||
if (image) {
|
||||
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_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;
|
||||
}
|
||||
} else {
|
||||
ds4_chat_append_message(engine, &chat->transcript, "user", user_text);
|
||||
}
|
||||
ds4_chat_append_assistant_prefix(engine, &chat->transcript, think_mode);
|
||||
|
||||
const int old_pos = ds4_session_pos(chat->session);
|
||||
@@ -1389,12 +1559,18 @@ static int run_chat_turn(ds4_engine *engine, cli_config *cfg, repl_chat *chat, c
|
||||
progress.use_color ? cli_prefill_progress_cb : NULL,
|
||||
progress.use_color ? &progress : NULL);
|
||||
cli_dist_busy_set(cfg, true);
|
||||
int sync_rc = ds4_session_sync(chat->session, &chat->transcript, err, sizeof(err));
|
||||
int sync_rc = ds4_session_sync_multimodal(chat->session,
|
||||
&chat->transcript,
|
||||
chat->images,
|
||||
chat->image_count,
|
||||
err,
|
||||
sizeof(err));
|
||||
cli_dist_busy_set(cfg, false);
|
||||
if (sync_rc != 0) {
|
||||
ds4_session_set_progress(chat->session, NULL, NULL);
|
||||
ds4_session_set_display_progress(chat->session, NULL, NULL);
|
||||
chat->transcript.len = rollback_len;
|
||||
repl_chat_trim_images(chat, rollback_images);
|
||||
fprintf(stderr, "ds4: prompt processing failed: %s\n", err);
|
||||
return 1;
|
||||
}
|
||||
@@ -1419,29 +1595,41 @@ static int run_chat_turn(ds4_engine *engine, cli_config *cfg, repl_chat *chat, c
|
||||
uint64_t rng = cfg->gen.seed ? cfg->gen.seed :
|
||||
((uint64_t)time(NULL) ^ ((uint64_t)getpid() << 32) ^ (uint64_t)clock());
|
||||
int generated = 0;
|
||||
const bool speculative_argmax = cfg->gen.temperature <= 0.0f &&
|
||||
((ds4_engine_mtp_draft_tokens(engine) > 1 &&
|
||||
getenv("DS4_MTP_SPEC_DISABLE") == NULL) ||
|
||||
cli_splitkv_spec_requested());
|
||||
const bool greedy_argmax = cfg->gen.temperature <= 0.0f &&
|
||||
cli_greedy_argmax_requested(speculative_argmax);
|
||||
bool have_greedy_next = false;
|
||||
int greedy_next = -1;
|
||||
const double t_decode0 = cli_now_sec();
|
||||
while (generated < max_tokens && !cli_interrupt_requested()) {
|
||||
int token = ds4_session_sample(chat->session,
|
||||
int token;
|
||||
if (greedy_argmax && have_greedy_next) {
|
||||
token = greedy_next;
|
||||
have_greedy_next = false;
|
||||
} else {
|
||||
token = ds4_session_sample(chat->session,
|
||||
cfg->gen.temperature,
|
||||
0,
|
||||
cfg->gen.top_p,
|
||||
cfg->gen.min_p,
|
||||
&rng);
|
||||
}
|
||||
if (ds4_token_is_stop_for_think_mode(engine, token, think_mode)) break;
|
||||
|
||||
int toks[17];
|
||||
int ntok = 0;
|
||||
if (cfg->gen.temperature <= 0.0f && ds4_engine_mtp_draft_tokens(engine) > 1 &&
|
||||
if (ds4_engine_mtp_draft_tokens(engine) > 1 &&
|
||||
getenv("DS4_MTP_SPEC_DISABLE") == NULL) {
|
||||
cli_dist_busy_set(cfg, true);
|
||||
ntok = ds4_session_eval_speculative_argmax(chat->session,
|
||||
token,
|
||||
max_tokens - generated,
|
||||
ds4_token_eos(engine),
|
||||
toks,
|
||||
(int)(sizeof(toks) / sizeof(toks[0])),
|
||||
err,
|
||||
sizeof(err));
|
||||
ntok = ds4_session_eval_speculative(
|
||||
chat->session, token, max_tokens - generated,
|
||||
ds4_token_eos(engine), cfg->gen.temperature, 0,
|
||||
cfg->gen.top_p, cfg->gen.min_p, &rng,
|
||||
toks, (int)(sizeof(toks) / sizeof(toks[0])),
|
||||
err, sizeof(err));
|
||||
cli_dist_busy_set(cfg, false);
|
||||
if (ntok < 0) {
|
||||
fprintf(stderr, "ds4: decode failed: %s\n", err);
|
||||
@@ -1491,6 +1679,7 @@ static int run_chat_turn(ds4_engine *engine, cli_config *cfg, repl_chat *chat, c
|
||||
const bool interrupted = cli_interrupt_requested();
|
||||
if (interrupted && generated == 0) {
|
||||
chat->transcript.len = rollback_len;
|
||||
repl_chat_trim_images(chat, rollback_images);
|
||||
ds4_session_invalidate(chat->session);
|
||||
} else if (repl_chat_assistant_turn_uses_eos(engine)) {
|
||||
ds4_tokens_push(&chat->transcript, ds4_token_eos(engine));
|
||||
@@ -1507,6 +1696,16 @@ static int run_chat_turn(ds4_engine *engine, cli_config *cfg, repl_chat *chat, c
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool cli_file_has_image_magic(const char *path) {
|
||||
unsigned char magic[8] = {0};
|
||||
FILE *fp = fopen(path, "rb");
|
||||
if (!fp) return false;
|
||||
size_t n = fread(magic, 1, sizeof(magic), fp);
|
||||
fclose(fp);
|
||||
return (n >= 8 && !memcmp(magic, "\x89PNG\r\n\x1a\n", 8)) ||
|
||||
(n >= 2 && magic[0] == 0xff && magic[1] == 0xd8);
|
||||
}
|
||||
|
||||
static int run_repl(ds4_engine *engine, cli_config *cfg) {
|
||||
repl_chat chat;
|
||||
if (repl_chat_init(engine, &chat, cfg) != 0) return 1;
|
||||
@@ -1547,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]) {
|
||||
@@ -1578,6 +1786,22 @@ static int run_repl(ds4_engine *engine, cli_config *cfg) {
|
||||
printf("Power: %d%%.\n", power);
|
||||
}
|
||||
}
|
||||
} else if (!strncmp(cmd, "/steer", 6) &&
|
||||
(cmd[6] == '\0' || isspace((unsigned char)cmd[6]))) {
|
||||
char *arg = trim_inplace(cmd + 6);
|
||||
if (!arg[0]) {
|
||||
printf("Steering FFN: %g.\n",
|
||||
(double)ds4_session_directional_steering_ffn(chat.session));
|
||||
} else {
|
||||
float scale = 0.0f;
|
||||
if (!parse_steering_level(arg, &scale)) {
|
||||
fprintf(stderr, "ds4: /steer must be between -100 and 100\n");
|
||||
} else if (ds4_session_set_directional_steering_ffn(
|
||||
chat.session, scale) == 0) {
|
||||
cfg->engine.directional_steering_ffn = scale;
|
||||
printf("Steering FFN: %g.\n", (double)scale);
|
||||
}
|
||||
}
|
||||
} else if (!strncmp(cmd, "/ctx", 4) && (cmd[4] == '\0' || isspace((unsigned char)cmd[4]))) {
|
||||
char *arg = trim_inplace(cmd + 4);
|
||||
if (!arg[0]) {
|
||||
@@ -1586,7 +1810,7 @@ static int run_repl(ds4_engine *engine, cli_config *cfg) {
|
||||
cfg->gen.ctx_size = parse_int(arg, "/ctx");
|
||||
log_context_memory(cfg->engine.backend,
|
||||
cfg->gen.ctx_size,
|
||||
cfg->engine.prefill_chunk,
|
||||
ds4_engine_prefill_chunk(engine),
|
||||
cfg->engine.ssd_streaming);
|
||||
rc = repl_chat_set_ctx(engine, &chat, cfg->gen.ctx_size);
|
||||
if (rc != 0) {
|
||||
@@ -1605,10 +1829,24 @@ static int run_repl(ds4_engine *engine, cli_config *cfg) {
|
||||
char *path = trim_inplace(cmd + 5);
|
||||
if (!path[0]) {
|
||||
fprintf(stderr, "ds4: /read needs a file path\n");
|
||||
} else if (cli_file_has_image_magic(path)) {
|
||||
char image_error[256] = {0};
|
||||
ds4_vision_embedding image = {0};
|
||||
if (!ds4_engine_vision_encode_file(engine, path, &image,
|
||||
image_error,
|
||||
sizeof(image_error))) {
|
||||
fprintf(stderr, "ds4: /read image failed: %s\n", image_error);
|
||||
} else {
|
||||
fprintf(stderr,
|
||||
"ds4: image %ux%u, %u image tokens\n",
|
||||
image.width, image.height, image.token_count);
|
||||
rc = run_chat_turn(engine, cfg, &chat, "", &image);
|
||||
ds4_vision_embedding_free(&image);
|
||||
}
|
||||
} else {
|
||||
char *prompt = read_prompt_file(path, false);
|
||||
if (prompt) {
|
||||
rc = run_chat_turn(engine, cfg, &chat, prompt);
|
||||
rc = run_chat_turn(engine, cfg, &chat, prompt, NULL);
|
||||
free(prompt);
|
||||
}
|
||||
}
|
||||
@@ -1616,7 +1854,7 @@ static int run_repl(ds4_engine *engine, cli_config *cfg) {
|
||||
fprintf(stderr, "ds4: unknown command: %s\n", cmd);
|
||||
fprintf(stderr, "ds4: type /help for commands\n");
|
||||
} else {
|
||||
rc = run_chat_turn(engine, cfg, &chat, cmd);
|
||||
rc = run_chat_turn(engine, cfg, &chat, cmd, NULL);
|
||||
}
|
||||
linenoiseFree(line);
|
||||
}
|
||||
@@ -1730,6 +1968,20 @@ static cli_config parse_options(int argc, char **argv) {
|
||||
}
|
||||
if (dist_parse == DS4_DIST_CLI_MATCHED) continue;
|
||||
|
||||
char tp_parse_err[256] = {0};
|
||||
ds4_tp_cli_parse_result tp_parse = ds4_tp_parse_cli_arg(arg,
|
||||
&i,
|
||||
argc,
|
||||
argv,
|
||||
&c.engine.tp,
|
||||
tp_parse_err,
|
||||
sizeof(tp_parse_err));
|
||||
if (tp_parse == DS4_TP_CLI_ERROR) {
|
||||
fprintf(stderr, "ds4: %s\n", tp_parse_err[0] ? tp_parse_err : "invalid tensor-parallel option");
|
||||
exit(2);
|
||||
}
|
||||
if (tp_parse == DS4_TP_CLI_MATCHED) continue;
|
||||
|
||||
if (!strcmp(arg, "-p") || !strcmp(arg, "--prompt")) {
|
||||
if (c.gen.prompt) {
|
||||
fprintf(stderr, "ds4: specify only one prompt source\n");
|
||||
@@ -1743,18 +1995,50 @@ 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")) {
|
||||
c.gen.raw_prompt = true;
|
||||
} else if (!strcmp(arg, "-m") || !strcmp(arg, "--model")) {
|
||||
c.engine.model_path = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--vision")) {
|
||||
c.engine.vision_path = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--mtp")) {
|
||||
c.engine.glm_mtp = true;
|
||||
} else if (!strcmp(arg, "--mtp-model")) {
|
||||
c.engine.mtp_path = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--mtp-draft")) {
|
||||
c.engine.mtp_draft_tokens = parse_int(need_arg(&i, argc, argv, arg), arg);
|
||||
} else if (!strcmp(arg, "--mtp-margin")) {
|
||||
c.engine.mtp_margin = parse_float_range(need_arg(&i, argc, argv, arg), arg, 0.0f, 1000.0f);
|
||||
} else if (!strcmp(arg, "--mtp-timing")) {
|
||||
c.engine.glm_mtp = true;
|
||||
c.engine.glm_mtp_timing = true;
|
||||
} else if (!strcmp(arg, "--dspark")) {
|
||||
c.engine.dspark = true;
|
||||
} else if (!strcmp(arg, "--dspark-confidence")) {
|
||||
c.engine.dspark = true;
|
||||
c.engine.dspark_confidence_threshold =
|
||||
parse_float_range(need_arg(&i, argc, argv, arg), arg, 0.0f, 1.0f);
|
||||
c.engine.dspark_confidence_threshold_set = true;
|
||||
} else if (!strcmp(arg, "--dspark-strict")) {
|
||||
c.engine.dspark = true;
|
||||
c.engine.dspark_strict = true;
|
||||
} else if (!strcmp(arg, "--mtp-exact-sampling")) {
|
||||
c.engine.dspark_exact_sampling = true;
|
||||
} else if (!strcmp(arg, "-n") || !strcmp(arg, "--tokens")) {
|
||||
c.gen.n_predict = parse_int(need_arg(&i, argc, argv, arg), arg);
|
||||
} else if (!strcmp(arg, "-c") || !strcmp(arg, "--ctx")) {
|
||||
@@ -1843,6 +2127,12 @@ static cli_config parse_options(int argc, char **argv) {
|
||||
} else if (!strcmp(arg, "--cuda")) {
|
||||
c.engine.backend = DS4_BACKEND_CUDA;
|
||||
#endif
|
||||
} else if (!strcmp(arg, "--gpu-vram")) {
|
||||
c.gpu_vram_arg = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--gpu-devices")) {
|
||||
c.gpu_devices_arg = need_arg(&i, argc, argv, arg);
|
||||
} else if (!strcmp(arg, "--cuda-tensor-parallel")) {
|
||||
c.engine.cuda_tensor_parallel = true;
|
||||
} else if (!strcmp(arg, "--dump-tokens")) {
|
||||
c.gen.dump_tokens = true;
|
||||
} else if (!strcmp(arg, "--dump-logits")) {
|
||||
@@ -1864,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")) {
|
||||
@@ -1923,15 +2221,38 @@ 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))) {
|
||||
fprintf(stderr, "ds4: %s\n", tp_err);
|
||||
exit(2);
|
||||
}
|
||||
char dist_err[256];
|
||||
if (ds4_dist_prepare_engine_options(c.dist, &c.engine, dist_err, sizeof(dist_err)) != 0) {
|
||||
fprintf(stderr, "ds4: %s\n", dist_err);
|
||||
exit(2);
|
||||
}
|
||||
if (!ds4_tp_validate_engine_options(&c.engine, tp_err, sizeof(tp_err))) {
|
||||
fprintf(stderr, "ds4: %s\n", tp_err);
|
||||
exit(2);
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
@@ -1939,15 +2260,35 @@ 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.prompt == NULL) {
|
||||
fprintf(stderr, "ds4: --dump-tokens requires -p or --prompt-file\n");
|
||||
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;
|
||||
}
|
||||
int rc = ds4_dump_text_tokenization(cfg.engine.model_path,
|
||||
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;
|
||||
}
|
||||
int rc;
|
||||
if (cfg.gen.raw_prompt || is_rendered_chat_prompt(cfg.gen.prompt)) {
|
||||
rc = ds4_dump_text_tokenization(cfg.engine.model_path,
|
||||
cfg.gen.prompt,
|
||||
stdout);
|
||||
} else {
|
||||
rc = ds4_dump_chat_tokenization(cfg.engine.model_path,
|
||||
cfg.gen.system,
|
||||
cfg.gen.prompt,
|
||||
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;
|
||||
}
|
||||
@@ -1955,13 +2296,92 @@ int main(int argc, char **argv) {
|
||||
cfg.engine.first_token_test = cfg.gen.first_token_test;
|
||||
cfg.engine.metal_graph_test = cfg.gen.metal_graph_test;
|
||||
cfg.engine.context_size = cfg.gen.ctx_size;
|
||||
cfg.engine.placement_ctx_hint = cfg.gen.ctx_size;
|
||||
ds4_engine *engine = NULL;
|
||||
if (ds4_engine_open(&engine, &cfg.engine) != 0) {
|
||||
if (cfg.gpu_vram_arg || cfg.gpu_devices_arg) {
|
||||
ds4_gpu_config gpu_cfg = {0};
|
||||
bool skip_cuda = false;
|
||||
char errbuf[256];
|
||||
if (parse_gpu_vram_arg(cfg.gpu_vram_arg, cfg.gpu_devices_arg,
|
||||
&gpu_cfg, &skip_cuda,
|
||||
errbuf, sizeof(errbuf)) != 0) {
|
||||
fprintf(stderr, "ds4: %s\n", errbuf);
|
||||
ds4_dist_options_free(cfg.dist);
|
||||
free(cfg.prompt_owned);
|
||||
return 2;
|
||||
}
|
||||
cfg.engine.backend = skip_cuda ? DS4_BACKEND_CPU : DS4_BACKEND_CUDA;
|
||||
if (skip_cuda) {
|
||||
if (ds4_engine_open(&engine, &cfg.engine) != 0) {
|
||||
ds4_dist_options_free(cfg.dist);
|
||||
free(cfg.prompt_owned);
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
const bool was_auto =
|
||||
(cfg.gpu_vram_arg && !strcmp(cfg.gpu_vram_arg, "auto")) ||
|
||||
(!cfg.gpu_vram_arg && cfg.gpu_devices_arg);
|
||||
char layout[256];
|
||||
if (format_gpu_layout_line(&gpu_cfg, was_auto,
|
||||
layout, sizeof(layout)) > 0) {
|
||||
fprintf(stdout, "%s\n", layout);
|
||||
fflush(stdout);
|
||||
}
|
||||
if (ds4_engine_create_with_gpu_config(&engine, &cfg.engine,
|
||||
&gpu_cfg) != 0) {
|
||||
ds4_dist_options_free(cfg.dist);
|
||||
free(cfg.prompt_owned);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} else if (ds4_engine_open(&engine, &cfg.engine) != 0) {
|
||||
ds4_dist_options_free(cfg.dist);
|
||||
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);
|
||||
ds4_engine_close(engine);
|
||||
ds4_dist_options_free(cfg.dist);
|
||||
free(cfg.prompt_owned);
|
||||
return rc;
|
||||
}
|
||||
ds4_tp *tp_leader = NULL;
|
||||
if (cfg.engine.tp.role == DS4_TP_LEADER) {
|
||||
char tp_err[256] = "";
|
||||
ds4_tp_identity tp_id = {
|
||||
.gguf_bytes = ds4_engine_model_bytes(engine),
|
||||
.model_id = (uint32_t)ds4_engine_model_id(engine),
|
||||
.n_layer = (uint32_t)ds4_engine_layer_count(engine),
|
||||
.n_embd = (uint32_t)ds4_engine_embd_dim(engine),
|
||||
.n_vocab = (uint32_t)ds4_engine_vocab_size(engine),
|
||||
.quant_bits = (uint32_t)ds4_engine_routed_quant_bits(engine),
|
||||
.ctx_size = (uint32_t)cfg.gen.ctx_size,
|
||||
};
|
||||
ds4_engine_tp_gate_schedule(engine,
|
||||
&tp_id.gate_slot_start,
|
||||
&tp_id.gate_slot_step,
|
||||
&tp_id.gates_per_token,
|
||||
tp_id.gate_slot_mask);
|
||||
if (!ds4_tp_create(&tp_leader, &cfg.engine.tp, &tp_id, tp_err, sizeof(tp_err)) ||
|
||||
!ds4_engine_tp_bind(engine, tp_leader, tp_err, sizeof(tp_err))) {
|
||||
fprintf(stderr, "ds4: %s\n", tp_err);
|
||||
ds4_tp_free(tp_leader);
|
||||
ds4_engine_close(engine);
|
||||
ds4_dist_options_free(cfg.dist);
|
||||
free(cfg.prompt_owned);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (cfg.dist && cfg.dist->role == DS4_DISTRIBUTED_WORKER) {
|
||||
ds4_dist_generation_options dist_gen = {
|
||||
.prompt = cfg.gen.prompt,
|
||||
@@ -1984,6 +2404,10 @@ int main(int argc, char **argv) {
|
||||
return rc;
|
||||
}
|
||||
if (!cfg.inspect) {
|
||||
log_context_memory(cfg.engine.backend,
|
||||
cfg.gen.ctx_size,
|
||||
ds4_engine_prefill_chunk(engine),
|
||||
cfg.engine.ssd_streaming);
|
||||
cli_warn_think_max_downgraded(&cfg.gen, "--think-max");
|
||||
}
|
||||
int rc = 0;
|
||||
@@ -1995,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) {
|
||||
@@ -2003,8 +2428,11 @@ int main(int argc, char **argv) {
|
||||
} else {
|
||||
rc = run_generation(engine, &cfg);
|
||||
}
|
||||
if (tp_leader) ds4_tp_send_stop(tp_leader);
|
||||
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;
|
||||
}
|
||||
+26366
-3208
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
|
||||
@@ -7012,6 +7012,17 @@ static int dist_worker_handle_snapshot_load(
|
||||
snprintf(err, sizeof(err), "invalid distributed snapshot load header");
|
||||
}
|
||||
|
||||
/* Reject a token_count larger than the worker context up front, before the
|
||||
* allocation and the read loop below. token_count is only bounded to
|
||||
* ~UINT32_MAX/4 by the header check above, so without this a peer could
|
||||
* drive a multi-GB malloc and a full-length socket read before the
|
||||
* matching-state check further down (which also tests token_count > ctx_size)
|
||||
* finally rejects the request. */
|
||||
if (!err[0] && begin.token_count > (uint32_t)state->ctx_size) {
|
||||
dist_discard_bytes(upstream->fd, body_bytes);
|
||||
snprintf(err, sizeof(err), "snapshot token count exceeds worker context size");
|
||||
}
|
||||
|
||||
int *tokens = NULL;
|
||||
if (!err[0]) {
|
||||
tokens = malloc((size_t)begin.token_count * sizeof(tokens[0]));
|
||||
|
||||
+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
|
||||
+788
-102
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
|
||||
@@ -0,0 +1,607 @@
|
||||
/* GLM-5.3 vision operations shared by CUDA and ROCm. */
|
||||
|
||||
#ifndef DS4_GLM53_VISION_TYPES_DEFINED
|
||||
#define DS4_GLM53_VISION_TYPES_DEFINED
|
||||
#define DS4_GLM53_VISION_LAYERS 24u
|
||||
typedef struct {
|
||||
uint64_t norm1;
|
||||
uint64_t qkv_weight;
|
||||
uint64_t qkv_bias;
|
||||
uint64_t q_norm;
|
||||
uint64_t k_norm;
|
||||
uint64_t attn_proj_weight;
|
||||
uint64_t attn_proj_bias;
|
||||
uint64_t norm2;
|
||||
uint64_t gate_weight;
|
||||
uint64_t gate_bias;
|
||||
uint64_t up_weight;
|
||||
uint64_t up_bias;
|
||||
uint64_t down_weight;
|
||||
uint64_t down_bias;
|
||||
} ds4_glm53_vision_layer_weights;
|
||||
|
||||
typedef struct {
|
||||
uint64_t patch_weight;
|
||||
uint64_t patch_bias;
|
||||
uint64_t post_norm;
|
||||
uint64_t downsample_weight;
|
||||
uint64_t downsample_bias;
|
||||
uint64_t merger_proj;
|
||||
uint64_t merger_norm;
|
||||
uint64_t merger_norm_bias;
|
||||
uint64_t merger_gate;
|
||||
uint64_t merger_up;
|
||||
uint64_t merger_down;
|
||||
ds4_glm53_vision_layer_weights layer[DS4_GLM53_VISION_LAYERS];
|
||||
} ds4_glm53_vision_weights;
|
||||
#endif
|
||||
|
||||
#ifndef DS4_GLM53_VISION_STREAM
|
||||
#define DS4_GLM53_VISION_STREAM 0
|
||||
#endif
|
||||
|
||||
__device__ __forceinline__ static float glm53_vision_bf16(
|
||||
const uint16_t *p) {
|
||||
return __uint_as_float((uint32_t)(*p) << 16);
|
||||
}
|
||||
|
||||
__device__ __forceinline__ static float glm53_vision_erf_approx(float x) {
|
||||
const float sign = x < 0.0f ? -1.0f : 1.0f;
|
||||
const float a = fabsf(x);
|
||||
const float t = 1.0f / (1.0f + 0.3275911f * a);
|
||||
const float p = (((((1.061405429f * t - 1.453152027f) * t) +
|
||||
1.421413741f) * t - 0.284496736f) * t +
|
||||
0.254829592f) * t;
|
||||
return sign * (1.0f - p * expf(-a * a));
|
||||
}
|
||||
|
||||
__global__ static void glm53_vision_bias_kernel(
|
||||
float *x,
|
||||
const uint16_t *bias,
|
||||
const float *residual,
|
||||
uint64_t count,
|
||||
uint32_t width) {
|
||||
const uint64_t i = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i >= count) return;
|
||||
float v = x[i] + glm53_vision_bf16(bias + i % width);
|
||||
if (residual) v += residual[i];
|
||||
x[i] = v;
|
||||
}
|
||||
|
||||
__global__ static void glm53_vision_rms_kernel(
|
||||
float *out,
|
||||
const float *x,
|
||||
const uint16_t *weight,
|
||||
uint32_t width,
|
||||
float eps) {
|
||||
__shared__ float partial[256];
|
||||
const uint32_t row = blockIdx.x;
|
||||
const uint32_t tid = threadIdx.x;
|
||||
const float *xr = x + (uint64_t)row * width;
|
||||
float *yr = out + (uint64_t)row * width;
|
||||
float sum = 0.0f;
|
||||
for (uint32_t d = tid; d < width; d += blockDim.x) {
|
||||
sum = fmaf(xr[d], xr[d], sum);
|
||||
}
|
||||
partial[tid] = sum;
|
||||
__syncthreads();
|
||||
for (uint32_t stride = blockDim.x / 2u; stride != 0u; stride >>= 1u) {
|
||||
if (tid < stride) partial[tid] += partial[tid + stride];
|
||||
__syncthreads();
|
||||
}
|
||||
const float inv = rsqrtf(partial[0] / (float)width + eps);
|
||||
for (uint32_t d = tid; d < width; d += blockDim.x) {
|
||||
yr[d] = xr[d] * inv * glm53_vision_bf16(weight + d);
|
||||
}
|
||||
}
|
||||
|
||||
__global__ static void glm53_vision_qkv_rope_kernel(
|
||||
float *q,
|
||||
float *k,
|
||||
float *v,
|
||||
const float *qkv,
|
||||
const uint16_t *bias,
|
||||
const uint16_t *q_weight,
|
||||
const uint16_t *k_weight,
|
||||
uint32_t rows,
|
||||
uint32_t grid_w,
|
||||
float eps) {
|
||||
__shared__ float qsum[32];
|
||||
__shared__ float ksum[32];
|
||||
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;
|
||||
float q0 = qkv[qkv_base + lane] +
|
||||
glm53_vision_bf16(bias + (uint64_t)head * 64u + lane);
|
||||
float q1 = qkv[qkv_base + lane + 32u] +
|
||||
glm53_vision_bf16(bias + (uint64_t)head * 64u + lane + 32u);
|
||||
float k0 = qkv[qkv_base + 1024u + lane] +
|
||||
glm53_vision_bf16(bias + 1024u +
|
||||
(uint64_t)head * 64u + lane);
|
||||
float k1 = qkv[qkv_base + 1024u + lane + 32u] +
|
||||
glm53_vision_bf16(bias + 1024u +
|
||||
(uint64_t)head * 64u + lane + 32u);
|
||||
qsum[lane] = fmaf(q0, q0, q1 * q1);
|
||||
ksum[lane] = fmaf(k0, k0, k1 * k1);
|
||||
__syncthreads();
|
||||
for (uint32_t stride = 16u; stride != 0u; stride >>= 1u) {
|
||||
if (lane < stride) {
|
||||
qsum[lane] += qsum[lane + stride];
|
||||
ksum[lane] += ksum[lane + stride];
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
const float qinv = rsqrtf(qsum[0] / 64.0f + eps);
|
||||
const float kinv = rsqrtf(ksum[0] / 64.0f + eps);
|
||||
q0 *= qinv * glm53_vision_bf16(q_weight + lane);
|
||||
q1 *= qinv * glm53_vision_bf16(q_weight + lane + 32u);
|
||||
k0 *= kinv * glm53_vision_bf16(k_weight + lane);
|
||||
k1 *= kinv * glm53_vision_bf16(k_weight + lane + 32u);
|
||||
|
||||
const uint32_t merge_w = grid_w / 2u;
|
||||
const uint32_t group_index = row / 4u;
|
||||
const uint32_t within = row & 3u;
|
||||
const uint32_t py = (group_index / merge_w) * 2u + within / 2u;
|
||||
const uint32_t px = (group_index % merge_w) * 2u + within % 2u;
|
||||
const uint32_t freq_index = lane & 15u;
|
||||
const uint32_t pos = lane < 16u ? py : px;
|
||||
const float inv_freq = powf(10000.0f, -(float)freq_index / 16.0f);
|
||||
const float angle = (float)pos * inv_freq;
|
||||
const float cs = cosf(angle);
|
||||
const float sn = sinf(angle);
|
||||
q[out_base + lane] = q0 * cs - q1 * sn;
|
||||
q[out_base + lane + 32u] = q1 * cs + q0 * sn;
|
||||
k[out_base + lane] = k0 * cs - k1 * sn;
|
||||
k[out_base + lane + 32u] = k1 * cs + k0 * sn;
|
||||
v[out_base + lane] = qkv[qkv_base + 2048u + lane] +
|
||||
glm53_vision_bf16(bias + 2048u +
|
||||
(uint64_t)head * 64u + lane);
|
||||
v[out_base + lane + 32u] = qkv[qkv_base + 2048u + lane + 32u] +
|
||||
glm53_vision_bf16(
|
||||
bias + 2048u +
|
||||
(uint64_t)head * 64u + lane + 32u);
|
||||
}
|
||||
|
||||
__global__ static void glm53_vision_attention_kernel(
|
||||
float *out,
|
||||
const float *q,
|
||||
const float *k,
|
||||
const float *v,
|
||||
uint32_t rows) {
|
||||
__shared__ float dot[32];
|
||||
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 base = (uint64_t)row * 1024u +
|
||||
(uint64_t)head * 64u;
|
||||
const float q0 = q[base + lane];
|
||||
const float q1 = q[base + lane + 32u];
|
||||
float acc0 = 0.0f;
|
||||
float acc1 = 0.0f;
|
||||
float max_score = -INFINITY;
|
||||
float denom = 0.0f;
|
||||
for (uint32_t key_row = 0; key_row < rows; key_row++) {
|
||||
const uint64_t kb = (uint64_t)key_row * 1024u +
|
||||
(uint64_t)head * 64u;
|
||||
dot[lane] = q0 * k[kb + lane] + q1 * k[kb + lane + 32u];
|
||||
__syncthreads();
|
||||
for (uint32_t stride = 16u; stride != 0u; stride >>= 1u) {
|
||||
if (lane < stride) dot[lane] += dot[lane + stride];
|
||||
__syncthreads();
|
||||
}
|
||||
const float score = dot[0] * 0.125f;
|
||||
const float next_max = fmaxf(max_score, score);
|
||||
const float old_scale = key_row == 0u ? 0.0f :
|
||||
expf(max_score - next_max);
|
||||
const float new_scale = expf(score - next_max);
|
||||
denom = denom * old_scale + new_scale;
|
||||
acc0 = acc0 * old_scale + new_scale * v[kb + lane];
|
||||
acc1 = acc1 * old_scale + new_scale * v[kb + lane + 32u];
|
||||
max_score = next_max;
|
||||
__syncthreads();
|
||||
}
|
||||
out[base + lane] = acc0 / denom;
|
||||
out[base + lane + 32u] = acc1 / denom;
|
||||
}
|
||||
|
||||
__global__ static void glm53_vision_swiglu_bias_kernel(
|
||||
float *out,
|
||||
const float *gate,
|
||||
const uint16_t *gate_bias,
|
||||
const float *up,
|
||||
const uint16_t *up_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 = (uint32_t)(i % width);
|
||||
const float g = fminf(gate[i] + glm53_vision_bf16(gate_bias + d), 10.0f);
|
||||
const float u = fminf(fmaxf(up[i] + glm53_vision_bf16(up_bias + d),
|
||||
-10.0f), 10.0f);
|
||||
out[i] = (g / (1.0f + expf(-g))) * u;
|
||||
}
|
||||
|
||||
__global__ static void glm53_vision_reorder_kernel(
|
||||
float *out,
|
||||
const float *x,
|
||||
uint64_t count) {
|
||||
const uint64_t i = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i >= count) return;
|
||||
const uint32_t d = (uint32_t)(i % 4096u);
|
||||
const uint64_t row = i / 4096u;
|
||||
const uint32_t channel = d / 4u;
|
||||
const uint32_t within = d & 3u;
|
||||
out[i] = x[(row * 4u + within) * 1024u + channel];
|
||||
}
|
||||
|
||||
__global__ static void glm53_vision_layernorm_gelu_kernel(
|
||||
float *out,
|
||||
const float *x,
|
||||
const uint16_t *weight,
|
||||
const uint16_t *bias,
|
||||
uint32_t width,
|
||||
float eps) {
|
||||
__shared__ float partial[256];
|
||||
const uint32_t row = blockIdx.x;
|
||||
const uint32_t tid = threadIdx.x;
|
||||
const float *xr = x + (uint64_t)row * width;
|
||||
float *yr = out + (uint64_t)row * width;
|
||||
float sum = 0.0f;
|
||||
for (uint32_t d = tid; d < width; d += blockDim.x) sum += xr[d];
|
||||
partial[tid] = sum;
|
||||
__syncthreads();
|
||||
for (uint32_t stride = blockDim.x / 2u; stride != 0u; stride >>= 1u) {
|
||||
if (tid < stride) partial[tid] += partial[tid + stride];
|
||||
__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;
|
||||
var = fmaf(centered, centered, var);
|
||||
}
|
||||
partial[tid] = var;
|
||||
__syncthreads();
|
||||
for (uint32_t stride = blockDim.x / 2u; stride != 0u; stride >>= 1u) {
|
||||
if (tid < stride) partial[tid] += partial[tid + stride];
|
||||
__syncthreads();
|
||||
}
|
||||
const float inv = rsqrtf(partial[0] / (float)width + eps);
|
||||
const float inv_sqrt2 = 0.7071067811865475f;
|
||||
for (uint32_t d = tid; d < width; d += blockDim.x) {
|
||||
float value = (xr[d] - mean) * inv * glm53_vision_bf16(weight + d) +
|
||||
glm53_vision_bf16(bias + d);
|
||||
yr[d] = 0.5f * value *
|
||||
(1.0f + glm53_vision_erf_approx(value * inv_sqrt2));
|
||||
}
|
||||
}
|
||||
|
||||
__global__ static void glm53_vision_scatter_hc_kernel(
|
||||
float *hc,
|
||||
const float *image,
|
||||
uint32_t dst_row,
|
||||
uint32_t image_row,
|
||||
uint32_t rows,
|
||||
uint32_t total_rows,
|
||||
uint32_t width,
|
||||
uint32_t n_hc) {
|
||||
const uint64_t i = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x;
|
||||
const uint64_t count = (uint64_t)rows * n_hc * width;
|
||||
if (i >= count) return;
|
||||
const uint32_t d = (uint32_t)(i % width);
|
||||
const uint64_t linear_row = i / width;
|
||||
const uint32_t image_delta = (uint32_t)(linear_row / n_hc);
|
||||
const uint32_t hc_index = (uint32_t)(linear_row % n_hc);
|
||||
if (dst_row + image_delta >= total_rows) return;
|
||||
const uint64_t dst = ((uint64_t)(dst_row + image_delta) * n_hc +
|
||||
hc_index) * width + d;
|
||||
const uint64_t src = (uint64_t)(image_row + image_delta) * width + d;
|
||||
hc[dst] = image[src];
|
||||
}
|
||||
|
||||
static const uint16_t *glm53_vision_weight(
|
||||
const void *model_map,
|
||||
uint64_t model_size,
|
||||
uint64_t offset,
|
||||
uint64_t elements,
|
||||
const char *label) {
|
||||
if (!model_map || elements > UINT64_MAX / sizeof(uint16_t) ||
|
||||
offset > model_size) return NULL;
|
||||
const uint64_t bytes = elements * sizeof(uint16_t);
|
||||
if (bytes > model_size - offset) return NULL;
|
||||
#if defined(__HIP_PLATFORM_AMD__)
|
||||
return (const uint16_t *)cuda_model_range_ptr(
|
||||
model_map, offset, bytes, label);
|
||||
#else
|
||||
return (const uint16_t *)cuda_resolve_weight_ptr(
|
||||
model_map, offset, bytes, 0, label);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int glm53_vision_launch_ok(const char *label) {
|
||||
return cuda_ok(cudaGetLastError(), label);
|
||||
}
|
||||
|
||||
extern "C" int ds4_gpu_glm53_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_glm53_vision_weights *weights) {
|
||||
if (!out || !patches || !model_map || !weights || grid_h == 0u ||
|
||||
grid_w == 0u || (grid_h & 1u) != 0u || (grid_w & 1u) != 0u ||
|
||||
grid_h > UINT32_MAX / grid_w) return 0;
|
||||
const uint32_t rows = grid_h * grid_w;
|
||||
const uint32_t merged_rows = rows / 4u;
|
||||
const uint64_t row1024 = (uint64_t)rows * 1024u;
|
||||
const uint64_t row3072 = (uint64_t)rows * 3072u;
|
||||
const uint64_t row4096 = (uint64_t)rows * 4096u;
|
||||
const uint64_t merged10240 = (uint64_t)merged_rows * 10240u;
|
||||
if (row4096 > SIZE_MAX / sizeof(float) ||
|
||||
merged10240 > SIZE_MAX / sizeof(float) ||
|
||||
merged10240 > UINT32_MAX) 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 *gate = NULL, *up = NULL, *mid = NULL;
|
||||
ds4_gpu_tensor *cur = NULL, *tmp = NULL;
|
||||
const uint16_t *bias = NULL;
|
||||
const uint16_t *merger_norm = NULL, *merger_bias = NULL;
|
||||
const uint64_t merged4096 = (uint64_t)merged_rows * 4096u;
|
||||
int ok = 0;
|
||||
#define VISION_ALLOC(name_, count_) do { \
|
||||
name_ = ds4_gpu_tensor_alloc((count_) * sizeof(float)); \
|
||||
if (!(name_)) goto cleanup; \
|
||||
} while (0)
|
||||
VISION_ALLOC(patch, (uint64_t)rows * 1176u);
|
||||
VISION_ALLOC(a, row1024);
|
||||
VISION_ALLOC(b, row1024);
|
||||
VISION_ALLOC(qkv, row3072);
|
||||
VISION_ALLOC(q, row1024);
|
||||
VISION_ALLOC(k, row1024);
|
||||
VISION_ALLOC(v, row1024);
|
||||
VISION_ALLOC(attn, row1024);
|
||||
VISION_ALLOC(gate, row4096);
|
||||
VISION_ALLOC(up, row4096);
|
||||
VISION_ALLOC(mid, row4096);
|
||||
#undef VISION_ALLOC
|
||||
if (!ds4_gpu_tensor_write(patch, 0, patches,
|
||||
(uint64_t)rows * 1176u * sizeof(float)) ||
|
||||
!ds4_gpu_begin_commands()) goto cleanup;
|
||||
ok = ds4_gpu_glm53_matmul_bf16(
|
||||
a, model_map, model_size, weights->patch_weight,
|
||||
1176u, 1024u, patch, rows);
|
||||
if (ok) {
|
||||
bias = glm53_vision_weight(model_map, model_size,
|
||||
weights->patch_bias, 1024u, "vision patch bias");
|
||||
if (!bias) ok = 0;
|
||||
}
|
||||
if (ok) {
|
||||
glm53_vision_bias_kernel<<<
|
||||
(unsigned)((row1024 + 255u) / 256u), 256u, 0,
|
||||
DS4_GLM53_VISION_STREAM>>>(
|
||||
(float *)a->ptr, bias, NULL, row1024, 1024u);
|
||||
ok = glm53_vision_launch_ok("GLM-5.3 vision patch bias");
|
||||
}
|
||||
|
||||
cur = a;
|
||||
tmp = b;
|
||||
for (uint32_t il = 0; ok && il < DS4_GLM53_VISION_LAYERS; il++) {
|
||||
const ds4_glm53_vision_layer_weights *w = &weights->layer[il];
|
||||
const uint16_t *norm = glm53_vision_weight(
|
||||
model_map, model_size, w->norm1, 1024u, "vision norm1");
|
||||
if (!norm) { ok = 0; break; }
|
||||
glm53_vision_rms_kernel<<<rows, 256u, 0,
|
||||
DS4_GLM53_VISION_STREAM>>>(
|
||||
(float *)tmp->ptr, (const float *)cur->ptr,
|
||||
norm, 1024u, 1.0e-5f);
|
||||
ok = glm53_vision_launch_ok("GLM-5.3 vision norm1");
|
||||
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, *q_norm = NULL, *k_norm = NULL;
|
||||
if (ok) {
|
||||
qkv_bias = glm53_vision_weight(model_map, model_size,
|
||||
w->qkv_bias, 3072u, "vision QKV bias");
|
||||
q_norm = glm53_vision_weight(model_map, model_size,
|
||||
w->q_norm, 64u, "vision Q norm");
|
||||
k_norm = glm53_vision_weight(model_map, model_size,
|
||||
w->k_norm, 64u, "vision K norm");
|
||||
if (!qkv_bias || !q_norm || !k_norm) ok = 0;
|
||||
}
|
||||
if (ok) {
|
||||
glm53_vision_qkv_rope_kernel<<<dim3(rows, 16u, 1u), 32u, 0,
|
||||
DS4_GLM53_VISION_STREAM>>>(
|
||||
(float *)q->ptr, (float *)k->ptr, (float *)v->ptr,
|
||||
(const float *)qkv->ptr, qkv_bias, q_norm, k_norm,
|
||||
rows, grid_w, 1.0e-5f);
|
||||
ok = glm53_vision_launch_ok("GLM-5.3 vision QKV");
|
||||
}
|
||||
if (ok) {
|
||||
glm53_vision_attention_kernel<<<dim3(rows, 16u, 1u), 32u, 0,
|
||||
DS4_GLM53_VISION_STREAM>>>(
|
||||
(float *)attn->ptr, (const float *)q->ptr,
|
||||
(const float *)k->ptr, (const float *)v->ptr, rows);
|
||||
ok = glm53_vision_launch_ok("GLM-5.3 vision attention");
|
||||
}
|
||||
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, "vision attention bias");
|
||||
if (!bias) ok = 0;
|
||||
}
|
||||
if (ok) {
|
||||
glm53_vision_bias_kernel<<<
|
||||
(unsigned)((row1024 + 255u) / 256u), 256u, 0,
|
||||
DS4_GLM53_VISION_STREAM>>>(
|
||||
(float *)tmp->ptr, bias, (const float *)cur->ptr,
|
||||
row1024, 1024u);
|
||||
ok = glm53_vision_launch_ok("GLM-5.3 vision attention residual");
|
||||
}
|
||||
ds4_gpu_tensor *swap = cur; cur = tmp; tmp = swap;
|
||||
|
||||
if (ok) {
|
||||
norm = glm53_vision_weight(model_map, model_size,
|
||||
w->norm2, 1024u, "vision norm2");
|
||||
if (!norm) ok = 0;
|
||||
}
|
||||
if (ok) {
|
||||
glm53_vision_rms_kernel<<<rows, 256u, 0,
|
||||
DS4_GLM53_VISION_STREAM>>>(
|
||||
(float *)tmp->ptr, (const float *)cur->ptr,
|
||||
norm, 1024u, 1.0e-5f);
|
||||
ok = glm53_vision_launch_ok("GLM-5.3 vision norm2");
|
||||
}
|
||||
if (ok) ok = ds4_gpu_glm53_matmul_bf16(
|
||||
gate, model_map, model_size, w->gate_weight,
|
||||
1024u, 4096u, tmp, rows);
|
||||
if (ok) ok = ds4_gpu_glm53_matmul_bf16(
|
||||
up, model_map, model_size, w->up_weight,
|
||||
1024u, 4096u, tmp, rows);
|
||||
const uint16_t *gate_bias = NULL, *up_bias = NULL;
|
||||
if (ok) {
|
||||
gate_bias = glm53_vision_weight(model_map, model_size,
|
||||
w->gate_bias, 4096u, "vision gate bias");
|
||||
up_bias = glm53_vision_weight(model_map, model_size,
|
||||
w->up_bias, 4096u, "vision up bias");
|
||||
if (!gate_bias || !up_bias) ok = 0;
|
||||
}
|
||||
if (ok) {
|
||||
glm53_vision_swiglu_bias_kernel<<<
|
||||
(unsigned)((row4096 + 255u) / 256u), 256u, 0,
|
||||
DS4_GLM53_VISION_STREAM>>>(
|
||||
(float *)mid->ptr, (const float *)gate->ptr, gate_bias,
|
||||
(const float *)up->ptr, up_bias, row4096, 4096u);
|
||||
ok = glm53_vision_launch_ok("GLM-5.3 vision SwiGLU");
|
||||
}
|
||||
if (ok) ok = ds4_gpu_glm53_matmul_bf16(
|
||||
tmp, model_map, model_size, w->down_weight,
|
||||
4096u, 1024u, mid, rows);
|
||||
if (ok) {
|
||||
bias = glm53_vision_weight(model_map, model_size,
|
||||
w->down_bias, 1024u, "vision down bias");
|
||||
if (!bias) ok = 0;
|
||||
}
|
||||
if (ok) {
|
||||
glm53_vision_bias_kernel<<<
|
||||
(unsigned)((row1024 + 255u) / 256u), 256u, 0,
|
||||
DS4_GLM53_VISION_STREAM>>>(
|
||||
(float *)tmp->ptr, bias, (const float *)cur->ptr,
|
||||
row1024, 1024u);
|
||||
ok = glm53_vision_launch_ok("GLM-5.3 vision down residual");
|
||||
}
|
||||
swap = cur; cur = tmp; tmp = swap;
|
||||
}
|
||||
if (ok) {
|
||||
const uint16_t *norm = glm53_vision_weight(model_map, model_size,
|
||||
weights->post_norm, 1024u, "vision post norm");
|
||||
if (!norm) ok = 0;
|
||||
else {
|
||||
glm53_vision_rms_kernel<<<rows, 256u, 0,
|
||||
DS4_GLM53_VISION_STREAM>>>(
|
||||
(float *)tmp->ptr, (const float *)cur->ptr,
|
||||
norm, 1024u, 1.0e-5f);
|
||||
ok = glm53_vision_launch_ok("GLM-5.3 vision post norm");
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
glm53_vision_reorder_kernel<<<
|
||||
(unsigned)((merged4096 + 255u) / 256u), 256u, 0,
|
||||
DS4_GLM53_VISION_STREAM>>>(
|
||||
(float *)cur->ptr, (const float *)tmp->ptr, merged4096);
|
||||
ok = glm53_vision_launch_ok("GLM-5.3 vision downsample reorder");
|
||||
}
|
||||
if (ok) ok = ds4_gpu_glm53_matmul_bf16(
|
||||
tmp, model_map, model_size, weights->downsample_weight,
|
||||
4096u, 4096u, cur, merged_rows);
|
||||
if (ok) {
|
||||
bias = glm53_vision_weight(model_map, model_size,
|
||||
weights->downsample_bias, 4096u, "vision downsample bias");
|
||||
if (!bias) ok = 0;
|
||||
}
|
||||
if (ok) {
|
||||
glm53_vision_bias_kernel<<<
|
||||
(unsigned)((merged4096 + 255u) / 256u), 256u, 0,
|
||||
DS4_GLM53_VISION_STREAM>>>(
|
||||
(float *)tmp->ptr, bias, NULL, merged4096, 4096u);
|
||||
ok = glm53_vision_launch_ok("GLM-5.3 vision downsample bias");
|
||||
}
|
||||
if (ok) ok = ds4_gpu_glm53_matmul_bf16(
|
||||
cur, model_map, model_size, weights->merger_proj,
|
||||
4096u, 4096u, tmp, merged_rows);
|
||||
if (ok) {
|
||||
merger_norm = glm53_vision_weight(model_map, model_size,
|
||||
weights->merger_norm, 4096u, "vision merger norm");
|
||||
merger_bias = glm53_vision_weight(model_map, model_size,
|
||||
weights->merger_norm_bias, 4096u, "vision merger norm bias");
|
||||
if (!merger_norm || !merger_bias) ok = 0;
|
||||
}
|
||||
if (ok) {
|
||||
glm53_vision_layernorm_gelu_kernel<<<merged_rows, 256u, 0,
|
||||
DS4_GLM53_VISION_STREAM>>>(
|
||||
(float *)tmp->ptr, (const float *)cur->ptr,
|
||||
merger_norm, merger_bias, 4096u, 1.0e-5f);
|
||||
ok = glm53_vision_launch_ok("GLM-5.3 vision merger norm");
|
||||
}
|
||||
if (ok) ok = ds4_gpu_glm53_matmul_bf16(
|
||||
gate, model_map, model_size, weights->merger_gate,
|
||||
4096u, 10240u, tmp, merged_rows);
|
||||
if (ok) ok = ds4_gpu_glm53_matmul_bf16(
|
||||
up, model_map, model_size, weights->merger_up,
|
||||
4096u, 10240u, tmp, merged_rows);
|
||||
if (ok) ok = ds4_gpu_swiglu_tensor(
|
||||
mid, gate, up, (uint32_t)merged10240, 10.0f, 1.0f);
|
||||
if (ok) ok = ds4_gpu_glm53_matmul_bf16(
|
||||
cur, model_map, model_size, weights->merger_down,
|
||||
10240u, 4096u, mid, merged_rows);
|
||||
if (ds4_gpu_end_commands() == 0) ok = 0;
|
||||
if (ok) ok = ds4_gpu_tensor_read(
|
||||
cur, 0, out, merged4096 * sizeof(float));
|
||||
|
||||
cleanup:
|
||||
ds4_gpu_tensor_free(mid);
|
||||
ds4_gpu_tensor_free(up);
|
||||
ds4_gpu_tensor_free(gate);
|
||||
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;
|
||||
}
|
||||
|
||||
extern "C" int ds4_gpu_glm53_scatter_image_hc(
|
||||
ds4_gpu_tensor *hc,
|
||||
const ds4_gpu_tensor *image,
|
||||
uint32_t dst_row,
|
||||
uint32_t image_row,
|
||||
uint32_t rows,
|
||||
uint32_t total_rows,
|
||||
uint32_t n_embd,
|
||||
uint32_t n_hc) {
|
||||
if (!hc || !image || rows == 0u || n_embd == 0u || n_hc == 0u ||
|
||||
dst_row > total_rows || rows > total_rows - dst_row) return 0;
|
||||
const uint64_t count = (uint64_t)rows * n_hc * n_embd;
|
||||
glm53_vision_scatter_hc_kernel<<<
|
||||
(unsigned)((count + 255u) / 256u), 256u, 0,
|
||||
DS4_GLM53_VISION_STREAM>>>(
|
||||
(float *)hc->ptr, (const float *)image->ptr,
|
||||
dst_row, image_row, rows, total_rows, n_embd, n_hc);
|
||||
return glm53_vision_launch_ok("GLM-5.3 vision scatter");
|
||||
}
|
||||
|
||||
#undef DS4_GLM53_VISION_STREAM
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
/* ds4_gpu_args.c — shared parser for --gpu-vram and --gpu-devices.
|
||||
*
|
||||
* See ds4_gpu_args.h. The actual CUDA probing for the "auto" branch
|
||||
* lives in ds4_cuda.cu so this .c file never includes <cuda_runtime.h>
|
||||
* directly (mirroring the rest of the tree's separation between .c and
|
||||
* .cu compilation units).
|
||||
*/
|
||||
#include "ds4_gpu_args.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define DS4_GPU_ARGS_DEFAULT_SAFETY_MARGIN ((size_t)512 * 1024 * 1024)
|
||||
|
||||
/* Helper: copy at most outlen-1 bytes of msg into out and NUL-terminate. */
|
||||
static void errbuf_set(char *out, size_t outlen, const char *msg) {
|
||||
if (!out || outlen == 0) return;
|
||||
size_t n = strlen(msg);
|
||||
if (n >= outlen) n = outlen - 1;
|
||||
memcpy(out, msg, n);
|
||||
out[n] = '\0';
|
||||
}
|
||||
|
||||
static void errbuf_setf(char *out, size_t outlen, const char *fmt, ...) {
|
||||
if (!out || outlen == 0) return;
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(out, outlen, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/* Maximum per-entry values. Generous, but small enough to keep all
|
||||
* downstream arithmetic safely inside size_t/int. */
|
||||
#define DS4_GPU_ARGS_MAX_VRAM_GB 16384 /* 16 TiB per device */
|
||||
#define DS4_GPU_ARGS_MAX_DEVICE_INDEX 1023 /* CUDA visible-device ID cap */
|
||||
|
||||
/* Parse a comma-separated list of nonnegative integers into out_count
|
||||
* entries of out[]. Returns 0 on success, nonzero on parse error.
|
||||
* Empty entries (",,") or trailing/leading commas error. Per-entry
|
||||
* upper bound enforced via max_value (use LONG_MAX for "no cap"). */
|
||||
static int parse_csv_int_list(const char *s,
|
||||
long *out,
|
||||
int *out_count,
|
||||
int max_count,
|
||||
long max_value,
|
||||
char *errbuf,
|
||||
size_t errbuflen,
|
||||
const char *what) {
|
||||
*out_count = 0;
|
||||
if (!s || !*s) {
|
||||
errbuf_setf(errbuf, errbuflen, "%s: empty value", what);
|
||||
return 1;
|
||||
}
|
||||
const char *p = s;
|
||||
while (*p) {
|
||||
/* Disallow leading whitespace inside the list. */
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
char *end = NULL;
|
||||
errno = 0;
|
||||
long v = strtol(p, &end, 10);
|
||||
if (end == p) {
|
||||
errbuf_setf(errbuf, errbuflen, "%s: not a number: '%s'", what, p);
|
||||
return 1;
|
||||
}
|
||||
if (errno != 0 || v < 0 || v > max_value) {
|
||||
errbuf_setf(errbuf, errbuflen,
|
||||
"%s: out-of-range value: '%s' (allowed 0..%ld)",
|
||||
what, p, max_value);
|
||||
return 1;
|
||||
}
|
||||
if (*out_count >= max_count) {
|
||||
errbuf_setf(errbuf, errbuflen, "%s: too many entries (max %d)", what, max_count);
|
||||
return 1;
|
||||
}
|
||||
out[*out_count] = v;
|
||||
(*out_count)++;
|
||||
while (*end == ' ' || *end == '\t') end++;
|
||||
if (*end == ',') {
|
||||
end++;
|
||||
if (*end == '\0' || *end == ',') {
|
||||
errbuf_setf(errbuf, errbuflen, "%s: empty entry near '%s'", what, end);
|
||||
return 1;
|
||||
}
|
||||
} else if (*end != '\0') {
|
||||
errbuf_setf(errbuf, errbuflen, "%s: junk after value: '%s'", what, end);
|
||||
return 1;
|
||||
}
|
||||
p = end;
|
||||
}
|
||||
if (*out_count == 0) {
|
||||
errbuf_setf(errbuf, errbuflen, "%s: no values parsed", what);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int parse_gpu_vram_arg(const char *vram_arg,
|
||||
const char *devices_arg,
|
||||
ds4_gpu_config *out,
|
||||
bool *out_skip_cuda,
|
||||
char *errbuf,
|
||||
size_t errbuflen) {
|
||||
if (!out || !out_skip_cuda) {
|
||||
errbuf_set(errbuf, errbuflen, "internal: NULL out parameter");
|
||||
return 1;
|
||||
}
|
||||
*out_skip_cuda = false;
|
||||
/* Caller zeroed *out — just confirm. */
|
||||
out->n_gpus = 0;
|
||||
out->safety_margin_bytes = DS4_GPU_ARGS_DEFAULT_SAFETY_MARGIN;
|
||||
|
||||
/* Parse devices_arg first so we have the filter when handling "auto". */
|
||||
long dev_list[DS4_MAX_GPUS] = {0};
|
||||
int dev_count = 0;
|
||||
if (devices_arg) {
|
||||
if (parse_csv_int_list(devices_arg, dev_list, &dev_count,
|
||||
DS4_MAX_GPUS,
|
||||
DS4_GPU_ARGS_MAX_DEVICE_INDEX,
|
||||
errbuf, errbuflen,
|
||||
"--gpu-devices") != 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Case 1: vram_arg unset. */
|
||||
if (vram_arg == NULL) {
|
||||
if (devices_arg == NULL) {
|
||||
errbuf_set(errbuf, errbuflen, "internal: parse_gpu_vram_arg called with both args NULL");
|
||||
return 1;
|
||||
}
|
||||
/* --gpu-devices given without --gpu-vram: implicit auto with filter. */
|
||||
vram_arg = "auto";
|
||||
}
|
||||
|
||||
/* Case 2: --gpu-vram 0 — short-circuit. */
|
||||
if (!strcmp(vram_arg, "0")) {
|
||||
if (devices_arg) {
|
||||
errbuf_set(errbuf, errbuflen,
|
||||
"--gpu-vram 0 conflicts with --gpu-devices");
|
||||
return 1;
|
||||
}
|
||||
*out_skip_cuda = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Case 3: --gpu-vram auto. */
|
||||
if (!strcmp(vram_arg, "auto")) {
|
||||
#if !defined(DS4_NO_GPU) && !defined(__APPLE__)
|
||||
int filter_buf[DS4_MAX_GPUS];
|
||||
const int *filter_ptr = NULL;
|
||||
if (dev_count > 0) {
|
||||
for (int i = 0; i < dev_count; i++) filter_buf[i] = (int)dev_list[i];
|
||||
filter_ptr = filter_buf;
|
||||
}
|
||||
if (ds4_gpu_args_probe_auto_cuda(filter_ptr, dev_count, out,
|
||||
out->safety_margin_bytes,
|
||||
errbuf, errbuflen) != 0) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
(void)dev_list; (void)dev_count;
|
||||
errbuf_set(errbuf, errbuflen,
|
||||
"--gpu-vram auto requires a CUDA build");
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Case 4: explicit GB budgets — "N[,N...]" */
|
||||
long vram_list[DS4_MAX_GPUS] = {0};
|
||||
int vram_count = 0;
|
||||
if (parse_csv_int_list(vram_arg, vram_list, &vram_count,
|
||||
DS4_MAX_GPUS,
|
||||
DS4_GPU_ARGS_MAX_VRAM_GB,
|
||||
errbuf, errbuflen,
|
||||
"--gpu-vram") != 0) {
|
||||
return 1;
|
||||
}
|
||||
if (dev_count > 0 && dev_count != vram_count) {
|
||||
errbuf_setf(errbuf, errbuflen,
|
||||
"--gpu-devices count (%d) does not match --gpu-vram count (%d)",
|
||||
dev_count, vram_count);
|
||||
return 1;
|
||||
}
|
||||
out->n_gpus = vram_count;
|
||||
for (int i = 0; i < vram_count; i++) {
|
||||
int dev_id = (dev_count > 0) ? (int)dev_list[i] : i;
|
||||
out->device_indices[i] = dev_id;
|
||||
out->vram_bytes[i] = (size_t)vram_list[i] * (size_t)1024 * 1024 * 1024;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int format_gpu_layout_line(const ds4_gpu_config *cfg,
|
||||
bool was_auto,
|
||||
char *out,
|
||||
size_t outlen) {
|
||||
if (!out || outlen == 0 || !cfg) return -1;
|
||||
/* "ds4: GPU config: N devices [d0,d1,...] requested, budgets G0,G1,... GB; auto=<bool>" */
|
||||
int written = snprintf(out, outlen,
|
||||
"ds4: GPU config: %d device%s [",
|
||||
cfg->n_gpus, cfg->n_gpus == 1 ? "" : "s");
|
||||
if (written < 0 || (size_t)written >= outlen) return written;
|
||||
for (int i = 0; i < cfg->n_gpus; i++) {
|
||||
int n = snprintf(out + written, outlen - (size_t)written,
|
||||
"%s%d", i ? "," : "", cfg->device_indices[i]);
|
||||
if (n < 0 || (size_t)(written + n) >= outlen) return written + n;
|
||||
written += n;
|
||||
}
|
||||
int n = snprintf(out + written, outlen - (size_t)written,
|
||||
"] requested, budgets ");
|
||||
if (n < 0 || (size_t)(written + n) >= outlen) return written + n;
|
||||
written += n;
|
||||
for (int i = 0; i < cfg->n_gpus; i++) {
|
||||
double gb = (double)cfg->vram_bytes[i] / (1024.0 * 1024.0 * 1024.0);
|
||||
int m = snprintf(out + written, outlen - (size_t)written,
|
||||
"%s%.0f", i ? "," : "", gb);
|
||||
if (m < 0 || (size_t)(written + m) >= outlen) return written + m;
|
||||
written += m;
|
||||
}
|
||||
int t = snprintf(out + written, outlen - (size_t)written,
|
||||
" GB; auto=%s", was_auto ? "true" : "false");
|
||||
if (t < 0) return t;
|
||||
written += t;
|
||||
return written;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/* ds4_gpu_args.h — shared CLI argument parser for --gpu-vram and
|
||||
* --gpu-devices flags. Used by ds4_cli, ds4_server, ds4_agent, and
|
||||
* ds4_bench. See mgpu-cli-wiring task (wave 2) for design.
|
||||
*
|
||||
* The parser does not link CUDA headers directly. On CUDA builds the
|
||||
* "auto" path is gated and dispatches to ds4_gpu_args_probe_auto_cuda,
|
||||
* which is defined in ds4_cuda.cu. On Metal/CPU builds, "auto" returns
|
||||
* an error.
|
||||
*/
|
||||
#ifndef DS4_GPU_ARGS_H
|
||||
#define DS4_GPU_ARGS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "ds4_gpu_mgpu.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Single canonical parser. Populates *out and *out_skip_cuda on
|
||||
* success; returns 0 on success, nonzero on parse error (errbuf
|
||||
* populated). Caller zeroes out and out_skip_cuda before calling.
|
||||
*
|
||||
* vram_arg may be:
|
||||
* NULL -- flag unset
|
||||
* "0" -- skip CUDA entirely; sets *out_skip_cuda = true
|
||||
* "auto" -- query free VRAM via CUDA (CUDA build only)
|
||||
* "N[,N...]" -- explicit GB budgets per device
|
||||
*
|
||||
* devices_arg may be:
|
||||
* NULL -- 0..n-1 of visible devices (or 0..vram_count-1)
|
||||
* "N[,N...]" -- explicit CUDA device indices
|
||||
*
|
||||
* Constraints:
|
||||
* - If both args are given and counts differ, error.
|
||||
* - Negative or non-integer values error.
|
||||
* - Counts > DS4_MAX_GPUS error.
|
||||
*/
|
||||
int parse_gpu_vram_arg(
|
||||
const char *vram_arg,
|
||||
const char *devices_arg,
|
||||
ds4_gpu_config *out,
|
||||
bool *out_skip_cuda,
|
||||
char *errbuf,
|
||||
size_t errbuflen);
|
||||
|
||||
/* Format the layout echo line written to stdout right before
|
||||
* ds4_engine_create_with_gpu_config is called.
|
||||
*
|
||||
* Example output:
|
||||
* ds4: GPU config: 2 devices [0,1] requested, budgets 40,12 GB; auto=true
|
||||
*
|
||||
* Returns the number of bytes written (excluding the trailing NUL), or
|
||||
* -1 if out is NULL/outlen is 0.
|
||||
*/
|
||||
int format_gpu_layout_line(const ds4_gpu_config *cfg,
|
||||
bool was_auto,
|
||||
char *out,
|
||||
size_t outlen);
|
||||
|
||||
/* CUDA-only auto probe. Defined in ds4_cuda.cu under the same gate.
|
||||
* Returns 0 on success, nonzero on failure (errbuf populated).
|
||||
* device_filter may be NULL (use all visible devices) or a list of
|
||||
* length filter_len (use only those device indices).
|
||||
*
|
||||
* Declared here for the parser's use; not called by application code
|
||||
* directly. The Mac/CPU builds skip this branch via the
|
||||
* #if !defined(DS4_NO_GPU) && !defined(__APPLE__) gate in the parser. */
|
||||
#if !defined(DS4_NO_GPU) && !defined(__APPLE__)
|
||||
int ds4_gpu_args_probe_auto_cuda(const int *device_filter,
|
||||
int filter_len,
|
||||
ds4_gpu_config *out,
|
||||
size_t safety_margin_bytes,
|
||||
char *errbuf,
|
||||
size_t errbuflen);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* DS4_GPU_ARGS_H */
|
||||
@@ -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
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
/* ds4_gpu_mgpu.h — multi-GPU plumbing types and APIs (v0).
|
||||
*
|
||||
* This header carries the new multi-GPU additions for the multi-GPU plumbing PP work
|
||||
* (device-aware CUDA). It is included from ds4_cuda.cu and from
|
||||
* downstream tasks that need access to g_gpu[], g_n_gpus, g_gpu_peer_ok[],
|
||||
* the ds4_gpu_config struct, and the new tensor APIs.
|
||||
*
|
||||
* Why not in ds4_gpu.h? The legacy ds4_gpu.h is included from C-only
|
||||
* callers (ds4.c, ds4_cli.c, etc.) and from the Metal build, but is NOT
|
||||
* included from ds4_cuda.cu historically. That asymmetry hid pre-existing
|
||||
* signature mismatches between the legacy header and ds4_cuda.cu. We keep
|
||||
* the legacy header opaque and put the new shared types here, so this
|
||||
* file is the single source of truth for both ds4_cuda.cu and downstream
|
||||
* multi-GPU tasks without disturbing the legacy contract.
|
||||
*
|
||||
* The struct definitions reference CUDA-specific handle types via void *
|
||||
* placeholders so the header is safe to include from C builds, Metal
|
||||
* builds, and the CUDA build (where ds4_cuda.cu casts the void * back
|
||||
* to cudaStream_t / cublasHandle_t / cudaEvent_t internally).
|
||||
*/
|
||||
#ifndef DS4_GPU_MGPU_H
|
||||
#define DS4_GPU_MGPU_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define DS4_MAX_GPUS 16
|
||||
|
||||
/* Complete definition of the previously-opaque ds4_gpu_tensor, plus a
|
||||
* typedef so the new API prototypes below can use the bare name
|
||||
* `ds4_gpu_tensor *` in both C and C++ without forcing callers to
|
||||
* include ds4_gpu.h first. Callers that include this header can
|
||||
* stack-allocate or struct-embed tensors and pass to
|
||||
* ds4_gpu_tensor_alloc_on. */
|
||||
struct ds4_gpu_tensor {
|
||||
void *ptr;
|
||||
uint64_t bytes;
|
||||
int owner;
|
||||
int device_id; /* -1 means legacy/untagged → treat as device 0 */
|
||||
};
|
||||
#ifndef DS4_GPU_TENSOR_DEFINED
|
||||
#define DS4_GPU_TENSOR_DEFINED
|
||||
typedef struct ds4_gpu_tensor ds4_gpu_tensor;
|
||||
#endif
|
||||
|
||||
#ifndef DS4_GPU_ATTENTION_DECODE_ROW_DEFINED
|
||||
#define DS4_GPU_ATTENTION_DECODE_ROW_DEFINED
|
||||
#define DS4_GPU_ATTENTION_DECODE_BATCH_MAX 32u
|
||||
typedef struct {
|
||||
uint64_t raw_kv;
|
||||
uint64_t comp_kv;
|
||||
uint64_t topk;
|
||||
uint32_t pos;
|
||||
uint32_t n_raw;
|
||||
uint32_t raw_cap;
|
||||
uint32_t raw_start;
|
||||
uint32_t n_comp;
|
||||
uint32_t top_k;
|
||||
uint32_t window;
|
||||
uint32_t ratio;
|
||||
uint32_t indexed;
|
||||
} ds4_gpu_attention_decode_row;
|
||||
#endif
|
||||
|
||||
/* Tagged so headers (notably ds4.h) can forward-declare `struct
|
||||
* ds4_gpu_config` without dragging in this entire header. */
|
||||
typedef struct ds4_gpu_config {
|
||||
int device_indices[DS4_MAX_GPUS]; /* CUDA device IDs to use */
|
||||
/* Explicit per-device budget in bytes. The engine does NOT auto-fill
|
||||
* missing budgets - a value of 0 means "zero bytes of budget for
|
||||
* this slot" and (combined with reserves) will push placement to
|
||||
* CPU spill. Auto-detection (e.g. mapping --gpu-vram auto to
|
||||
* cudaMemGetInfo) is the caller's job; see CLI flag wiring for the
|
||||
* canonical CLI path. The engine emits a clear stderr and refuses
|
||||
* if n_gpus > 0 and every vram_bytes[] is 0 (almost certainly a
|
||||
* caller bug from zero-initializing the struct). */
|
||||
size_t vram_bytes[DS4_MAX_GPUS];
|
||||
int n_gpus;
|
||||
size_t safety_margin_bytes; /* per-device reserve */
|
||||
} ds4_gpu_config;
|
||||
|
||||
typedef struct {
|
||||
int device_id;
|
||||
void *stream; /* cudaStream_t under CUDA */
|
||||
void *cublas; /* cublasHandle_t under CUDA */
|
||||
int cublas_ready;
|
||||
void *scratch;
|
||||
size_t scratch_bytes;
|
||||
size_t budget_bytes;
|
||||
size_t used_bytes;
|
||||
void *boundary_event; /* cudaEvent_t under CUDA */
|
||||
} ds4_gpu_ctx;
|
||||
|
||||
extern ds4_gpu_ctx g_gpu[DS4_MAX_GPUS];
|
||||
extern int g_n_gpus;
|
||||
extern int g_gpu_peer_ok[DS4_MAX_GPUS][DS4_MAX_GPUS];
|
||||
|
||||
/* Primary multi-device init. The existing ds4_gpu_init (declared in
|
||||
* ds4_gpu.h) is a thin shim that builds a single-device config for
|
||||
* device 0 and calls this. */
|
||||
int ds4_gpu_init_multi(const ds4_gpu_config *cfg);
|
||||
|
||||
/* Caller-supplied struct alloc on a specific device. Returns 0 on
|
||||
* success, nonzero on error. Pair with ds4_gpu_tensor_free_in_place. */
|
||||
int ds4_gpu_tensor_alloc_on(ds4_gpu_tensor *t, int device_id, uint64_t bytes);
|
||||
void ds4_gpu_tensor_free_in_place(ds4_gpu_tensor *t);
|
||||
|
||||
/* Heap-allocated tensor on a specific logical tier; mirrors the legacy
|
||||
* ds4_gpu_tensor_alloc ABI (returns ds4_gpu_tensor *) but with a tier
|
||||
* parameter. Returns NULL on failure. Used by the multi-tier graph
|
||||
* allocations in ds4.c. Single-tier callers can continue using the
|
||||
* legacy ds4_gpu_tensor_alloc(bytes) which is equivalent to
|
||||
* ds4_gpu_tensor_alloc_ptr_on(0, bytes). */
|
||||
ds4_gpu_tensor *ds4_gpu_tensor_alloc_ptr_on(int tier, uint64_t bytes);
|
||||
int ds4_gpu_tensor_copy_async(ds4_gpu_tensor *dst, const ds4_gpu_tensor *src, uint64_t bytes);
|
||||
void ds4_gpu_enable_q8_dequant_gemm(void);
|
||||
|
||||
/* Heap-allocated MANAGED-memory tensor on a specific logical tier. Used
|
||||
* for the KV-cache pool. Stamps tier on the tensor so subsequent
|
||||
* ds4_gpu_tensor_free runs under the correct device. Note managed memory
|
||||
* pages on first-touch and is not strictly device-bound; the stamped
|
||||
* tier records the home tier for accounting + free. */
|
||||
ds4_gpu_tensor *ds4_gpu_tensor_alloc_managed_on(int tier, uint64_t bytes);
|
||||
|
||||
/* Cross-device tensor copy. Same-device → cudaMemcpyAsync; peer-capable
|
||||
* cross-device → cudaMemcpyPeerAsync with event sync; non-peer → pinned
|
||||
* host bounce (per src→dst pair). Honors DS4_FORCE_HOST_BOUNCE=1. */
|
||||
int ds4_gpu_tensor_copy_xdev(ds4_gpu_tensor *dst,
|
||||
const ds4_gpu_tensor *src,
|
||||
uint64_t bytes);
|
||||
int ds4_gpu_tensor_copy_xdev_default(ds4_gpu_tensor *dst,
|
||||
const ds4_gpu_tensor *src,
|
||||
uint64_t bytes);
|
||||
|
||||
/* Grouped default-stream handoff whose copies execute on the destination
|
||||
* device. The source default stream records readiness; the destination waits,
|
||||
* performs all three copies, then naturally orders its following kernels.
|
||||
* This allows source-device work submitted afterward to overlap the handoff. */
|
||||
int ds4_gpu_tensor_copy_xdev3_default_dst(
|
||||
ds4_gpu_tensor *dst0,
|
||||
const ds4_gpu_tensor *src0,
|
||||
uint64_t bytes0,
|
||||
ds4_gpu_tensor *dst1,
|
||||
const ds4_gpu_tensor *src1,
|
||||
uint64_t bytes1,
|
||||
ds4_gpu_tensor *dst2,
|
||||
const ds4_gpu_tensor *src2,
|
||||
uint64_t bytes2);
|
||||
|
||||
/* Cross-device copy of three buffers between the same source/destination
|
||||
* tiers, recording a single readiness event for the destination stream. This
|
||||
* is meant for tiny grouped activation handoffs where three independent
|
||||
* ds4_gpu_tensor_copy_xdev calls would spend more time in event plumbing than
|
||||
* in the copies themselves. Falls back internally when a grouped peer copy is
|
||||
* not applicable. */
|
||||
int ds4_gpu_tensor_copy_xdev3(ds4_gpu_tensor *dst0,
|
||||
const ds4_gpu_tensor *src0,
|
||||
uint64_t bytes0,
|
||||
ds4_gpu_tensor *dst1,
|
||||
const ds4_gpu_tensor *src1,
|
||||
uint64_t bytes1,
|
||||
ds4_gpu_tensor *dst2,
|
||||
const ds4_gpu_tensor *src2,
|
||||
uint64_t bytes2);
|
||||
|
||||
/* Cross-device copy that also orders against prior work on the destination
|
||||
* stream before overwriting dst. This is needed for pipelined prefill slots
|
||||
* where the next producer reuses a destination buffer that the destination
|
||||
* stage just consumed. */
|
||||
int ds4_gpu_tensor_copy_xdev_ordered(ds4_gpu_tensor *dst,
|
||||
const ds4_gpu_tensor *src,
|
||||
uint64_t bytes);
|
||||
|
||||
/* Order a peer read without copying: records readiness on src's stream and
|
||||
* makes dst_tier's stream wait for it. */
|
||||
int ds4_gpu_tensor_wait_xdev(const ds4_gpu_tensor *src, int dst_tier);
|
||||
int ds4_gpu_tensor_wait_xdev_default(const ds4_gpu_tensor *src, int dst_tier);
|
||||
|
||||
/* Cross-device float add used by CUDA tensor-parallel reductions.
|
||||
* See ds4_gpu.h for the full contract. */
|
||||
int ds4_gpu_add_xdev_tensor(ds4_gpu_tensor *out,
|
||||
const ds4_gpu_tensor *local,
|
||||
const ds4_gpu_tensor *remote,
|
||||
ds4_gpu_tensor *remote_tmp,
|
||||
uint32_t n);
|
||||
|
||||
/* Returns the device_id recorded on the tensor; -1 if untagged. */
|
||||
int ds4_gpu_tensor_device(const ds4_gpu_tensor *t);
|
||||
|
||||
/* Set the current CUDA device by LOGICAL tier index (0..g_n_gpus-1).
|
||||
*
|
||||
* This is the canonical shim for per-layer device routing in the
|
||||
* multi-tier execution path. The caller passes a logical tier index;
|
||||
* the shim internally indexes g_gpu[tier].device_id and calls
|
||||
* cudaSetDevice. Returns 0 on success, nonzero on error or if the
|
||||
* tier index is out of range.
|
||||
*
|
||||
* Wave-2 multi-GPU placement scaffolding adds this shim but does not
|
||||
* exercise the multi-tier execution path; multi-GPU execution
|
||||
* (follow-up) is its first caller. */
|
||||
int ds4_gpu_set_current_device(int logical_tier);
|
||||
int ds4_gpu_set_current_device_fenced(int logical_tier);
|
||||
|
||||
/* Register the mmap'd host model pointer for selective-cache lookups
|
||||
* WITHOUT triggering any device-side copy. This bypasses the
|
||||
* DS4_CUDA_COPY_MODEL environment-variable branch that
|
||||
* ds4_gpu_set_model_map normally honors, which is essential for
|
||||
* multi-tier startup: we want only per-device selective tensor caches,
|
||||
* never the whole-model copy. Returns 1 on success, 0 on error. */
|
||||
int ds4_gpu_register_model_map_no_copy(const void *model_map, uint64_t model_size);
|
||||
|
||||
/* Strict per-device selective-cache lookup (no fallback).
|
||||
*
|
||||
* Returns 1 only if a covering entry exists whose device_id matches the
|
||||
* caller-supplied expected_device (a PHYSICAL CUDA device id). Otherwise
|
||||
* returns 0 — no host-pointer fallback, no different-device match. The
|
||||
* caller is expected to have cudaSetDevice'd to expected_device before
|
||||
* invoking; the returned pointer is valid to consume from that device's
|
||||
* kernel.
|
||||
*
|
||||
* Used by multi-tier kernel-dispatch resolvers in
|
||||
* multi-GPU execution (multi-GPU execution). Single-tier callers should
|
||||
* keep using ds4_gpu_lookup_cache for back-compat behavior. */
|
||||
int ds4_gpu_lookup_cache_strict(uint64_t source_offset,
|
||||
uint64_t bytes,
|
||||
int expected_device,
|
||||
void **out_device_ptr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* DS4_GPU_MGPU_H */
|
||||
@@ -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
|
||||
+67
-19
@@ -147,12 +147,20 @@ static void print_model_runtime(FILE *fp, const help_colors *c,
|
||||
ds4_help_tool tool, bool full) {
|
||||
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", "Vision encoder GGUF for the selected model.");
|
||||
}
|
||||
#ifdef DS4_ROCM_BUILD
|
||||
opt(fp, c, "--metal | --rocm | --cpu", "Select the backend explicitly.");
|
||||
opt(fp, c, "--backend NAME", "Backend name: metal, rocm, or cpu.");
|
||||
#else
|
||||
opt(fp, c, "--metal | --cuda | --cpu", "Select the backend explicitly.");
|
||||
opt(fp, c, "--backend NAME", "Backend name: metal, cuda, or cpu.");
|
||||
opt(fp, c, "--gpu-vram N[,N,...]|auto", "CUDA VRAM budgets per device, in GiB, or auto-detect free VRAM.");
|
||||
opt(fp, c, "--gpu-devices N[,N,...]", "CUDA device indices used by multi-GPU placement.");
|
||||
if (tool != DS4_HELP_EVAL) {
|
||||
opt(fp, c, "--cuda-tensor-parallel", "Enable the paired DeepSeek tensor/expert path on an even multi-GPU CUDA placement.");
|
||||
}
|
||||
#endif
|
||||
if (tool != DS4_HELP_BENCH) {
|
||||
opt(fp, c, "-c, --ctx N", "Allocated context tokens.");
|
||||
@@ -164,21 +172,31 @@ 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", "Metal graph prefill chunk size. Default: auto (PRO long prompts use 8192; others use 4096).");
|
||||
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_BENCH) {
|
||||
opt(fp, c, "--mtp FILE", "Optional MTP support GGUF used for draft-token probes.");
|
||||
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) {
|
||||
opt(fp, c, "--mtp", "Enable model-embedded MTP speculation.");
|
||||
opt(fp, c, "--mtp-model FILE", "External MTP or DSpark support GGUF.");
|
||||
opt(fp, c, "--mtp-draft N", "Maximum autoregressive MTP draft tokens. Default: 1");
|
||||
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: 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.");
|
||||
}
|
||||
@@ -186,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.");
|
||||
@@ -194,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.");
|
||||
@@ -229,6 +249,17 @@ static void print_distributed(FILE *fp, const help_colors *c) {
|
||||
opt(fp, c, "--dist-replay-check", "Diagnostic: reset and replay prompt, then compare logits.");
|
||||
opt(fp, c, "--debug", "Print coordinator route/debug logs.");
|
||||
fputc('\n', fp);
|
||||
title(fp, c, "Tensor Parallelism");
|
||||
fputc('\n', fp);
|
||||
para(fp, c, "Tensor parallelism uses the same coordinator/worker addresses as distributed mode, but always runs one 50/50 worker. Add --tensor-parallel, omit --layers, start the worker, then start the coordinator.");
|
||||
fputc('\n', fp);
|
||||
opt(fp, c, "--tensor-parallel", "Switch --role/--listen/--coordinator to two-machine tensor parallelism.");
|
||||
opt(fp, c, "--transport auto|rdma|tcp", "Tensor gate transport. Default: auto");
|
||||
opt(fp, c, "--rdma-device NAME", "Select a verbs device when auto-detection is ambiguous.");
|
||||
opt(fp, c, "--rdma-gid-index N", "Select the local verbs GID index.");
|
||||
opt(fp, c, "--tensor-parallel-token-prefill", "GLM diagnostic: prefill one token at a time for exact arithmetic.");
|
||||
opt(fp, c, "--debug-hash N", "Cross-check hidden state every N tokens.");
|
||||
fputc('\n', fp);
|
||||
}
|
||||
|
||||
static void print_cli_diagnostics(FILE *fp, const help_colors *c);
|
||||
@@ -238,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);
|
||||
@@ -247,7 +279,7 @@ static void print_cli_specific(FILE *fp, const help_colors *c, bool full) {
|
||||
static void print_cli_diagnostics(FILE *fp, const help_colors *c) {
|
||||
title(fp, c, "Diagnostics And Data Collection");
|
||||
opt(fp, c, "--inspect", "Load the model and print a summary only.");
|
||||
opt(fp, c, "--dump-tokens", "Tokenize the prompt exactly as written, then exit.");
|
||||
opt(fp, c, "--dump-tokens", "Print the exact CLI prompt token stream, then exit. Use --raw for literal text.");
|
||||
opt(fp, c, "--dump-logits FILE", "Write full next-token logits as JSON.");
|
||||
opt(fp, c, "--dump-logprobs FILE", "Write greedy continuation top-logprobs as JSON.");
|
||||
opt(fp, c, "--logprobs-top-k N", "Alternatives stored by --dump-logprobs. Default: 20");
|
||||
@@ -258,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.");
|
||||
@@ -269,10 +302,10 @@ 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", "Read FILE and submit it as the next user message.");
|
||||
opt(fp, c, "/read FILE", "Submit a text file, PNG, or JPEG as the next user message.");
|
||||
opt(fp, c, "/quit, /exit", "Leave the prompt.");
|
||||
opt(fp, c, "Ctrl+C", "Stop current generation and return to ds4>.");
|
||||
fputc('\n', fp);
|
||||
@@ -281,8 +314,11 @@ static void print_cli_commands(FILE *fp, const help_colors *c) {
|
||||
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, "--non-interactive", "Run without TUI. With -p: one turn; without -p: repeated stdin prompts.");
|
||||
opt(fp, c, "--raw-prompt", "Non-interactive -p only: tokenize prompt without agent chat/tool text.");
|
||||
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.");
|
||||
opt(fp, c, "-sys, --system TEXT", "Extra system prompt. Empty disables extra text.");
|
||||
opt(fp, c, "--trace FILE", "Write prompt, token, and DSML debug trace.");
|
||||
opt(fp, c, "--chdir DIR", "Change working directory before loading runtime assets.");
|
||||
@@ -298,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.");
|
||||
@@ -310,6 +348,8 @@ static void print_server_api(FILE *fp, const help_colors *c) {
|
||||
opt(fp, c, "--port N", "Bind port. Default: 8000");
|
||||
opt(fp, c, "--cors", "Add Access-Control-Allow-* headers for browser JS clients.");
|
||||
opt(fp, c, "--trace FILE", "Write prompts, cache decisions, output, and tool calls.");
|
||||
opt(fp, c, "--batched-session N", "Keep N resident sessions and batch decode-ready requests.");
|
||||
opt(fp, c, "--mixed-prefill-quantum N", "Prefill chunk while generations are active. Default: 128; GLM-5.3 minimum: 1024");
|
||||
para(fp, c, "Endpoints: /v1/chat/completions, /v1/responses, /v1/completions, and /v1/messages.");
|
||||
para(fp, c, "Model endpoint aliases include deepseek-v4-flash and deepseek-v4-pro; both serve the loaded GGUF.");
|
||||
fputc('\n', fp);
|
||||
@@ -353,6 +393,7 @@ static void print_bench_specific(FILE *fp, const help_colors *c) {
|
||||
opt(fp, c, "--step-mul F", "Multiplicative step. Default: 1");
|
||||
opt(fp, c, "--step-incr N", "Linear step when --step-mul is 1. Default: 2048");
|
||||
opt(fp, c, "--gen-tokens N", "Greedy decode tokens per frontier. 0 for pure prefill. Default: 128");
|
||||
opt(fp, c, "--teacher-forced-decode", "Decode the following prompt tokens instead of each predicted argmax.");
|
||||
opt(fp, c, "--csv FILE", "Write CSV there instead of stdout.");
|
||||
opt(fp, c, "--dump-frontier-logits-dir DIR", "Write one full-logit JSON file per frontier.");
|
||||
fputc('\n', fp);
|
||||
@@ -360,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");
|
||||
@@ -483,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) {
|
||||
@@ -505,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);
|
||||
@@ -519,7 +567,7 @@ static void print_topic(FILE *fp, const help_colors *c, ds4_help_tool tool, cons
|
||||
title(fp, c, "Agent Tool System");
|
||||
para(fp, c, "The agent can read, search, write, edit, run bash, and browse through Chrome-backed web tools.");
|
||||
para(fp, c, "DeepSeek-family models emit DSML tool calls; GLM models use native <tool_call> syntax. Both are rendered live in the terminal.");
|
||||
para(fp, c, "Edit uses exact old/new replacement; [upto] can bridge a unique head and tail for large anchored edits.");
|
||||
para(fp, c, "Edit uses exact old/new replacement. --edit-upto enables anchored replacements between a unique head and tail.");
|
||||
fputc('\n', fp);
|
||||
} else if (tool == DS4_HELP_BENCH && streq(topic, "benchmark")) print_bench_specific(fp, c);
|
||||
else if (tool == DS4_HELP_EVAL && streq(topic, "evaluation")) print_eval_specific(fp, c);
|
||||
@@ -530,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);
|
||||
|
||||
+1204
File diff suppressed because it is too large.
Load diff
+157
@@ -0,0 +1,157 @@
|
||||
#ifndef DS4_IMAGE_H
|
||||
#define DS4_IMAGE_H
|
||||
|
||||
#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)
|
||||
|
||||
typedef struct {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint8_t *rgb;
|
||||
uint8_t fingerprint[32];
|
||||
} ds4_image;
|
||||
|
||||
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 patch_count;
|
||||
uint32_t image_token_count;
|
||||
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,
|
||||
size_t encoded_len,
|
||||
char *error,
|
||||
size_t error_cap);
|
||||
|
||||
int ds4_image_decode_file(
|
||||
ds4_image *out,
|
||||
const char *path,
|
||||
char *error,
|
||||
size_t error_cap);
|
||||
|
||||
void ds4_image_free(ds4_image *image);
|
||||
|
||||
int ds4_image_preprocess_glm53(
|
||||
ds4_image_patches *out,
|
||||
const ds4_image *image,
|
||||
uint32_t min_image_tokens,
|
||||
uint32_t max_image_tokens,
|
||||
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
|
||||
+16
-23
@@ -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,
|
||||
@@ -1318,32 +1324,19 @@ int ds4_kvstore_try_load_text(ds4_kvstore *kc,
|
||||
const double load_ms = (kv_now_sec() - load_t0) * 1000.0;
|
||||
kc->continued_last_store_tokens = loaded;
|
||||
const char *key_kind = ds4_kvstore_key_kind(hdr.ext_flags);
|
||||
bool consumed = false;
|
||||
if (kc->opt.cold_max_tokens > 0 && loaded > kc->opt.cold_max_tokens) {
|
||||
unlink(path);
|
||||
consumed = true;
|
||||
kv_logf(kc, DS4_KVSTORE_LOG_KVCACHE,
|
||||
"%s: kv cache hit text%s%s tokens=%d text=%u quant=%u key=%s load=%.1f ms consumed file=%s",
|
||||
kv_log_name(kc),
|
||||
responses_protocol ? " " : "",
|
||||
responses_protocol ? "RESPPROTO" : "",
|
||||
loaded, text_bytes, hdr.quant_bits, key_kind, load_ms, path);
|
||||
} else {
|
||||
ds4_kvstore_touch_file(path, hdr.hits + 1);
|
||||
kv_logf(kc, DS4_KVSTORE_LOG_KVCACHE,
|
||||
"%s: kv cache hit text%s%s tokens=%d text=%u quant=%u key=%s load=%.1f ms file=%s",
|
||||
kv_log_name(kc),
|
||||
responses_protocol ? " " : "",
|
||||
responses_protocol ? "RESPPROTO" : "",
|
||||
loaded, text_bytes, hdr.quant_bits, key_kind, load_ms, path);
|
||||
}
|
||||
ds4_kvstore_touch_file(path, hdr.hits + 1);
|
||||
kv_logf(kc, DS4_KVSTORE_LOG_KVCACHE,
|
||||
"%s: kv cache hit text%s%s tokens=%d text=%u quant=%u key=%s load=%.1f ms file=%s",
|
||||
kv_log_name(kc),
|
||||
responses_protocol ? " " : "",
|
||||
responses_protocol ? "RESPPROTO" : "",
|
||||
loaded, text_bytes, hdr.quant_bits, key_kind, load_ms, path);
|
||||
if (result) {
|
||||
result->tokens = loaded;
|
||||
result->text_bytes = text_bytes;
|
||||
result->quant_bits = hdr.quant_bits;
|
||||
result->ext_flags = hdr.ext_flags;
|
||||
result->load_ms = load_ms;
|
||||
result->consumed = consumed;
|
||||
result->path = kv_xstrdup(path);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -103,7 +103,6 @@ typedef struct {
|
||||
uint8_t quant_bits;
|
||||
uint8_t ext_flags;
|
||||
double load_ms;
|
||||
bool consumed;
|
||||
char *path;
|
||||
} ds4_kvstore_load_result;
|
||||
|
||||
@@ -215,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,149 @@
|
||||
/* Monotonic-contiguous multi-tier layer placement packer.
|
||||
*
|
||||
* Pure C99: no CUDA, no platform-specific code. Used by both the CUDA and
|
||||
* Metal/CPU builds. See ds4_layer_pack.h for the API contract and the
|
||||
* design doc docs/superpowers/specs/2026-05-26-multi-gpu-pp-v0-design.md
|
||||
* for the rationale. */
|
||||
|
||||
#include "ds4_layer_pack.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int ds4_compute_layer_placement(const size_t *entry_bytes,
|
||||
int n_entries,
|
||||
const ds4_layer_pack_config *cfg,
|
||||
int *device_for_entry) {
|
||||
if (!entry_bytes || !cfg || !device_for_entry) return 1;
|
||||
if (n_entries < 0) return 2;
|
||||
if (cfg->n_gpus < 0 || cfg->n_gpus > DS4_LAYER_PACK_MAX_GPUS) return 3;
|
||||
|
||||
/* Work over a local copy of budgets so the caller's struct is untouched. */
|
||||
size_t budget[DS4_LAYER_PACK_MAX_GPUS];
|
||||
for (int d = 0; d < cfg->n_gpus; d++) {
|
||||
budget[d] = cfg->gpu_budget_bytes[d];
|
||||
}
|
||||
|
||||
int d = 0;
|
||||
for (int e = 0; e < n_entries; e++) {
|
||||
/* Advance until the current device can hold this entry, or we run
|
||||
* out of devices. Strict greater-than: exact fits stay on d. */
|
||||
while (d < cfg->n_gpus && entry_bytes[e] > budget[d]) {
|
||||
d++;
|
||||
}
|
||||
if (d < cfg->n_gpus) {
|
||||
device_for_entry[e] = d;
|
||||
budget[d] -= entry_bytes[e];
|
||||
} else {
|
||||
device_for_entry[e] = DS4_LAYER_PACK_CPU;
|
||||
/* d stays at cfg->n_gpus so every subsequent entry also lands
|
||||
* on the CPU tier. */
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Internal helper: append " + embedding" or " + output head" to a buffer if
|
||||
* the matching pseudo-layer lives on this tier. */
|
||||
static void append_pseudo_layer_tags(char *buf, size_t buflen,
|
||||
const int *device_for_entry,
|
||||
int tier,
|
||||
int n_entries,
|
||||
int n_layers) {
|
||||
/* entry 0 -> embedding, entry n_layers+1 -> output head */
|
||||
if (n_entries > 0 && device_for_entry[0] == tier) {
|
||||
strncat(buf, " + embedding", buflen - strlen(buf) - 1);
|
||||
}
|
||||
if (n_entries > 1 && n_layers + 1 < n_entries &&
|
||||
device_for_entry[n_layers + 1] == tier) {
|
||||
strncat(buf, " + output head", buflen - strlen(buf) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Emit "L-R" for a contiguous transformer-layer range assigned to a tier.
|
||||
* Returns 1 if anything was printed (i.e. tier owned at least one layer). */
|
||||
static int print_layer_range_for_tier(FILE *out, const int *device_for_entry,
|
||||
int tier, int n_entries, int n_layers) {
|
||||
/* Transformer layers live at indices 1..n_layers in device_for_entry.
|
||||
* Their human-facing numbers are 0..n_layers-1 (i.e. entry index - 1). */
|
||||
int first = -1;
|
||||
int last = -1;
|
||||
for (int i = 1; i <= n_layers && i < n_entries; i++) {
|
||||
if (device_for_entry[i] == tier) {
|
||||
if (first < 0) first = i - 1; /* human-facing index */
|
||||
last = i - 1;
|
||||
}
|
||||
}
|
||||
if (first < 0) {
|
||||
fprintf(out, "(no transformer layers)");
|
||||
return 0;
|
||||
}
|
||||
if (first == last) {
|
||||
fprintf(out, "layer %d", first);
|
||||
} else {
|
||||
fprintf(out, "layers %d-%d", first, last);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ds4_layer_pack_print(FILE *out,
|
||||
const int *device_for_entry,
|
||||
int n_entries,
|
||||
int n_layers,
|
||||
const size_t *entry_bytes,
|
||||
const size_t *gpu_used_bytes,
|
||||
const size_t *gpu_budget_bytes,
|
||||
int n_gpus) {
|
||||
if (!out || !device_for_entry) return;
|
||||
(void)entry_bytes; /* not needed for the textual layout */
|
||||
|
||||
fprintf(out, "multi-GPU layout:\n");
|
||||
for (int d = 0; d < n_gpus; d++) {
|
||||
fprintf(out, " GPU%d: ", d);
|
||||
int have_layers = print_layer_range_for_tier(out, device_for_entry,
|
||||
d, n_entries, n_layers);
|
||||
/* Pseudo-layer tags. */
|
||||
char tags[64];
|
||||
tags[0] = '\0';
|
||||
append_pseudo_layer_tags(tags, sizeof(tags), device_for_entry, d,
|
||||
n_entries, n_layers);
|
||||
if (tags[0]) {
|
||||
if (!have_layers) {
|
||||
/* Erase the "(no transformer layers)" stub and just write
|
||||
* the tag with leading "+ " stripped. */
|
||||
/* The stub was already written; appending " + embedding"
|
||||
* is still informative. */
|
||||
}
|
||||
fprintf(out, "%s", tags);
|
||||
}
|
||||
/* Usage line. */
|
||||
if (gpu_used_bytes && gpu_budget_bytes) {
|
||||
const double used_gb = (double)gpu_used_bytes[d] / 1073741824.0;
|
||||
const double budget_gb = (double)gpu_budget_bytes[d] / 1073741824.0;
|
||||
/* Two-space pad before the parens matches the design doc. */
|
||||
fprintf(out, " (%.1f / %.1f GB)", used_gb, budget_gb);
|
||||
}
|
||||
fputc('\n', out);
|
||||
}
|
||||
/* CPU tier: only print if at least one entry is on CPU. */
|
||||
int cpu_present = 0;
|
||||
for (int i = 0; i < n_entries; i++) {
|
||||
if (device_for_entry[i] == DS4_LAYER_PACK_CPU) { cpu_present = 1; break; }
|
||||
}
|
||||
if (cpu_present) {
|
||||
fprintf(out, " CPU : ");
|
||||
int have_layers = print_layer_range_for_tier(out, device_for_entry,
|
||||
DS4_LAYER_PACK_CPU,
|
||||
n_entries, n_layers);
|
||||
char tags[64];
|
||||
tags[0] = '\0';
|
||||
append_pseudo_layer_tags(tags, sizeof(tags), device_for_entry,
|
||||
DS4_LAYER_PACK_CPU, n_entries, n_layers);
|
||||
if (tags[0]) {
|
||||
(void)have_layers;
|
||||
fprintf(out, "%s", tags);
|
||||
}
|
||||
fputc('\n', out);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#ifndef DS4_LAYER_PACK_H
|
||||
#define DS4_LAYER_PACK_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define DS4_LAYER_PACK_MAX_GPUS 16
|
||||
#define DS4_LAYER_PACK_CPU (-1)
|
||||
|
||||
typedef struct {
|
||||
/* Per-device budget in BYTES (already net of per-device overhead such as
|
||||
* cuBLAS workspace, global scratch, logits buffer, MTP, steering, and the
|
||||
* configured safety margin). The packer treats these as raw capacity. */
|
||||
size_t gpu_budget_bytes[DS4_LAYER_PACK_MAX_GPUS];
|
||||
int n_gpus;
|
||||
} ds4_layer_pack_config;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Compute a monotonic-contiguous layer placement.
|
||||
*
|
||||
* Inputs:
|
||||
* entry_bytes[] — per-entry byte footprint, in FORWARD ORDER:
|
||||
* entry 0 = embedding pseudo-layer
|
||||
* entry 1..n_layers = transformer layers
|
||||
* entry n_layers+1 = output-head pseudo-layer
|
||||
* n_entries — total number of entries (typically n_layers + 2)
|
||||
* cfg — per-device budgets
|
||||
*
|
||||
* Output:
|
||||
* device_for_entry[] — filled with target device for each entry; CPU is
|
||||
* DS4_LAYER_PACK_CPU. Caller-owned, must be
|
||||
* large enough for n_entries int slots.
|
||||
*
|
||||
* Returns:
|
||||
* 0 on success.
|
||||
* Nonzero on configuration errors (null pointer, n_entries < 0,
|
||||
* n_gpus out of range).
|
||||
*
|
||||
* Per the design doc, an entry that exceeds every GPU budget spills to CPU;
|
||||
* by the monotonicity rule every entry after it also goes to CPU. There is
|
||||
* no error path for "entry too large" — the CPU tier is always available. */
|
||||
int ds4_compute_layer_placement(const size_t *entry_bytes,
|
||||
int n_entries,
|
||||
const ds4_layer_pack_config *cfg,
|
||||
int *device_for_entry);
|
||||
|
||||
/* Print a human-readable layout summary matching the design doc's format:
|
||||
*
|
||||
* multi-GPU layout:
|
||||
* GPU0: layers 0-21 + embedding (38.4 / 40.0 GB)
|
||||
* GPU1: layers 22-31 (11.7 / 12.0 GB)
|
||||
* CPU : layers 32-42 + output head
|
||||
*
|
||||
* Entry-naming convention:
|
||||
* entry 0 -> "embedding"
|
||||
* entry i in 1..n_layers -> transformer layer (numbered i-1)
|
||||
* entry n_layers+1 -> "output head"
|
||||
*
|
||||
* gpu_used_bytes[] and gpu_budget_bytes[] may be NULL — in that case the
|
||||
* "(used / budget)" line is omitted for the GPU lines. */
|
||||
void ds4_layer_pack_print(FILE *out,
|
||||
const int *device_for_entry,
|
||||
int n_entries,
|
||||
int n_layers,
|
||||
const size_t *entry_bytes,
|
||||
const size_t *gpu_used_bytes,
|
||||
const size_t *gpu_budget_bytes,
|
||||
int n_gpus);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* DS4_LAYER_PACK_H */
|
||||
@@ -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
|
||||
Loaded 100 of 2729 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user