forked from tinygrad/tinygrad
Compare commits
514
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
723b905733 | ||
|
|
1392d3da50 | ||
|
|
0dfa36aa41 | ||
|
|
dd91740759 | ||
|
|
e63dfbdbb8 | ||
|
|
433e162c86 | ||
|
|
330310cb4f | ||
|
|
2663d16dae | ||
|
|
2b787196b3 | ||
|
|
f34f308b61 | ||
|
|
6a6c3042f4 | ||
|
|
5ae6526d47 | ||
|
|
020c7a14fd | ||
|
|
f9ae840f91 | ||
|
|
371ac77173 | ||
|
|
1d878ac67a | ||
|
|
7e14f3690d | ||
|
|
479e077ecf | ||
|
|
2731aa27f7 | ||
|
|
405f292dae | ||
|
|
c84876fdd2 | ||
|
|
1f3c0ac89c | ||
|
|
a609a0a29d | ||
|
|
24af3a0941 | ||
|
|
ac40497945 | ||
|
|
b536514c83 | ||
|
|
82bd6d5476 | ||
|
|
226556ddf3 | ||
|
|
33cd373ad3 | ||
|
|
707d87e97b | ||
|
|
ded106b183 | ||
|
|
39e246848c | ||
|
|
8e9c929a51 | ||
|
|
0dc55feddc | ||
|
|
0319b1e75f | ||
|
|
aaf76ca406 | ||
|
|
6fd714d069 | ||
|
|
e8c8ba1c77 | ||
|
|
4f4f8e4f95 | ||
|
|
39f9bd0461 | ||
|
|
e1ba1755b7 | ||
|
|
4f44116bd6 | ||
|
|
5231b5274c | ||
|
|
01647028fb | ||
|
|
404cda437a | ||
|
|
6c26eaf724 | ||
|
|
a3e85c297a | ||
|
|
543da4dcb6 | ||
|
|
cac1bb1c9a | ||
|
|
e3431a2172 | ||
|
|
7cd71fb54a | ||
|
|
67ef401f41 | ||
|
|
290aa54df5 | ||
|
|
f0bdf2d9e9 | ||
|
|
313221aac2 | ||
|
|
6201202e23 | ||
|
|
a4fd692435 | ||
|
|
ec18aadf43 | ||
|
|
c7b6ebbc21 | ||
|
|
f4c7aa7cca | ||
|
|
f7742b7758 | ||
|
|
54a39db8dc | ||
|
|
83ee6144f8 | ||
|
|
f48b583ee0 | ||
|
|
2c4e5bb50b | ||
|
|
ccf14f0530 | ||
|
|
d34e0030ef | ||
|
|
1d134dadcd | ||
|
|
b77ffbd200 | ||
|
|
c9ed7f3961 | ||
|
|
1b06b01144 | ||
|
|
e740ded0ed | ||
|
|
8bf84d0e4f | ||
|
|
6f4bfde234 | ||
|
|
88face1a98 | ||
|
|
34db1c81ad | ||
|
|
c68c174daa | ||
|
|
5fb32893ba | ||
|
|
5b05106537 | ||
|
|
69be34faa1 | ||
|
|
be7e3891ab | ||
|
|
7807ba0b9c | ||
|
|
18960ec154 | ||
|
|
68d76d4d61 | ||
|
|
deed921d84 | ||
|
|
0bcc92382e | ||
|
|
f626844e5f | ||
|
|
f2822d1077 | ||
|
|
ce086fefb4 | ||
|
|
a5c833702f | ||
|
|
f2b9fe1653 | ||
|
|
f6fc4e3f2c | ||
|
|
60f1cd916b | ||
|
|
5ae064e51a | ||
|
|
a33577b067 | ||
|
|
98be30f1b0 | ||
|
|
017d51bb14 | ||
|
|
9cd01059c2 | ||
|
|
eb5cfe9036 | ||
|
|
93d8f02551 | ||
|
|
5c6af9cb18 | ||
|
|
9e2fe7a15a | ||
|
|
13a0169360 | ||
|
|
674466abad | ||
|
|
0d60a6cb5d | ||
|
|
cde95fd3b3 | ||
|
|
06f26aaa86 | ||
|
|
9dbcddc649 | ||
|
|
fd10521869 | ||
|
|
ed0848c055 | ||
|
|
613ac4bec1 | ||
|
|
46bf11106f | ||
|
|
fe95f765c9 | ||
|
|
c2aada33ff | ||
|
|
cf6fff13aa | ||
|
|
b87159cee1 | ||
|
|
a3bde519d6 | ||
|
|
37738a7276 | ||
|
|
c12eefe447 | ||
|
|
79250d76de | ||
|
|
4197f74231 | ||
|
|
8f4bd50ddd | ||
|
|
32bbf82d5b | ||
|
|
60cc1d7827 | ||
|
|
d025993642 | ||
|
|
af079e8cc8 | ||
|
|
e3c4b3d9b2 | ||
|
|
3123f947b0 | ||
|
|
b48cb9fce7 | ||
|
|
a6c810eb03 | ||
|
|
593aa1e17b | ||
|
|
fdb4109f5f | ||
|
|
85841ce0d1 | ||
|
|
1c183e6de9 | ||
|
|
f5b00de319 | ||
|
|
902a1efe06 | ||
|
|
9c49e35aab | ||
|
|
7cc973a4e0 | ||
|
|
61f7fd3735 | ||
|
|
1a5a364f3e | ||
|
|
0b58ff679c | ||
|
|
0bd725376b | ||
|
|
d7913e206b | ||
|
|
2f5572ab20 | ||
|
|
0a1bfc9fad | ||
|
|
8c7906d200 | ||
|
|
661440e672 | ||
|
|
9aa3c48200 | ||
|
|
02d67a8333 | ||
|
|
12f34d8eb1 | ||
|
|
a9830e2b4d | ||
|
|
812d5cef62 | ||
|
|
0640cfce35 | ||
|
|
086f45cb81 | ||
|
|
cb0db2d042 | ||
|
|
793c1664f3 | ||
|
|
bd2711b7c1 | ||
|
|
b3dad4fa0c | ||
|
|
49627aecb5 | ||
|
|
959958135d | ||
|
|
f196af2327 | ||
|
|
112e50ec3a | ||
|
|
ae63f23557 | ||
|
|
287679a88a | ||
|
|
2eacd4fa68 | ||
|
|
7fdc58b1cc | ||
|
|
cb857a5a40 | ||
|
|
6083de5cff | ||
|
|
55c8e18930 | ||
|
|
76dce1eb8d | ||
|
|
6242b09066 | ||
|
|
92d92c70a9 | ||
|
|
26c4b6319e | ||
|
|
53cad325c7 | ||
|
|
4456e62343 | ||
|
|
39d7fd0981 | ||
|
|
4e6bdac412 | ||
|
|
bc485d271f | ||
|
|
6b361e4de7 | ||
|
|
38e0fe103b | ||
|
|
e6ad2907a8 | ||
|
|
fede358811 | ||
|
|
f06832bf6f | ||
|
|
ee3161e924 | ||
|
|
38fa0643cf | ||
|
|
f267a49639 | ||
|
|
14cbecef8f | ||
|
|
54d69f439f | ||
|
|
980c42d611 | ||
|
|
7eb763a3c2 | ||
|
|
5c3d044465 | ||
|
|
f19d89e29a | ||
|
|
2d4e7afcbf | ||
|
|
0d66815849 | ||
|
|
d16a2510ad | ||
|
|
118e812b31 | ||
|
|
0820c7a424 | ||
|
|
22ffff3258 | ||
|
|
a0a901c8e4 | ||
|
|
c015351ac5 | ||
|
|
6074c002e1 | ||
|
|
6042b87272 | ||
|
|
cc72b9f7be | ||
|
|
6a3b297548 | ||
|
|
ea6c82f3be | ||
|
|
0abcf09b74 | ||
|
|
4bdc865131 | ||
|
|
4c20f1d357 | ||
|
|
ecf79e260d | ||
|
|
9860e5d285 | ||
|
|
625c05df1e | ||
|
|
b49c03fb1c | ||
|
|
dc04c7820e | ||
|
|
6ece327cf3 | ||
|
|
07268b724f | ||
|
|
55032514ce | ||
|
|
2824504f90 | ||
|
|
7fef98c86e | ||
|
|
b831ca62d9 | ||
|
|
d851aca9ae | ||
|
|
7dc8b666e7 | ||
|
|
023bfdb380 | ||
|
|
9f01775cf4 | ||
|
|
9607787ce1 | ||
|
|
ab79879613 | ||
|
|
a5678317c2 | ||
|
|
1d694dd700 | ||
|
|
65ca68567e | ||
|
|
a7df1a1ace | ||
|
|
d9004cff22 | ||
|
|
6f87158d77 | ||
|
|
021c015eb4 | ||
|
|
2e7790f16c | ||
|
|
8df3dac0ec | ||
|
|
ed110993d3 | ||
|
|
f4cc28824c | ||
|
|
76bf6b7eec | ||
|
|
08fbd25f1a | ||
|
|
5aabbb1991 | ||
|
|
7a887e84b2 | ||
|
|
7b21ffac00 | ||
|
|
9b481a4893 | ||
|
|
9b0c65688d | ||
|
|
7e561fcb97 | ||
|
|
a5ea95d8b8 | ||
|
|
df528499ce | ||
|
|
4ee114b42a | ||
|
|
9d0cd0ebcb | ||
|
|
11edcc144c | ||
|
|
a31aca9e52 | ||
|
|
8b164aefea | ||
|
|
477b573807 | ||
|
|
bb0e99acbf | ||
|
|
93865e2c66 | ||
|
|
5b60a09ab0 | ||
|
|
b0a1285330 | ||
|
|
a2e64e16aa | ||
|
|
a9069c177a | ||
|
|
8950942e75 | ||
|
|
356f665377 | ||
|
|
7204d46786 | ||
|
|
af242819d8 | ||
|
|
52596dbf38 | ||
|
|
07cce78cec | ||
|
|
f986829461 | ||
|
|
4fd4eafb23 | ||
|
|
daa154aa22 | ||
|
|
12f889aaad | ||
|
|
298748ebd3 | ||
|
|
3082956a17 | ||
|
|
402bea7ddd | ||
|
|
8f9cbdf0cc | ||
|
|
8f59041ee5 | ||
|
|
1cf8a2c7fe | ||
|
|
3919ce8427 | ||
|
|
756e82e055 | ||
|
|
cc32aa18db | ||
|
|
77f698e55b | ||
|
|
554d078ac4 | ||
|
|
176377ff6e | ||
|
|
1c3c9e96f6 | ||
|
|
e8a8d99b99 | ||
|
|
dcc2d021e7 | ||
|
|
80bf60d782 | ||
|
|
1cb0600086 | ||
|
|
1bcb6bdc62 | ||
|
|
d716d0d927 | ||
|
|
9216aa494c | ||
|
|
9aa9e11301 | ||
|
|
3fdbb82bfe | ||
|
|
0ccef542e0 | ||
|
|
c655aaf3a2 | ||
|
|
592e3f8363 | ||
|
|
3715006a21 | ||
|
|
d80254c1d9 | ||
|
|
c773891e3f | ||
|
|
0e7ab863a0 | ||
|
|
a57188ea6d | ||
|
|
1707dca3b4 | ||
|
|
eba5b7e750 | ||
|
|
a8ecb73363 | ||
|
|
f55c1a37d2 | ||
|
|
6732d05157 | ||
|
|
e68aa16e3f | ||
|
|
8c3cb00d36 | ||
|
|
c117da9850 | ||
|
|
57d1104a92 | ||
|
|
a1263fadf3 | ||
|
|
e6324d1e1c | ||
|
|
c63d94e059 | ||
|
|
c89ae6c083 | ||
|
|
2067133732 | ||
|
|
ab68c58759 | ||
|
|
fc214da417 | ||
|
|
0a0b6cb596 | ||
|
|
b8cc74ecf8 | ||
|
|
7064e76bc8 | ||
|
|
0c5307b4f3 | ||
|
|
a4fadcf606 | ||
|
|
c218b4842d | ||
|
|
bd6e70ac15 | ||
|
|
9550378704 | ||
|
|
b3e2f17b24 | ||
|
|
e8ba214b56 | ||
|
|
68b4407fe3 | ||
|
|
d539aaf752 | ||
|
|
8c2bf02d17 | ||
|
|
ca86a42703 | ||
|
|
df3b114fbc | ||
|
|
e37b44d048 | ||
|
|
2cfb421a81 | ||
|
|
c31038ff37 | ||
|
|
49778d9a48 | ||
|
|
72280bb218 | ||
|
|
af2a43c850 | ||
|
|
a1366e2f6c | ||
|
|
0b757bb9bc | ||
|
|
7cbe8e0d15 | ||
|
|
a746861ac0 | ||
|
|
8d2cc64b69 | ||
|
|
cb892e1b92 | ||
|
|
d4a1f39038 | ||
|
|
34c9b9d434 | ||
|
|
901d257a26 | ||
|
|
b757437f64 | ||
|
|
00d6eed43c | ||
|
|
2776c5b369 | ||
|
|
58edff61d9 | ||
|
|
954d4f7797 | ||
|
|
7fe8e350c5 | ||
|
|
42714e1399 | ||
|
|
821e80ff9a | ||
|
|
37a54dc7cf | ||
|
|
e25f86721d | ||
|
|
138fb4a783 | ||
|
|
bfd4048abf | ||
|
|
057a18a07c | ||
|
|
c30bf116b7 | ||
|
|
e7bf2a811d | ||
|
|
e688e07758 | ||
|
|
97022960ae | ||
|
|
417563ca20 | ||
|
|
5ca87f1bac | ||
|
|
26cbadd69a | ||
|
|
fae893753b | ||
|
|
c17849a1f8 | ||
|
|
539a03343a | ||
|
|
a57569349c | ||
|
|
5c43a89fb1 | ||
|
|
e6f5bb9c09 | ||
|
|
4b0525e594 | ||
|
|
64ccbde3bb | ||
|
|
6ea665ed66 | ||
|
|
0725acc392 | ||
|
|
4a1f32977c | ||
|
|
13c381b0c0 | ||
|
|
ac7067ac60 | ||
|
|
80169c6758 | ||
|
|
adacaa3e17 | ||
|
|
89ab344c42 | ||
|
|
25c3bd027b | ||
|
|
6b35220622 | ||
|
|
b1859805b1 | ||
|
|
faba071b1d | ||
|
|
81dc8ec232 | ||
|
|
95ca5081fe | ||
|
|
303d1677b3 | ||
|
|
673c6463f9 | ||
|
|
849074f0db | ||
|
|
0252cb8fa7 | ||
|
|
cc6d33bde7 | ||
|
|
16c5ff2490 | ||
|
|
1b7f040984 | ||
|
|
39d144546e | ||
|
|
e103fb2a10 | ||
|
|
2297118541 | ||
|
|
cd6d0d6ee3 | ||
|
|
ff0cb28c21 | ||
|
|
ed8297a102 | ||
|
|
de04781b36 | ||
|
|
4f106ebe87 | ||
|
|
04c271ac41 | ||
|
|
2e5a9a4121 | ||
|
|
e1013a6356 | ||
|
|
f891f5ffd0 | ||
|
|
3686a1758f | ||
|
|
4a253db9b4 | ||
|
|
479ffb0cda | ||
|
|
2b5018e86a | ||
|
|
e11df72e0f | ||
|
|
a8c84ab34e | ||
|
|
ffef35c53e | ||
|
|
55e4f9d4f3 | ||
|
|
0c6a2c7dd6 | ||
|
|
ad2fdeae69 | ||
|
|
115bf9940f | ||
|
|
7edf80a48a | ||
|
|
22722ea2e7 | ||
|
|
d41ca5e60f | ||
|
|
e29606f07e | ||
|
|
8611fe22a7 | ||
|
|
2821bd646f | ||
|
|
44f1f45cd5 | ||
|
|
566f32fe9f | ||
|
|
8c8b43de62 | ||
|
|
e17c21e102 | ||
|
|
d4d537c8ae | ||
|
|
abe2256299 | ||
|
|
8c49a7a34b | ||
|
|
9dd3b8402e | ||
|
|
c0d2f9ac0c | ||
|
|
4c206a52b1 | ||
|
|
4a3b8f6501 | ||
|
|
59b88ea5e2 | ||
|
|
f76422b8af | ||
|
|
1827ec57f7 | ||
|
|
b6189db8e9 | ||
|
|
73e670c10f | ||
|
|
fca695a36f | ||
|
|
0c96cdc300 | ||
|
|
baa6148066 | ||
|
|
1858f1fd9a | ||
|
|
f253c4469d | ||
|
|
28195d51fb | ||
|
|
9020a88f03 | ||
|
|
d8cbc11105 | ||
|
|
1fd6b1035f | ||
|
|
46230e9f17 | ||
|
|
9636dd1a25 | ||
|
|
f258708d7d | ||
|
|
28e6ef6937 | ||
|
|
969df866a3 | ||
|
|
7a9cd8e329 | ||
|
|
b4372df9c6 | ||
|
|
d51e55aa17 | ||
|
|
be25207a7a | ||
|
|
d726e5f7f3 | ||
|
|
470c032a5e | ||
|
|
a8a8030bc9 | ||
|
|
581bfdd94f | ||
|
|
07ac911665 | ||
|
|
c2f1e5ae2a | ||
|
|
757a727808 | ||
|
|
2cce85a606 | ||
|
|
9b27ea8523 | ||
|
|
6cb419b9b7 | ||
|
|
5b0b68ec55 | ||
|
|
ad32bd272b | ||
|
|
874d33128b | ||
|
|
3bf9e70b19 | ||
|
|
77e124e455 | ||
|
|
b45058b5ec | ||
|
|
46f0003776 | ||
|
|
de57be1f26 | ||
|
|
9b508dfafc | ||
|
|
e1f42681fa | ||
|
|
3eab809e06 | ||
|
|
6122b3c98f | ||
|
|
f295f9fc99 | ||
|
|
d79772f057 | ||
|
|
c1a10e0726 | ||
|
|
7b6d2ddf23 | ||
|
|
f489f4bc36 | ||
|
|
80d2073a11 | ||
|
|
85e9440446 | ||
|
|
0db63e13cf | ||
|
|
6eedca57ce | ||
|
|
0796853845 | ||
|
|
568bfb6a37 | ||
|
|
f993228b73 | ||
|
|
c9cd44b751 | ||
|
|
13fff4f258 | ||
|
|
0170a3075a | ||
|
|
c21a552f3d | ||
|
|
67dc02d7e7 | ||
|
|
3cb786f447 | ||
|
|
87289a7410 | ||
|
|
c2625c78cb | ||
|
|
33755a3465 | ||
|
|
104ee90ccf | ||
|
|
a2385ae21d | ||
|
|
3331944547 | ||
|
|
7c1ce50f63 | ||
|
|
be5f62d269 | ||
|
|
e22935c758 | ||
|
|
314df72b5f | ||
|
|
23c7813f44 | ||
|
|
05bc7c6994 | ||
|
|
09dabfe05e | ||
|
|
59df317b12 | ||
|
|
e14cadb1fb | ||
|
|
0258c7fefc | ||
|
|
fb607fb990 | ||
|
|
15c936db01 |
@@ -4,7 +4,7 @@ inputs:
|
||||
python-version:
|
||||
description: 'Python version to use'
|
||||
required: false
|
||||
default: '' # if you don't set a version, the native python version will be used
|
||||
default: '3.14'
|
||||
key:
|
||||
description: 'Key for the python cache'
|
||||
required: false
|
||||
@@ -41,12 +41,12 @@ inputs:
|
||||
description: "Install LLVM?"
|
||||
required: false
|
||||
default: 'false'
|
||||
tinydreno:
|
||||
description: "Install tinydreno"
|
||||
qemu:
|
||||
description: "Install qemu?"
|
||||
required: false
|
||||
default: 'false'
|
||||
qemu:
|
||||
description: "Install qemu"
|
||||
ninja:
|
||||
description: "Install ninja?"
|
||||
required: false
|
||||
default: 'false'
|
||||
runs:
|
||||
@@ -59,18 +59,18 @@ runs:
|
||||
echo "OMP_NUM_THREADS=1" >> "$GITHUB_ENV"
|
||||
# no buffers should be over 300MB in CI
|
||||
echo "MAX_BUFFER_SIZE=300000000" >> "$GITHUB_ENV"
|
||||
if [[ "$RUNNER_OS" == "Linux" ]]; then
|
||||
echo "VIRTUAL_ENV=/opt/venv/${{ inputs.python-version }}" >> "$GITHUB_ENV"
|
||||
echo "UV_PYTHON_INSTALL_DIR=/opt/python" >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "VIRTUAL_ENV=${{ github.workspace }}/.venv" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b
|
||||
with:
|
||||
enable-cache: 'false' # see below for manual caching
|
||||
|
||||
- name: Set up Python ${{ inputs.python-version }}
|
||||
uses: actions/setup-python@v6
|
||||
if: inputs.python-version != ''
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
|
||||
# **** Caching packages ****
|
||||
|
||||
- name: Cache Python packages (PR)
|
||||
@@ -109,15 +109,15 @@ runs:
|
||||
if: inputs.deps != ''
|
||||
shell: bash
|
||||
run: |
|
||||
uv venv .venv
|
||||
uv venv --allow-existing --python ${{ inputs.python-version }} "$VIRTUAL_ENV"
|
||||
DEPS="${{ inputs.deps }}"
|
||||
uv pip install --python .venv -e ".[${DEPS// /,}]" ${{ inputs.pydeps }} --torch-backend cpu --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/Triton-Nightly/pypi/simple/
|
||||
uv pip install --python "$VIRTUAL_ENV" -e ".[${DEPS// /,}]" ${{ inputs.pydeps }} --torch-backend cpu --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/Triton-Nightly/pypi/simple/
|
||||
- name: Install dependencies in venv (without extra)
|
||||
if: inputs.deps == ''
|
||||
shell: bash
|
||||
run: |
|
||||
uv venv .venv
|
||||
uv pip install --python .venv -e . ${{ inputs.pydeps }}
|
||||
uv venv --allow-existing --python ${{ inputs.python-version }} "$VIRTUAL_ENV"
|
||||
uv pip install --python "$VIRTUAL_ENV" -e . ${{ inputs.pydeps }}
|
||||
- name: Prune uv cache
|
||||
if: github.event_name != 'pull_request'
|
||||
shell: bash
|
||||
@@ -125,16 +125,15 @@ runs:
|
||||
- name: Configure venv
|
||||
shell: bash
|
||||
run: |
|
||||
echo "VIRTUAL_ENV=${{ github.workspace }}/.venv" >> "$GITHUB_ENV"
|
||||
if [[ "$RUNNER_OS" == "Windows" ]]; then
|
||||
echo "${{ github.workspace }}/.venv/Scripts" >> "$GITHUB_PATH"
|
||||
echo "$VIRTUAL_ENV/Scripts" >> "$GITHUB_PATH"
|
||||
else
|
||||
echo "${{ github.workspace }}/.venv/bin" >> "$GITHUB_PATH"
|
||||
echo "$VIRTUAL_ENV/bin" >> "$GITHUB_PATH"
|
||||
fi
|
||||
|
||||
# ******************* apt *******************
|
||||
- name: Setup apt
|
||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true')
|
||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true' || inputs.ninja == 'true')
|
||||
shell: bash
|
||||
run: |
|
||||
sudo mkdir -p /var/cache/apt/archives
|
||||
@@ -162,7 +161,7 @@ runs:
|
||||
echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-20 main" | sudo tee /etc/apt/sources.list.d/llvm.list
|
||||
|
||||
- name: Compute Package List + Hash
|
||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true')
|
||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true' || inputs.ninja == 'true')
|
||||
id: apt-pkgs
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -187,25 +186,37 @@ runs:
|
||||
if [[ "${{ inputs.qemu }}" == "true" ]]; then
|
||||
pkgs+=" qemu-user-static"
|
||||
fi
|
||||
# **** ninja ****
|
||||
if [[ "${{ inputs.ninja }}" == "true" ]]; then
|
||||
pkgs+=" ninja-build"
|
||||
fi
|
||||
|
||||
echo "pkgs=$pkgs" >> "$GITHUB_OUTPUT"
|
||||
echo "hash=$(echo -n "$pkgs" | sha256sum | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
installed=true
|
||||
for pkg in $pkgs; do
|
||||
info=$(dpkg-query -W -f='${db:Status-Abbrev} ${Version}' "$pkg" 2> /dev/null || true)
|
||||
echo "${pkg}: ${info:-not in dpkg database}"
|
||||
[[ "$info" == ii* ]] || installed=false
|
||||
done
|
||||
echo "installed=$installed" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache apt (PR)
|
||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true') && github.event_name == 'pull_request'
|
||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true' || inputs.ninja == 'true') && github.event_name == 'pull_request' && steps.apt-pkgs.outputs.installed == 'false'
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: /var/cache/apt/archives/
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-apt-${{ steps.apt-pkgs.outputs.hash }}-${{ env.CACHE_VERSION }}
|
||||
- name: Cache apt
|
||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true') && github.event_name != 'pull_request'
|
||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true' || inputs.ninja == 'true') && github.event_name != 'pull_request' && steps.apt-pkgs.outputs.installed == 'false'
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: /var/cache/apt/archives/
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-apt-${{ steps.apt-pkgs.outputs.hash }}-${{ env.CACHE_VERSION }}
|
||||
|
||||
- name: Run apt Update + Install
|
||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true')
|
||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true' || inputs.ninja == 'true') && steps.apt-pkgs.outputs.installed == 'false'
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt -qq update || true
|
||||
@@ -277,12 +288,6 @@ runs:
|
||||
shell: bash
|
||||
run: brew install llvm@20
|
||||
|
||||
# *** tinydreno ***
|
||||
- name: Install tinydreno (linux)
|
||||
if: inputs.tinydreno == 'true' && runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: sudo curl -fL https://github.com/sirhcm/tinydreno/raw/refs/heads/master/libllvm-qcom.so -o /usr/lib/libllvm-qcom.so
|
||||
|
||||
# *** OpenCL ***
|
||||
- name: Install rusticl
|
||||
if: inputs.opencl == 'true'
|
||||
|
||||
@@ -35,15 +35,15 @@ jobs:
|
||||
key: 'autogen'
|
||||
amd: 'true'
|
||||
llvm: 'true'
|
||||
pydeps: 'pyyaml mako'
|
||||
deps: 'autogen'
|
||||
- name: Install autogen support packages
|
||||
run: sudo apt-get install -y --no-install-recommends libclang-20-dev llvm-20-dev hip-dev libusb-1.0-0-dev libdrm-dev liburing-dev
|
||||
- name: Regenerate autogen files
|
||||
run: |
|
||||
find tinygrad/runtime/autogen -type f -name "*.py" -not -path "*/amd/*" -not -name "__init__.py" -not -name "comgr.py" -not -name "metal.py" -not -name "iokit.py" -not -name "corefoundation.py" -not -name "libclang.py" -delete
|
||||
find tinygrad/runtime/autogen -type f -name "*.py" -not -path "*/amd/*" -not -name "__init__.py" -not -name "metal.py" -not -name "iokit.py" -not -name "corefoundation.py" -not -name "libclang.py" -delete
|
||||
python3 -c "from tinygrad.runtime.autogen import opencl"
|
||||
python3 -c "from tinygrad.runtime.autogen import cuda, nvrtc, nvjitlink, nv_570, nv_580, nv_610, nv"
|
||||
python3 -c "from tinygrad.runtime.autogen import comgr_3, hsa, hip, amd_gpu, sqtt, rocprof, amdgpu_kd, amdgpu_drm"
|
||||
python3 -c "from tinygrad.runtime.autogen import comgr, comgr_3, hsa, hip, amd_gpu, sqtt, rocprof, amdgpu_kd, amdgpu_drm"
|
||||
python3 -c "from tinygrad.runtime.autogen.am import *"
|
||||
python3 -c "from tinygrad.runtime.autogen.nv_regs import *"
|
||||
python3 -c "from tinygrad.runtime.autogen import libc, kfd, io_uring, pci, vfio"
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
python3 -c "from tinygrad.runtime.autogen import mesa"
|
||||
python3 -c "from tinygrad.runtime.autogen import avcodec"
|
||||
python3 -c "from tinygrad.runtime.autogen import llvm_qcom"
|
||||
python3 -c "from tinygrad.runtime.autogen import mlx5"
|
||||
python3 -c "from tinygrad.runtime.autogen import mlx5, bnxt"
|
||||
python3 -c "from tinygrad.runtime.autogen import ggml_common"
|
||||
REGEN=1 python3 -c "from tinygrad.runtime.autogen import libclang"
|
||||
- name: Check for differences
|
||||
@@ -102,42 +102,3 @@ jobs:
|
||||
with:
|
||||
name: autogen-macos-patch
|
||||
path: autogen-macos.patch
|
||||
|
||||
autogen-comgr-2:
|
||||
name: In-tree Autogen (comgr 2)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: 'autogen-comgr'
|
||||
- name: Install autogen support packages
|
||||
run: |
|
||||
wget https://repo.radeon.com/rocm/rocm.gpg.key -O - | gpg --dearmor | sudo tee /etc/apt/keyrings/rocm.gpg > /dev/null
|
||||
sudo tee /etc/apt/sources.list.d/rocm.list <<EOF
|
||||
deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/rocm/apt/6.2 $(lsb_release -cs) main
|
||||
EOF
|
||||
echo -e 'Package: *\nPin: release o=repo.radeon.com\nPin-Priority: 600' | sudo tee /etc/apt/preferences.d/rocm-pin-600
|
||||
sudo apt -qq update || true
|
||||
sudo apt-get install -y --no-install-recommends libclang-20-dev comgr
|
||||
- name: Regenerate autogen files
|
||||
run: |
|
||||
rm tinygrad/runtime/autogen/comgr.py
|
||||
python3 -c "from tinygrad.runtime.autogen import comgr"
|
||||
- name: Check for differences
|
||||
run: |
|
||||
if ! git diff --quiet; then
|
||||
git diff
|
||||
git diff > autogen-comgr2.patch
|
||||
echo "Autogen mismatch detected. Patch available at: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}#artifacts"
|
||||
exit 1
|
||||
fi
|
||||
- name: Upload patch artifact
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: autogen-comgr2-patch
|
||||
path: autogen-comgr2.patch
|
||||
|
||||
+168
-141
@@ -82,18 +82,22 @@ jobs:
|
||||
# pytest -nauto --durations=20
|
||||
|
||||
llmbenchmark:
|
||||
name: LLM (DEV=${{ matrix.dev }})
|
||||
name: Benchmark ${{ matrix.model }} (DEV=${{ matrix.dev }})
|
||||
runs-on: [self-hosted, "${{ matrix.dev == 'METAL' && 'macOS' || matrix.dev == 'AMD' && 'tinybox' || 'tinyboxgreen' }}"]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
dev: ['METAL', 'AMD', 'NV']
|
||||
timeout-minutes: 60
|
||||
model: ['llama3.2:3b-f16', 'qwen3.8:27b', 'olmoe']
|
||||
# qwen3.8:27b doesn't fit on mac
|
||||
exclude: [{ dev: 'METAL', model: 'qwen3.8:27b' }, { dev: 'AMD', model: 'olmoe' }, { dev: 'NV', model: 'olmoe' }]
|
||||
timeout-minutes: 15
|
||||
defaults:
|
||||
run:
|
||||
shell: bash -e -o pipefail {0}
|
||||
env:
|
||||
DEV: ${{ matrix.dev }}
|
||||
HCQ2: ${{ matrix.dev == 'AMD' && '1' || '0' }}
|
||||
if: github.repository_owner == 'tinygrad'
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
@@ -101,13 +105,11 @@ jobs:
|
||||
- name: Setup (AMD)
|
||||
if: ${{ matrix.dev == 'AMD' }}
|
||||
run: |
|
||||
./extra/amdpci/setup_python_cap.sh
|
||||
./extra/hcq/hcq_smi.py amd rmmod
|
||||
./extra/hcq/hcq_smi.py amd kill_pids
|
||||
- name: Symlink models and datasets
|
||||
run: |
|
||||
mkdir -p weights
|
||||
ln -s /raid/weights/LLaMA-3 weights/LLaMA-3
|
||||
./extra/hcq/hcq_smi.py amd rmmod --expect
|
||||
./extra/hcq/hcq_smi.py amd kill_pids --sudoless
|
||||
- name: Setup (NV)
|
||||
if: ${{ matrix.dev == 'NV' }}
|
||||
run: lsof -tQ /dev/nvidia* | { xargs -r kill -9 || true; }
|
||||
- name: setup staging db
|
||||
if: github.ref == 'refs/heads/update_benchmark_staging'
|
||||
run: |
|
||||
@@ -115,20 +117,10 @@ jobs:
|
||||
rm -f /tmp/staging.db /tmp/staging.db-shm /tmp/staging.db-wal
|
||||
- name: reset process replay
|
||||
run: python3 test/external/process_replay/reset.py
|
||||
- name: Run llama3.2
|
||||
run: BENCHMARK_LOG=llama32_3b-f16 JITBEAM=2 IGNORE_BEAM_CACHE=1 python3 -m tinygrad.llm -m llama3.2:3b-f16 --benchmark --warmup
|
||||
- name: Run qwen3.6
|
||||
# qwen3.6:35b-a3b doesn't fit on mac
|
||||
if: ${{ matrix.dev != 'METAL' }}
|
||||
run: BENCHMARK_LOG=qwen36_35b-a3b JITBEAM=2 IGNORE_BEAM_CACHE=1 python3 -m tinygrad.llm -m qwen3.6:35b-a3b --benchmark --warmup
|
||||
- name: Run olmoe
|
||||
# just metal for now
|
||||
if: ${{ matrix.dev == 'METAL' }}
|
||||
run: BENCHMARK_LOG=olmoe JITBEAM=2 IGNORE_BEAM_CACHE=1 python3 -m tinygrad.llm -m olmoe --benchmark --warmup
|
||||
- name: Run LLaMA-3 8B on 4 GPUs with BEAM
|
||||
# only run on machines with multiple gpus
|
||||
if: ${{ matrix.dev != 'METAL' }}
|
||||
run: BENCHMARK_LOG=llama3_beam_4gpu JITBEAM=2 IGNORE_BEAM_CACHE=1 CAPTURE_PROCESS_REPLAY=0 python3 examples/llama3.py --size 8B --shard 4 --model weights/LLaMA-3/8B-SF-DPO/ --benchmark --temperature 0
|
||||
- name: Run ${{ matrix.model }}
|
||||
run: |
|
||||
MODEL=${{ matrix.model }}
|
||||
BENCHMARK_LOG=${MODEL//./} JITBEAM=2 IGNORE_BEAM_CACHE=1 python3 -m tinygrad.llm -m $MODEL --benchmark --warmup
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
@@ -139,12 +131,13 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
dev: ['METAL', 'AMD', 'NV']
|
||||
timeout-minutes: 60
|
||||
timeout-minutes: 10
|
||||
defaults:
|
||||
run:
|
||||
shell: bash -e -o pipefail {0}
|
||||
env:
|
||||
DEV: ${{ matrix.dev }}
|
||||
HCQ2: ${{ matrix.dev == 'AMD' && '1' || '0' }}
|
||||
if: github.repository_owner == 'tinygrad'
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
@@ -152,9 +145,11 @@ jobs:
|
||||
- name: Setup (AMD)
|
||||
if: ${{ matrix.dev == 'AMD' }}
|
||||
run: |
|
||||
./extra/amdpci/setup_python_cap.sh
|
||||
./extra/hcq/hcq_smi.py amd rmmod
|
||||
./extra/hcq/hcq_smi.py amd kill_pids
|
||||
./extra/hcq/hcq_smi.py amd rmmod --expect
|
||||
./extra/hcq/hcq_smi.py amd kill_pids --sudoless
|
||||
- name: Setup (NV)
|
||||
if: ${{ matrix.dev == 'NV' }}
|
||||
run: lsof -tQ /dev/nvidia* | { xargs -r kill -9 || true; }
|
||||
- name: setup staging db
|
||||
if: github.ref == 'refs/heads/update_benchmark_staging'
|
||||
run: |
|
||||
@@ -174,10 +169,6 @@ jobs:
|
||||
# slow on metal
|
||||
if: ${{ matrix.dev != 'METAL' }}
|
||||
run: time BENCHMARK_LOG=cifar DEFAULT_FLOAT=HALF STEPS=1000 TARGET_EVAL_ACC_PCT=93.0 python3 examples/hlb_cifar10.py
|
||||
- name: Run full CIFAR training steps w 6 GPUS
|
||||
# only run on machines with multiple gpus
|
||||
if: ${{ matrix.dev != 'METAL' }}
|
||||
run: time BENCHMARK_LOG=cifar_6gpu CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF STEPS=350 BS=1536 GPUS=6 TARGET_EVAL_ACC_PCT=93.0 python3 examples/hlb_cifar10.py
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
@@ -188,12 +179,13 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
dev: ['AMD', 'NV']
|
||||
timeout-minutes: 60
|
||||
timeout-minutes: 5
|
||||
defaults:
|
||||
run:
|
||||
shell: bash -e -o pipefail {0}
|
||||
env:
|
||||
DEV: ${{ matrix.dev }}
|
||||
HCQ2: ${{ matrix.dev == 'AMD' && '1' || '0' }}
|
||||
if: github.repository_owner == 'tinygrad'
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
@@ -201,9 +193,11 @@ jobs:
|
||||
- name: Setup (AMD)
|
||||
if: ${{ matrix.dev == 'AMD' }}
|
||||
run: |
|
||||
./extra/amdpci/setup_python_cap.sh
|
||||
./extra/hcq/hcq_smi.py amd rmmod
|
||||
./extra/hcq/hcq_smi.py amd kill_pids
|
||||
./extra/hcq/hcq_smi.py amd rmmod --expect
|
||||
./extra/hcq/hcq_smi.py amd kill_pids --sudoless
|
||||
- name: Setup (NV)
|
||||
if: ${{ matrix.dev == 'NV' }}
|
||||
run: lsof -tQ /dev/nvidia* | { xargs -r kill -9 || true; }
|
||||
- name: Symlink models and datasets
|
||||
run: |
|
||||
mkdir -p extra/datasets
|
||||
@@ -215,15 +209,8 @@ jobs:
|
||||
rm -f /tmp/staging.db /tmp/staging.db-shm /tmp/staging.db-wal
|
||||
- name: reset process replay
|
||||
run: test/external/process_replay/reset.py
|
||||
- name: Run MLPerf resnet eval on training data
|
||||
run: time BENCHMARK_LOG=resnet_eval MODEL=resnet python3 examples/mlperf/model_eval.py
|
||||
- name: Run 10 MLPerf ResNet50 training steps (1 gpu)
|
||||
run: BENCHMARK_LOG=resnet_10steps DEFAULT_FLOAT=HALF BENCHMARK=10 BS=256 GPUS=1 MODEL=resnet python3 examples/mlperf/model_train.py
|
||||
- name: Run 10 MLPerf ResNet50 training steps (6 gpu)
|
||||
run: BENCHMARK_LOG=resnet_10steps_6gpu CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=1536 GPUS=6 MODEL=resnet python3 examples/mlperf/model_train.py
|
||||
- name: Run 10 MLPerf Bert training steps (6 gpu)
|
||||
# TODO: remove BERT_LAYERS once scheduler is fast
|
||||
run: BENCHMARK_LOG=bert_10steps_6gpu CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=72 GPUS=6 BERT_LAYERS=2 MODEL=bert python3 examples/mlperf/model_train.py
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
@@ -234,12 +221,13 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
dev: ['METAL', 'AMD', 'NV']
|
||||
timeout-minutes: 60
|
||||
timeout-minutes: 15
|
||||
defaults:
|
||||
run:
|
||||
shell: bash -e -o pipefail {0}
|
||||
env:
|
||||
DEV: ${{ matrix.dev }}
|
||||
HCQ2: ${{ matrix.dev == 'AMD' && '1' || '0' }}
|
||||
if: github.repository_owner == 'tinygrad'
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
@@ -247,9 +235,11 @@ jobs:
|
||||
- name: Setup (AMD)
|
||||
if: ${{ matrix.dev == 'AMD' }}
|
||||
run: |
|
||||
./extra/amdpci/setup_python_cap.sh
|
||||
./extra/hcq/hcq_smi.py amd rmmod
|
||||
./extra/hcq/hcq_smi.py amd kill_pids
|
||||
./extra/hcq/hcq_smi.py amd rmmod --expect
|
||||
./extra/hcq/hcq_smi.py amd kill_pids --sudoless
|
||||
- name: Setup (NV)
|
||||
if: ${{ matrix.dev == 'NV' }}
|
||||
run: lsof -tQ /dev/nvidia* | { xargs -r kill -9 || true; }
|
||||
- name: setup staging db
|
||||
if: github.ref == 'refs/heads/update_benchmark_staging'
|
||||
run: |
|
||||
@@ -269,6 +259,59 @@ jobs:
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
multigpubenchmark:
|
||||
name: Multi-GPU Benchmarks (DEV=${{ matrix.dev }})
|
||||
runs-on: [self-hosted, "${{ matrix.dev == 'AMD' && 'tinybox' || 'tinyboxgreen' }}"]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
dev: ['AMD', 'NV']
|
||||
timeout-minutes: 20
|
||||
defaults:
|
||||
run:
|
||||
shell: bash -e -o pipefail {0}
|
||||
env:
|
||||
DEV: ${{ matrix.dev }}
|
||||
HCQ2: ${{ matrix.dev == 'AMD' && '1' || '0' }}
|
||||
if: github.repository_owner == 'tinygrad'
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup (AMD)
|
||||
if: ${{ matrix.dev == 'AMD' }}
|
||||
run: |
|
||||
./extra/hcq/hcq_smi.py amd rmmod --expect
|
||||
./extra/hcq/hcq_smi.py amd kill_pids --sudoless
|
||||
- name: Setup (NV)
|
||||
if: ${{ matrix.dev == 'NV' }}
|
||||
run: lsof -tQ /dev/nvidia* | { xargs -r kill -9 || true; }
|
||||
- name: Symlink models and datasets
|
||||
run: |
|
||||
mkdir -p weights
|
||||
mkdir -p extra/datasets
|
||||
ln -s /raid/weights/LLaMA-3 weights/LLaMA-3
|
||||
ln -s /raid/datasets/imagenet extra/datasets/imagenet
|
||||
- name: setup staging db
|
||||
if: github.ref == 'refs/heads/update_benchmark_staging'
|
||||
run: |
|
||||
echo "CACHEDB=/tmp/staging.db" >> $GITHUB_ENV
|
||||
rm -f /tmp/staging.db /tmp/staging.db-shm /tmp/staging.db-wal
|
||||
- name: reset process replay
|
||||
run: python3 test/external/process_replay/reset.py
|
||||
- name: Run LLaMA-3 8B on 4 GPUs with BEAM
|
||||
run: BENCHMARK_LOG=llama3_beam_4gpu JITBEAM=2 IGNORE_BEAM_CACHE=1 CAPTURE_PROCESS_REPLAY=0 python3 examples/llama3.py --size 8B --shard 4 --model weights/LLaMA-3/8B-SF-DPO/ --benchmark --temperature 0
|
||||
- name: Run full CIFAR training steps w 6 GPUS
|
||||
run: time BENCHMARK_LOG=cifar_6gpu CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF STEPS=350 BS=1536 GPUS=6 TARGET_EVAL_ACC_PCT=93.0 python3 examples/hlb_cifar10.py
|
||||
- name: Run MLPerf resnet eval on training data
|
||||
run: time BENCHMARK_LOG=resnet_eval MODEL=resnet python3 examples/mlperf/model_eval.py
|
||||
- name: Run 10 MLPerf ResNet50 training steps (6 gpu)
|
||||
run: BENCHMARK_LOG=resnet_10steps_6gpu CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=1536 GPUS=6 MODEL=resnet python3 examples/mlperf/model_train.py
|
||||
- name: Run 10 MLPerf Bert training steps (6 gpu)
|
||||
# TODO: remove BERT_LAYERS once scheduler is fast
|
||||
run: BENCHMARK_LOG=bert_10steps_6gpu CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=72 GPUS=6 BERT_LAYERS=2 MODEL=bert python3 examples/mlperf/model_train.py
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
tests:
|
||||
name: Tests (DEV=${{ matrix.dev }})
|
||||
runs-on: [self-hosted, "${{ matrix.dev == 'METAL' && 'macOS' || matrix.dev == 'AMD' && 'tinybox' || 'tinyboxgreen' }}"]
|
||||
@@ -276,7 +319,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
dev: ['METAL', 'AMD', 'NV']
|
||||
timeout-minutes: 60
|
||||
timeout-minutes: 11
|
||||
defaults:
|
||||
run:
|
||||
shell: bash -e -o pipefail {0}
|
||||
@@ -289,9 +332,11 @@ jobs:
|
||||
- name: Setup (AMD)
|
||||
if: ${{ matrix.dev == 'AMD' }}
|
||||
run: |
|
||||
./extra/amdpci/setup_python_cap.sh
|
||||
./extra/hcq/hcq_smi.py amd rmmod
|
||||
./extra/hcq/hcq_smi.py amd kill_pids
|
||||
./extra/hcq/hcq_smi.py amd rmmod --expect
|
||||
./extra/hcq/hcq_smi.py amd kill_pids --sudoless
|
||||
- name: Setup (NV)
|
||||
if: ${{ matrix.dev == 'NV' }}
|
||||
run: lsof -tQ /dev/nvidia* | { xargs -r kill -9 || true; }
|
||||
- name: setup staging db
|
||||
if: github.ref == 'refs/heads/update_benchmark_staging'
|
||||
run: |
|
||||
@@ -353,9 +398,10 @@ jobs:
|
||||
- name: Test benchmark allreduce
|
||||
if: ${{ matrix.dev == 'NV' }}
|
||||
run: python test/external/external_benchmark_multitensor_allreduce.py
|
||||
- name: HEVC Decode Benchmark
|
||||
if: ${{ matrix.dev == 'NV' }}
|
||||
run: VALIDATE=1 MAX_FRAMES=100 ASSERT_FPS=1400 JITBEAM=1 PYTHONPATH=. python3 extra/hevc/decode.py
|
||||
# TODO: HEVC decode timing test
|
||||
# - name: HEVC Decode Benchmark
|
||||
# if: ${{ matrix.dev == 'NV' }}
|
||||
# run: IGNORE_BEAM_CACHE=1 VALIDATE=1 MAX_FRAMES=100 ASSERT_FPS=1400 JITBEAM=1 PYTHONPATH=. python3 extra/hevc/decode.py
|
||||
- uses: actions/upload-artifact@v7
|
||||
if: ${{ matrix.dev != 'AMD' }}
|
||||
with:
|
||||
@@ -368,7 +414,7 @@ jobs:
|
||||
testusbgpu:
|
||||
name: UsbGPU Benchmark
|
||||
runs-on: [self-hosted, macOS]
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 3
|
||||
defaults:
|
||||
run:
|
||||
shell: bash -e -o pipefail {0}
|
||||
@@ -383,32 +429,64 @@ jobs:
|
||||
rm -f /tmp/staging.db /tmp/staging.db-shm /tmp/staging.db-wal
|
||||
- name: Kill stale pids
|
||||
run: |
|
||||
PYTHONPATH=. ./extra/hcq/hcq_smi.py amd kill_pids
|
||||
PYTHONPATH=. ./extra/hcq/hcq_smi.py nv kill_pids
|
||||
# since sudo is required for usbgpu on macos, do not write bytecode, as some of the files are owned by root
|
||||
./extra/hcq/hcq_smi.py amd kill_pids --sudoless
|
||||
./extra/hcq/hcq_smi.py nv kill_pids --sudoless
|
||||
- name: UsbGPU boot time
|
||||
run: sudo -E PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=. GMMU=0 DEBUG=2 AM_RESET=1 DEV=USB+AMD time python3.11 test/test_tiny.py TestTiny.test_plus
|
||||
run: GMMU=0 DEBUG=2 AM_RESET=1 DEV=USB+AMD time python3.11 test/test_tiny.py TestTiny.test_plus
|
||||
- name: UsbGPU tiny tests
|
||||
run: sudo -E PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=. GMMU=0 DEV=USB+AMD python3.11 test/test_tiny.py
|
||||
run: GMMU=0 DEV=USB+AMD python3.11 test/test_tiny.py
|
||||
- name: UsbGPU copy speeds
|
||||
run: sudo -E PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=. GMMU=0 DEV=USB+AMD python3.11 test/external/external_test_usb_asm24.py TestDevCopySpeeds
|
||||
#- name: UsbGPU openpilot test
|
||||
# run: sudo -E PYTHONPATH=. GMMU=0 DEV=USB+AMD GRAPH_ONE_KERNEL=1 python3.11 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/9118973ed03c1ae1d40cf69a29507ec2cc78efd7/selfdrive/modeld/models/supercombo.onnx
|
||||
- name: UsbGPU (USB4/TB) install script
|
||||
run: PYTHONPATH=. sh extra/setup_tinygpu_osx.sh
|
||||
- name: UsbGPU (USB4/TB) boot time
|
||||
run: PYTHONPATH=. DEBUG=3 DEV=PCI+NV:NAK time python3.11 test/test_tiny.py TestTiny.test_plus
|
||||
- name: UsbGPU (USB4/TB) tiny tests
|
||||
run: PYTHONPATH=. DEV=PCI+NV:NAK python3.11 test/test_tiny.py
|
||||
run: SIZE=64000000 PYTHONPATH=. GMMU=0 DEV=USB+AMD python3.11 test/external/external_test_usb_asm24.py
|
||||
|
||||
testcommalatest:
|
||||
name: comma Benchmark (0.11.2)
|
||||
runs-on: [self-hosted, Linux, comma]
|
||||
timeout-minutes: 12
|
||||
testcomma:
|
||||
strategy:
|
||||
matrix:
|
||||
dev: ['QCOM', 'QCOM:IR3']
|
||||
version: ['0.11.0', '0.11.2']
|
||||
model: ['vision', 'policy', 'supercombo', 'dmonitoring']
|
||||
# exclude non-existent models
|
||||
exclude: [{ version: '0.11.0', model: supercombo }, { version: '0.11.2', model: vision }, { version: '0.11.2', model: policy }]
|
||||
include:
|
||||
- version: '0.11.0'
|
||||
model: vision
|
||||
url: https://github.com/commaai/openpilot/raw/v0.11.0/selfdrive/modeld/models/driving_vision.onnx
|
||||
timing: 18
|
||||
- version: '0.11.0'
|
||||
model: policy
|
||||
url: https://github.com/commaai/openpilot/raw/v0.11.0/selfdrive/modeld/models/driving_policy.onnx
|
||||
timing: 3.4
|
||||
- version: '0.11.0'
|
||||
model: dmonitoring
|
||||
url: https://github.com/commaai/openpilot/raw/v0.11.0/selfdrive/modeld/models/dmonitoring_model.onnx
|
||||
timing: 13
|
||||
- version: '0.11.2'
|
||||
model: supercombo
|
||||
url: https://gitlab.com/commaai/openpilot-lfs.git/gitlab-lfs/objects/433f85f956837606ad1f1cbee4aa7e2158ad23c768dea914b20436c97232741b
|
||||
timing: 28
|
||||
- dev: QCOM:IR3
|
||||
version: '0.11.2'
|
||||
model: supercombo
|
||||
timing: 29
|
||||
- version: '0.11.2'
|
||||
model: dmonitoring
|
||||
url: https://gitlab.com/commaai/openpilot-lfs.git/gitlab-lfs/objects/3e7b31dfbc0a5234f1baf196513b77fc6af12204b8a8ffe8ee0417e48352f316
|
||||
timing: 12.5
|
||||
# IR3 dmonitoring is slightly slower
|
||||
- dev: QCOM:IR3
|
||||
model: dmonitoring
|
||||
timing: 13.0
|
||||
fail-fast: false
|
||||
name: openpilot ${{ matrix.version }} compile3 ${{ matrix.model }} (DEV=${{ matrix.dev }})
|
||||
runs-on: [self-hosted, Linux, comma4]
|
||||
timeout-minutes: 5
|
||||
defaults:
|
||||
run:
|
||||
shell: bash -e -o pipefail {0}
|
||||
if: github.repository_owner == 'tinygrad'
|
||||
env:
|
||||
DEV: ${{ matrix.dev }}
|
||||
ASSERT_MIN_STEP_TIME: ${{ matrix.timing }}
|
||||
BENCHMARK_LOG: ${{ matrix.dev == 'QCOM:IR3' && 'ir3_' || '' }}openpilot_${{ matrix.version }}_${{ matrix.model }}
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
@@ -419,45 +497,10 @@ jobs:
|
||||
rm -f /tmp/staging.db /tmp/staging.db-shm /tmp/staging.db-wal
|
||||
- name: reset process replay
|
||||
run: test/external/process_replay/reset.py
|
||||
- name: openpilot compile3 0.11.2 supercombo
|
||||
run: BENCHMARK_LOG=openpilot_0_11_2_supercombo PYTHONPATH="." ASSERT_MIN_STEP_TIME=26 DEV=QCOM FLOAT16=1 IMAGE=1 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://gitlab.com/commaai/openpilot-lfs.git/gitlab-lfs/objects/433f85f956837606ad1f1cbee4aa7e2158ad23c768dea914b20436c97232741b
|
||||
- name: openpilot compile3 0.11.2 supercombo (from pickle)
|
||||
run: BENCHMARK_LOG=openpilot_0_11_2_supercombo_run_pickle RUN_PICKLE=1 PYTHONPATH="." ASSERT_MIN_STEP_TIME=26 DEV=QCOM taskset -c 4-7 python3 examples/openpilot/compile3.py
|
||||
- name: IR3 openpilot compile3 0.11.2 supercombo
|
||||
run: BENCHMARK_LOG=ir3_openpilot_0_11_2_supercombo PYTHONPATH="." ASSERT_MIN_STEP_TIME=41 DEV=QCOM:IR3 FLOAT16=1 IMAGE=1 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://gitlab.com/commaai/openpilot-lfs.git/gitlab-lfs/objects/433f85f956837606ad1f1cbee4aa7e2158ad23c768dea914b20436c97232741b
|
||||
- name: openpilot compile3 0.11.2 dmonitoring
|
||||
run: BENCHMARK_LOG=openpilot_0_11_2_dmonitoring PYTHONPATH="." ASSERT_MIN_STEP_TIME=11 DEV=QCOM FLOAT16=1 IMAGE=1 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://gitlab.com/commaai/openpilot-lfs.git/gitlab-lfs/objects/3e7b31dfbc0a5234f1baf196513b77fc6af12204b8a8ffe8ee0417e48352f316
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
testcommaold:
|
||||
name: comma Benchmark (0.11.0)
|
||||
runs-on: [self-hosted, Linux, comma]
|
||||
timeout-minutes: 12
|
||||
defaults:
|
||||
run:
|
||||
shell: bash -e -o pipefail {0}
|
||||
if: github.repository_owner == 'tinygrad'
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: setup staging db
|
||||
if: github.ref == 'refs/heads/update_benchmark_staging'
|
||||
run: |
|
||||
echo "CACHEDB=/tmp/staging.db" >> $GITHUB_ENV
|
||||
rm -f /tmp/staging.db /tmp/staging.db-shm /tmp/staging.db-wal
|
||||
- name: reset process replay
|
||||
run: test/external/process_replay/reset.py
|
||||
- name: openpilot compile3 0.11.0 driving_vision
|
||||
run: BENCHMARK_LOG=openpilot_0_11_0_vision PYTHONPATH="." ASSERT_MIN_STEP_TIME=17 DEV=QCOM FLOAT16=1 IMAGE=1 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/v0.11.0/selfdrive/modeld/models/driving_vision.onnx
|
||||
- name: openpilot compile3 0.11.0 driving_vision (from pickle)
|
||||
run: BENCHMARK_LOG=openpilot_0_11_0_vision_run_pickle RUN_PICKLE=1 PYTHONPATH="." ASSERT_MIN_STEP_TIME=17 DEV=QCOM taskset -c 4-7 python3 examples/openpilot/compile3.py
|
||||
- name: IR3 openpilot compile3 0.11.0 driving_vision
|
||||
run: BENCHMARK_LOG=ir3_openpilot_0_11_0_vision PYTHONPATH="." ASSERT_MIN_STEP_TIME=18 DEV=QCOM:IR3 FLOAT16=1 IMAGE=1 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/v0.11.0/selfdrive/modeld/models/driving_vision.onnx
|
||||
- name: openpilot compile3 0.11.0 driving_policy
|
||||
run: BENCHMARK_LOG=openpilot_0_11_0_policy PYTHONPATH="." ASSERT_MIN_STEP_TIME=3.2 DEV=QCOM FLOAT16=1 IMAGE=1 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/v0.11.0/selfdrive/modeld/models/driving_policy.onnx
|
||||
- name: openpilot compile3 0.11.0 dmonitoring
|
||||
run: BENCHMARK_LOG=openpilot_0_11_0_dmonitoring PYTHONPATH="." ASSERT_MIN_STEP_TIME=11 DEV=QCOM FLOAT16=1 IMAGE=1 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/v0.11.0/selfdrive/modeld/models/dmonitoring_model.onnx
|
||||
- name: compile
|
||||
run: FLOAT16=1 IMAGE=1 taskset -c 4-7 python3 examples/openpilot/compile3.py ${{ matrix.url }} openpilot.pkl
|
||||
- name: run pickle
|
||||
run: BENCHMARK_LOG="${BENCHMARK_LOG}_run_pickle" RUN_PICKLE=1 taskset -c 4-7 python3 examples/openpilot/compile3.py - openpilot.pkl
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
@@ -470,15 +513,6 @@ jobs:
|
||||
shell: bash -e -o pipefail {0}
|
||||
if: github.repository_owner == 'tinygrad'
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: setup staging db
|
||||
if: github.ref == 'refs/heads/update_benchmark_staging'
|
||||
run: |
|
||||
echo "CACHEDB=/tmp/staging.db" >> $GITHUB_ENV
|
||||
rm -f /tmp/staging.db /tmp/staging.db-shm /tmp/staging.db-wal
|
||||
- name: reset process replay
|
||||
run: test/external/process_replay/reset.py
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: setup staging db
|
||||
@@ -491,8 +525,8 @@ jobs:
|
||||
- name: benchmark MobileNetV2 on DSP
|
||||
run: |
|
||||
# generate quantized weights
|
||||
ln -s /data/home/tiny/tinygrad/extra/datasets/imagenet extra/datasets/imagenet
|
||||
ln -s /data/home/tiny/tinygrad/testsig-*.so .
|
||||
ln -s ~/tinygrad/extra/datasets/imagenet extra/datasets/imagenet
|
||||
ln -s ~/tinygrad/testsig-*.so .
|
||||
PYTHONPATH=. DEV=CPU QUANT=1 CNT=0 python3 examples/test_onnx_imagenet.py https://github.com/xamcat/mobcat-samples/raw/refs/heads/master/onnx_runtime/InferencingSample/InferencingSample/mobilenetv2-7.onnx /tmp/model.quant.onnx
|
||||
# benchmark on DSP with NOOPT=1, the devectorizer has issues
|
||||
PYTHONPATH=. DEV=DSP NOOPT=1 CNT=2 DEBUG=2 python3 examples/test_onnx_imagenet.py /tmp/model.quant.onnx
|
||||
@@ -502,7 +536,7 @@ jobs:
|
||||
testcommausbgpubenchmark:
|
||||
name: UsbGPU Benchmark (comma)
|
||||
runs-on: [self-hosted, Linux, comma4]
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 10
|
||||
defaults:
|
||||
run:
|
||||
shell: bash -e -o pipefail {0}
|
||||
@@ -522,7 +556,7 @@ jobs:
|
||||
- name: openpilot run_pickle big_driving_supercombo
|
||||
run: BENCHMARK_LOG=usbgpu_openpilot_big_driving_supercombo_run_pickle RUN_PICKLE=1 PICKLE_OOB=1 PYTHONPATH="." GMMU=0 DEV=USB+AMD ASSERT_MIN_STEP_TIME=50 python3 examples/openpilot/compile3.py - openpilot.pkl
|
||||
- name: Test copy speeds
|
||||
run: SIZE=64e6 PYTHONPATH=. GMMU=0 DEV=USB+AMD python3 test/external/external_test_usb_asm24.py TestDevCopySpeeds
|
||||
run: SIZE=64000000 PYTHONPATH=. GMMU=0 DEV=USB+AMD python3 test/external/external_test_usb_asm24.py
|
||||
|
||||
driverbenchmarks:
|
||||
name: PCI Driver Benchmark (DEV=${{ matrix.dev }})
|
||||
@@ -531,7 +565,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
dev: ['AMD', 'NV']
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
defaults:
|
||||
run:
|
||||
shell: bash -e -o pipefail {0}
|
||||
@@ -543,9 +577,8 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup
|
||||
run: |
|
||||
./extra/amdpci/setup_python_cap.sh
|
||||
./extra/hcq/hcq_smi.py ${{ matrix.dev == 'AMD' && 'amd' || 'nv' }} rmmod
|
||||
./extra/hcq/hcq_smi.py ${{ matrix.dev == 'AMD' && 'amd' || 'nv' }} kill_pids
|
||||
./extra/hcq/hcq_smi.py ${{ matrix.dev }} rmmod --expect
|
||||
./extra/hcq/hcq_smi.py ${{ matrix.dev }} kill_pids --sudoless
|
||||
mkdir -p extra/datasets
|
||||
ln -s /raid/datasets/imagenet extra/datasets/imagenet
|
||||
- name: setup staging db
|
||||
@@ -580,34 +613,28 @@ jobs:
|
||||
run: |
|
||||
GRAPH_ONE_KERNEL=1 NSZ=8192 python3 test/speed/external_test_copy_speed.py TestCopySpeed.testCopyDefaulttoCPUJit
|
||||
GRAPH_ONE_KERNEL=1 NSZ=8192 python3 test/speed/external_test_copy_speed.py TestCopySpeed.testCopyCPUtoDefaultJit
|
||||
# TODO: HEVC decode timing test
|
||||
# - name: HEVC Decode Benchmark
|
||||
# if: ${{ matrix.dev == 'NV' }}
|
||||
# run: IGNORE_BEAM_CACHE=1 VALIDATE=1 MAX_FRAMES=100 ASSERT_FPS=1400 JITBEAM=1 PYTHONPATH=. python3 extra/hevc/decode.py
|
||||
- name: Run 10 MLPerf ResNet50 training steps (1 gpu)
|
||||
if: ${{ matrix.dev == 'NV' }}
|
||||
run: BENCHMARK_LOG=resnet_10steps MNISTMOCK=1 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=256 GPUS=1 MODEL=resnet python3 examples/mlperf/model_train.py
|
||||
- name: Run 10 MLPerf Bert training steps (1 gpu)
|
||||
# TODO: remove BERT_LAYERS once scheduler is fast
|
||||
run: BENCHMARK_LOG=bert_10steps CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=66 GPUS=1 BERT_LAYERS=2 MODEL=bert python3 examples/mlperf/model_train.py
|
||||
- name: Remote
|
||||
run: |
|
||||
pkill -f 'extra/remote/serve.py' || true
|
||||
PYTHONPATH=. python3 extra/remote/serve.py 6482 &
|
||||
sleep 1
|
||||
DEBUG=2 PYTHONPATH=. REMOTE=127.0.0.1:6482 AM_RESET=1 python3 test/test_tiny.py
|
||||
if [[ "${{ matrix.dev }}" == "AMD" ]]; then
|
||||
DEBUG=2 PYTHONPATH=. REMOTE=127.0.0.1:6482 AM_RESET=1 AMD_AQL=1 python3 test/test_tiny.py
|
||||
fi
|
||||
pkill -f 'extra/remote/serve.py' || true
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
llvmspeed:
|
||||
name: LLVM Speed
|
||||
runs-on: [self-hosted, Linux, tinyboxrandom]
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 10
|
||||
if: github.repository_owner == 'tinygrad'
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Speed Test
|
||||
run: DEV=CPU:LLVM THREADS=0 python3 test/speed/external_test_speed_v_torch.py
|
||||
run: DEV=CPU:LLVM python3 test/speed/external_test_speed_v_torch.py
|
||||
- name: Speed Test (BEAM=2)
|
||||
run: BEAM=2 DEV=CPU:LLVM THREADS=0 python3 test/speed/external_test_speed_v_torch.py
|
||||
run: IGNORE_BEAM_CACHE=1 BEAM=2 DEV=CPU:LLVM python3 test/speed/external_test_speed_v_torch.py
|
||||
|
||||
@@ -8,7 +8,7 @@ permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Configure Git Credentials
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
name: Platform Tests
|
||||
env:
|
||||
# increment this when downloads substantially change to avoid the internet
|
||||
CACHE_VERSION: '19'
|
||||
CAPTURE_PROCESS_REPLAY: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.title, '[pr]') && '1' || '0' }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
CHECK_OOB: 1
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: platform-${{ github.event_name }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
|
||||
# ****** OSX Tests ******
|
||||
|
||||
unittestmacos:
|
||||
name: MacOS (unit)
|
||||
runs-on: macos-26
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: unittest-macos
|
||||
deps: testing_unit
|
||||
- name: Run unit tests
|
||||
run: DEV=METAL python -m pytest -n=auto test/unit/ --durations=20
|
||||
- name: Run opt tests
|
||||
run: DEV=METAL python -m pytest -n=auto test/opt --durations=20
|
||||
- name: Test tensor core ops (fake)
|
||||
run: DEV=METAL DEBUG=3 TC=2 python test/backend/test_ops.py TestOps.test_gemm
|
||||
- name: Test tensor core ops (real)
|
||||
run: DEV=METAL DEBUG=3 python test/backend/test_ops.py TestOps.test_big_gemm
|
||||
- name: Test Beam Search
|
||||
run: DEV=METAL IGNORE_BEAM_CACHE=1 python3 -m pytest extra/optimization/test_beam_search.py
|
||||
- name: Test Device Specific
|
||||
run: DEV=METAL python3 -m pytest test/device/test_metal.py
|
||||
#- name: Fuzz Test linearizer
|
||||
# run: DEV=METAL DEPTH=4 FUZZ_N=50 FUZZ_MAX_SIZE=1000000 python test/external/fuzz_linearizer.py
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
unittestmacosmock:
|
||||
name: MacOS (unit, mock)
|
||||
runs-on: macos-26
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: unittest-macos-mock
|
||||
deps: testing_unit
|
||||
amd: 'true'
|
||||
ocelot: 'true'
|
||||
- name: Run NULL backend tests
|
||||
run: SPEC=2 DEV=NULL python -m pytest -n=auto test/null/ --durations=20
|
||||
- name: Run pytest (amd)
|
||||
env:
|
||||
DEV: MOCKKFD+AMD
|
||||
FORWARD_ONLY: 1
|
||||
run: |
|
||||
python3 -m pytest -n=auto test/device/test_hcq.py test/test_tiny.py --durations=20
|
||||
- name: Run pytest (ptx)
|
||||
env:
|
||||
DEV: "MOCK+NV:PTX"
|
||||
HCQ_RUNTIME_DEV: PYTHON
|
||||
FORWARD_ONLY: 1
|
||||
# TODO: failing due to library loading error
|
||||
CAPTURE_PROCESS_REPLAY: 0
|
||||
run: |
|
||||
python3 -m pytest -n=auto test/device/test_hcq2.py test/test_tiny.py \
|
||||
test/testextra/test_hevc.py::TestHevc::test_hevc_decode_compile --durations=20
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
testmetal:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
group: [1, 2]
|
||||
name: MacOS (DEV=METAL) (${{ matrix.group }})
|
||||
runs-on: macos-26
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
DEV: METAL
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: macos-metal
|
||||
deps: testing_unit
|
||||
- name: Check Device.DEFAULT and print some source
|
||||
run: |
|
||||
python -c "from tinygrad import Device; assert Device.DEFAULT == 'METAL'"
|
||||
DEBUG=4 python test/test_tiny.py TestTiny.test_plus
|
||||
- name: Run backend tests
|
||||
run: python -m pytest -n=auto test/backend --durations=20 --splits 2 --group ${{ matrix.group }}
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
testmacos:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
dev:
|
||||
- 'CPU:CLANG'
|
||||
- 'CPU:LLVM'
|
||||
- 'CPU:LVP'
|
||||
- 'WEBGPU'
|
||||
|
||||
name: MacOS (DEV=${{ matrix.dev }})
|
||||
runs-on: macos-26
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: macos-${{ matrix.dev }}
|
||||
deps: "testing_unit${{ contains(matrix.dev, 'LVP') && ' mesa' || '' }}"
|
||||
llvm: ${{ contains(matrix.dev, 'LLVM') || contains(matrix.dev, 'LVP') }}
|
||||
webgpu: ${{ matrix.dev == 'WEBGPU' }}
|
||||
- name: Set env
|
||||
run: printf "DEV=${{ matrix.dev }}${{ matrix.dev == 'CPU:CLANG' && '\nCPU_COUNT=2' || '' }}" >> $GITHUB_ENV
|
||||
- name: Check Device.DEFAULT and print some source
|
||||
run: |
|
||||
python -c "from tinygrad import Device; from tinygrad.helpers import Target; assert Device.DEFAULT == Target.parse('${{ matrix.dev }}').device"
|
||||
DEBUG=4 python test/test_tiny.py TestTiny.test_plus
|
||||
- name: Run test_tiny
|
||||
run: python -m pytest -n=auto test/test_tiny.py --durations=20
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
# ****** Windows Tests ******
|
||||
|
||||
testwindows:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
dev:
|
||||
- 'CPU:CLANG'
|
||||
- 'CPU:LLVM'
|
||||
- 'CPU:X86'
|
||||
- 'WEBGPU'
|
||||
|
||||
name: Windows (DEV=${{ matrix.dev }})
|
||||
runs-on: windows-2025
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: windows-${{ matrix.dev }}-minimal
|
||||
deps: testing_minimal
|
||||
pydeps: ${{ matrix.dev == 'WEBGPU' && 'dawn-python' || '' }}
|
||||
- name: Set env
|
||||
shell: bash
|
||||
run: printf "DEV=${{ matrix.dev }}${{ matrix.dev == 'CPU:CLANG' && '\nCPU_COUNT=2' || '' }}" >> $GITHUB_ENV
|
||||
- name: Check Device.DEFAULT and print some source
|
||||
shell: bash
|
||||
run: |
|
||||
python -c "from tinygrad import Device; from tinygrad.helpers import Target; assert Device.DEFAULT == Target.parse('${{ matrix.dev }}').device"
|
||||
DEBUG=4 python test/test_tiny.py TestTiny.test_plus
|
||||
- name: Run test_tiny
|
||||
shell: bash
|
||||
run: python -m pytest -n=auto test/test_tiny.py --durations=20
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Python
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# Sticky PR comment via the REST API: find an existing comment containing MARKER and PATCH it, or POST a new one.
|
||||
# Works on GitHub and Gitea (stdlib only, replaces marocchino/sticky-pull-request-comment which needs GraphQL).
|
||||
# Env vars: GITHUB_TOKEN, GITHUB_API_URL, GITHUB_REPOSITORY (set by the runner), PR_NUMBER, MARKER, and BODY_FILE or MESSAGE.
|
||||
import json, os, sys, urllib.request
|
||||
|
||||
api, repo = os.environ["GITHUB_API_URL"], os.environ["GITHUB_REPOSITORY"]
|
||||
pr, marker = os.environ["PR_NUMBER"], os.environ["MARKER"]
|
||||
body = open(os.environ["BODY_FILE"]).read() if os.environ.get("BODY_FILE") else os.environ["MESSAGE"]
|
||||
|
||||
if not body.strip():
|
||||
print("comment body is empty, not posting")
|
||||
sys.exit(0)
|
||||
|
||||
def req(url, method="GET", payload=None):
|
||||
r = urllib.request.Request(url, data=None if payload is None else json.dumps(payload).encode(), method=method,
|
||||
headers={"Authorization": f"token {os.environ['GITHUB_TOKEN']}", "Accept": "application/json", "Content-Type": "application/json"})
|
||||
return json.load(urllib.request.urlopen(r))
|
||||
|
||||
# find the latest sticky comment (paginate, 100 comments per page)
|
||||
existing, page = None, 1
|
||||
while True:
|
||||
comments = req(f"{api}/repos/{repo}/issues/{pr}/comments?per_page=100&page={page}")
|
||||
stickies = [c for c in comments if marker in (c.get("body") or "")]
|
||||
if stickies: existing = stickies[-1]
|
||||
if not comments or len(comments) < 100: break
|
||||
page += 1
|
||||
|
||||
if existing is not None and existing["body"] == body:
|
||||
print("comment is already up to date")
|
||||
sys.exit(0)
|
||||
url = f"{api}/repos/{repo}/issues/comments/{existing['id']}" if existing is not None else f"{api}/repos/{repo}/issues/{pr}/comments"
|
||||
resp = req(url, 'PATCH' if existing is not None else 'POST', {'body': body})
|
||||
print(f"{'updated' if existing is not None else 'created'} comment {resp['id']}")
|
||||
@@ -10,7 +10,7 @@ concurrency:
|
||||
jobs:
|
||||
checkbranch:
|
||||
name: Check PR Branch status
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
branchstat: ${{ steps.brstat.outputs.stat}}
|
||||
steps:
|
||||
@@ -26,14 +26,14 @@ jobs:
|
||||
- name: Check whether branch is up-to-date
|
||||
id: brstat
|
||||
run: |
|
||||
# fetch master from the base repo (tinygrad/tinygrad on GitHub, the mirror on Gitea), not the PR head remote
|
||||
git fetch "${{ github.event.pull_request.base.repo.clone_url }}" master
|
||||
git remote add tinygrad https://github.com/tinygrad/tinygrad
|
||||
git fetch tinygrad master
|
||||
echo "${{ github.event.pull_request.head.sha }}"
|
||||
git rev-list --left-right --count FETCH_HEAD...${{ github.event.pull_request.head.sha }} | awk '{print "Behind "$1" - Ahead "$2""}'
|
||||
count=$(git rev-list --left-right --count FETCH_HEAD...${{ github.event.pull_request.head.sha }} | awk '{print $1}')
|
||||
git rev-list --left-right --count tinygrad/master...${{ github.event.pull_request.head.sha }} | awk '{print "Behind "$1" - Ahead "$2""}'
|
||||
count=$(git rev-list --left-right --count tinygrad/master...${{ github.event.pull_request.head.sha }} | awk '{print $1}')
|
||||
if [ $count -gt 0 ]
|
||||
then
|
||||
echo "Current branch is behind ${{ github.event.pull_request.base.repo.full_name }} master branch!"
|
||||
echo "Current branch is behind tinygrad master branch!"
|
||||
echo "stat=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "stat=false" >> "$GITHUB_OUTPUT"
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
needs: checkbranch
|
||||
if: needs.checkbranch.outputs.branchstat == 'false'
|
||||
steps:
|
||||
@@ -75,30 +75,28 @@ jobs:
|
||||
python sz.py "$BASE" "$PR" > loc_content.txt
|
||||
- name: Comment Code Line Diff
|
||||
continue-on-error: false
|
||||
env:
|
||||
uses: marocchino/sticky-pull-request-comment@v3
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
MARKER: "### Changes"
|
||||
BODY_FILE: loc_content.txt
|
||||
# note: run the script from the base checkout, never from the PR checkout
|
||||
run: python3 "$GITHUB_WORKSPACE/base/.github/workflows/sticky_comment.py"
|
||||
ignore_empty: true
|
||||
skip_unchanged: true
|
||||
recreate: true
|
||||
path: loc_content.txt
|
||||
|
||||
rebase:
|
||||
name: Core Library Line Difference
|
||||
permissions:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
needs: checkbranch
|
||||
if: needs.checkbranch.outputs.branchstat == 'true'
|
||||
steps:
|
||||
# pull_request_target: a plain checkout gets the base repo, so no PR code is executed
|
||||
- uses: actions/checkout@v6
|
||||
- name: Comment Rebase
|
||||
continue-on-error: false
|
||||
env:
|
||||
uses: marocchino/sticky-pull-request-comment@v3
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
MARKER: "line count difference bot is disabled"
|
||||
MESSAGE: |
|
||||
This branch currently is behind ${{ github.event.pull_request.base.repo.full_name }} master. The line count difference bot is disabled.
|
||||
run: python3 .github/workflows/sticky_comment.py
|
||||
skip_unchanged: true
|
||||
recreate: true
|
||||
message: |
|
||||
This branch currently is behind tinygrad/master. The line count difference bot is disabled.
|
||||
|
||||
+81
-274
@@ -21,7 +21,7 @@ concurrency:
|
||||
jobs:
|
||||
docs:
|
||||
name: Docs
|
||||
runs-on: &linux ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
CHECK_OOB: 0
|
||||
@@ -31,8 +31,7 @@ jobs:
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
deps: docs
|
||||
pydeps: "capstone torch"
|
||||
deps: "docs testing_minimal"
|
||||
- name: Build wheel and show size
|
||||
run: |
|
||||
uv build --wheel
|
||||
@@ -61,7 +60,7 @@ jobs:
|
||||
|
||||
torchbackend:
|
||||
name: Torch Backend Tests
|
||||
runs-on: *linux
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
@@ -73,10 +72,7 @@ jobs:
|
||||
deps: testing_unit
|
||||
pydeps: "pillow torchvision expecttest"
|
||||
llvm: 'true'
|
||||
- name: Install ninja
|
||||
run: |
|
||||
sudo apt update || true
|
||||
sudo apt install -y --no-install-recommends ninja-build
|
||||
ninja: 'true'
|
||||
- name: Test ResNet-18
|
||||
run: DEBUG=2 python3 extra/torch_backend/example.py
|
||||
- name: Test one op in torch tests
|
||||
@@ -86,9 +82,26 @@ jobs:
|
||||
- name: Custom tests
|
||||
run: DEV=CPU:LLVM GPUS=4 TINY_BACKEND=1 python3 -m pytest -nauto extra/torch_backend/test.py extra/torch_backend/test_inplace.py extra/torch_backend/test_multigpu.py extra/torch_backend/test_kernel_fusion.py --durations=20
|
||||
|
||||
torchbackendtrain:
|
||||
name: Torch Backend Training
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: torch-backend-pillow-torchvision-et-pt
|
||||
deps: testing_unit
|
||||
llvm: 'true'
|
||||
ninja: 'true'
|
||||
- name: Test beautiful_mnist in torch with TINY_BACKEND
|
||||
run: STEPS=20 DEV=CPU TARGET_EVAL_ACC_PCT=90.0 MAX_BUFFER_SIZE=0 TINY_BACKEND=1 python3 examples/other_mnist/beautiful_mnist_torch.py
|
||||
|
||||
bepython:
|
||||
name: Python Backend
|
||||
runs-on: *linux
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
@@ -126,7 +139,7 @@ jobs:
|
||||
|
||||
linter:
|
||||
name: Linters
|
||||
runs-on: *linux
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
@@ -157,7 +170,7 @@ jobs:
|
||||
|
||||
nulltest:
|
||||
name: Null Tests
|
||||
runs-on: *linux
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
@@ -191,7 +204,7 @@ jobs:
|
||||
|
||||
unittest:
|
||||
name: Unit Tests
|
||||
runs-on: *linux
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
@@ -219,8 +232,8 @@ jobs:
|
||||
run: python3 test/external/external_benchmark_schedule.py
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
- name: Repo line count < 25000 lines
|
||||
run: MAX_LINE_COUNT=25000 python sz.py
|
||||
- name: Repo line count <= 26000 lines
|
||||
run: MAX_LINE_COUNT=26500 python sz.py
|
||||
|
||||
spec:
|
||||
strategy:
|
||||
@@ -228,7 +241,7 @@ jobs:
|
||||
matrix:
|
||||
group: [1, 2]
|
||||
name: SPEC=2 (${{ matrix.group }})
|
||||
runs-on: *linux
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
@@ -244,7 +257,7 @@ jobs:
|
||||
|
||||
fuzzing:
|
||||
name: Fuzzing
|
||||
runs-on: *linux
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
@@ -260,7 +273,7 @@ jobs:
|
||||
|
||||
testopenclimage:
|
||||
name: CL IMAGE Tests
|
||||
runs-on: *linux
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
@@ -280,7 +293,7 @@ jobs:
|
||||
|
||||
testopenpilot:
|
||||
name: openpilot Compile Tests
|
||||
runs-on: *linux
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
@@ -294,7 +307,7 @@ jobs:
|
||||
llvm: 'true'
|
||||
- name: Test openpilot model kernel count and gate usage
|
||||
run: |
|
||||
ALLOWED_KERNEL_COUNT=123 ALLOWED_READ_IMAGE=1361 ALLOWED_GATED_READ_IMAGE=54 FLOAT16=1 DEV="CL::IMAGE_PITCH_ALIGNMENT=64" IMAGE=1 python examples/openpilot/compile3.py https://gitlab.com/commaai/openpilot-lfs.git/gitlab-lfs/objects/cf6376aa9a090f0da26c280ef69eabf9bbdd51d1faac9ed392919c3db69be916
|
||||
ALLOWED_KERNEL_COUNT=123 ALLOWED_READ_IMAGE=1361 ALLOWED_GATED_READ_IMAGE=38 FLOAT16=1 DEV="CL::IMAGE_PITCH_ALIGNMENT=64" IMAGE=1 python examples/openpilot/compile3.py https://gitlab.com/commaai/openpilot-lfs.git/gitlab-lfs/objects/cf6376aa9a090f0da26c280ef69eabf9bbdd51d1faac9ed392919c3db69be916
|
||||
# IMAGE_PITCH_ALIGNMENT=64 matches adreno 630
|
||||
- name: Test openpilot CL compile fp32 (test correctness)
|
||||
run: |
|
||||
@@ -309,7 +322,7 @@ jobs:
|
||||
|
||||
testonnxcpu:
|
||||
name: ONNX (CPU) Tests
|
||||
runs-on: *linux
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
@@ -328,7 +341,7 @@ jobs:
|
||||
|
||||
testoptim:
|
||||
name: Optimization Tests
|
||||
runs-on: *linux
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
@@ -360,7 +373,7 @@ jobs:
|
||||
|
||||
testllm:
|
||||
name: Test LLM
|
||||
runs-on: *linux
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
CHECK_OOB: 0
|
||||
@@ -377,17 +390,17 @@ jobs:
|
||||
run: |
|
||||
parallel --link --tagstring '[{1}]' '{2}' \
|
||||
::: llama 'llama q4' qwen3.5 qwen \
|
||||
::: $'echo "What\'s a male chicken called? Answer with only one word." | python3 -m tinygrad.llm --model llama3.2:1b | tee /dev/stderr | grep -i rooster' \
|
||||
$'echo "What\'s a male chicken called? Answer with only one word." | python3 -m tinygrad.llm --model llama3.2:1b-q4 | tee /dev/stderr | grep -i rooster' \
|
||||
$'echo "What\'s a male chicken called? Answer with only one word." | python3 -m tinygrad.llm --model qwen3.5:0.8b | tee /dev/stderr | grep -i rooster' \
|
||||
$'echo "What\'s a female chicken called? Answer with only one word." | python3 -m tinygrad.llm --model qwen3:0.6b | tee /dev/stderr | grep -i hen'
|
||||
::: $'echo "What\'s a male chicken called? Answer with only one word." | python3 -m tinygrad.llm --no_chat_template --model llama3.2:1b | tee /dev/stderr | grep -i rooster' \
|
||||
$'echo "What\'s a male chicken called? Answer with only one word." | python3 -m tinygrad.llm --no_chat_template --model llama3.2:1b-q4 | tee /dev/stderr | grep -i rooster' \
|
||||
$'echo "What\'s a male chicken called? Answer with only one word." | python3 -m tinygrad.llm --no_chat_template --model qwen3.5:0.8b | tee /dev/stderr | grep -i rooster' \
|
||||
$'echo "What\'s a female chicken called? Answer with only one word." | python3 -m tinygrad.llm --no_chat_template --model qwen3:0.6b | tee /dev/stderr | grep -i hen'
|
||||
# NOTE: qwen is dumb and only knows about female chickens
|
||||
|
||||
# ****** Models Tests ******
|
||||
|
||||
testmodels:
|
||||
name: Models
|
||||
runs-on: *linux
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
@@ -407,7 +420,7 @@ jobs:
|
||||
|
||||
testdsp:
|
||||
name: Linux (DSP)
|
||||
runs-on: *linux
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
@@ -435,7 +448,7 @@ jobs:
|
||||
- 'WEBGPU'
|
||||
|
||||
name: Linux (DEV=${{ matrix.dev }})
|
||||
runs-on: *linux
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
@@ -461,7 +474,7 @@ jobs:
|
||||
|
||||
testamdasm:
|
||||
name: AMD ASM IDE
|
||||
runs-on: *linux
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
DEV: MOCKKFD+AMD
|
||||
@@ -491,7 +504,7 @@ jobs:
|
||||
- name: Run AMD renderer tests (AMD:LLVM)
|
||||
run: DEV=MOCKKFD+AMD:LLVM python -m pytest -n=auto test/amd/ --durations 20
|
||||
- name: Run SQTT profiling tests
|
||||
run: PROFILE=1 SQTT=1 python3 -m pytest -n=auto test/amd/test_sqtt_profiler.py
|
||||
run: VIZ=-2 python3 -m pytest -n=auto test/amd/test_sqtt_profiler.py
|
||||
- name: Run AMD emulated tests on NULL backend
|
||||
env:
|
||||
AMD: 0
|
||||
@@ -505,35 +518,15 @@ jobs:
|
||||
- name: Run LLVM test
|
||||
run: DEV=MOCKKFD+AMD:LLVM python test/device/test_amd_llvm.py
|
||||
|
||||
hcq2:
|
||||
name: hcq2
|
||||
runs-on: *linux
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: hcq2
|
||||
deps: testing_unit
|
||||
amd: 'true'
|
||||
- name: Run HCQ2 tests
|
||||
run: HCQ_RUNTIME_DEV=PYTHON HCQ2=1 DEV=MOCKKFD+AMD FORWARD_ONLY=1 PYTHONPATH=. python test/test_tiny.py
|
||||
- name: Run HCQ2 multi-device tests
|
||||
run: |
|
||||
HCQ_RUNTIME_DEV=PYTHON HCQ2=1 DEV=MOCKKFD+AMD FORWARD_ONLY=1 PYTHONPATH=. python test/unit/test_multitensor.py \
|
||||
TestMultiTensor.test_simple_add TestMultiTensor.test_shard_reduce \
|
||||
TestMultiTensor.test_backward_sum TestMultiTensor.test_matmul_shard_0_0
|
||||
- name: Run HCQ2 JIT tests
|
||||
run: HCQ_RUNTIME_DEV=PYTHON HCQ2=1 DEV=MOCKKFD+AMD FORWARD_ONLY=1 PYTHONPATH=. python test/unit/test_jit.py
|
||||
|
||||
testmockam:
|
||||
name: Linux (am)
|
||||
runs-on: *linux
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
DEV: MOCKPCI+AMD
|
||||
HCQ2: 1
|
||||
HCQ_RUNTIME_DEV: PYTHON
|
||||
PYTHONPATH: .
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
@@ -543,20 +536,14 @@ jobs:
|
||||
key: mockam
|
||||
deps: testing_unit
|
||||
amd: 'true'
|
||||
- name: Run test_tiny on MOCKAM
|
||||
run: python test/test_tiny.py
|
||||
- name: Run tests on MOCKAM
|
||||
run: python -m pytest test/test_tiny.py test/unit/test_jit.py
|
||||
- name: Run test_tiny on MOCKUSB
|
||||
run: GMMU=0 DEV=MOCKUSB+AMD python test/test_tiny.py
|
||||
- name: Run test_hcq on MOCKPCI
|
||||
run: python -m pytest test/device/test_hcq.py
|
||||
run: HCQ2=0 GMMU=0 DEV=MOCKUSB+AMD python test/test_tiny.py
|
||||
- name: Run test_hcq2 on MOCKPCI
|
||||
run: python -m pytest test/device/test_hcq2.py
|
||||
- name: Run disk copy tests on MOCKPCI
|
||||
run: python -m pytest test/unit/test_disk_tensor.py -k test_copy_from_disk
|
||||
- name: Run test_tiny on MOCKPCI Remote
|
||||
run: |
|
||||
python extra/remote/serve.py 6667 &
|
||||
sleep 2
|
||||
REMOTE=127.0.0.1:6667 python test/test_tiny.py
|
||||
REMOTE=127.0.0.1:6667 python -m pytest test/unit/test_disk_tensor.py -k test_copy_from_disk; kill %1
|
||||
|
||||
testamd:
|
||||
strategy:
|
||||
@@ -566,11 +553,14 @@ jobs:
|
||||
arch: [gfx1100, gfx1201, gfx950]
|
||||
|
||||
name: Linux (${{ matrix.backend }} ${{ matrix.arch }})
|
||||
runs-on: *linux
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
DEV: MOCKKFD+AMD:${{ matrix.backend == 'amdllvm' && 'LLVM' || '' }}:${{ matrix.arch }}
|
||||
SKIP_SLOW_TEST: 1
|
||||
HCQ2: 1
|
||||
HCQ_RUNTIME_DEV: PYTHON
|
||||
PYTHONPATH: .
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
@@ -585,8 +575,13 @@ jobs:
|
||||
run: |
|
||||
python3 -c "from tinygrad import Device; assert Device.DEFAULT in ['AMD'], Device.DEFAULT"
|
||||
DEBUG=5 FORWARD_ONLY=1 python3 test/test_tiny.py TestTiny.test_plus
|
||||
- name: Run MXFP4 Llama training on NULL backend
|
||||
if: ${{ matrix.backend == 'amd' && matrix.arch == 'gfx950' }}
|
||||
run: HCQ2=0 PYTHONPATH=. DEV=NULL:HIP:gfx950 MXFP4=1 LLAMA_LAYERS=2 BENCHMARK=3 NULL_ALLOW_COPYOUT=1 NO_HIPCC=1 ROCM_PATH=/opt/rocm JITBEAM=0 examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama31_8b/implementations/tinybox_8xMI350X/profile.sh
|
||||
- name: Run pytest (amd)
|
||||
run: python -m pytest -n=auto test/backend/test_ops.py test/backend/test_dtype.py test/backend/test_dtype_alu.py test/backend/test_linearizer.py test/backend/test_randomness.py test/backend/test_jit.py test/backend/test_graph.py test/backend/test_multitensor.py test/device/test_hcq.py test/external/external_test_am.py test/backend/test_asm_gemm.py::TestAsmGEMM --durations=20
|
||||
run: python -m pytest -n=auto test/backend/test_ops.py test/backend/test_dtype.py test/backend/test_dtype_alu.py test/backend/test_linearizer.py test/backend/test_randomness.py test/backend/test_jit.py test/backend/test_graph.py test/backend/test_multitensor.py test/device/test_hcq2.py test/external/external_test_am.py test/backend/test_asm_gemm.py::TestAsmGEMM --durations=20
|
||||
- name: Run opt tests
|
||||
run: python -m pytest -n=auto test/opt --durations=20
|
||||
- name: Run disk copy tests
|
||||
run: python -m pytest test/unit/test_disk_tensor.py -k test_copy_from_disk
|
||||
- name: Run TRANSCENDENTAL math
|
||||
@@ -601,7 +596,7 @@ jobs:
|
||||
backend: [ptx, nv]
|
||||
|
||||
name: Linux (${{ matrix.backend }})
|
||||
runs-on: *linux
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
FORWARD_ONLY: 1
|
||||
@@ -616,7 +611,7 @@ jobs:
|
||||
cuda: 'true'
|
||||
ocelot: 'true'
|
||||
- name: Set env
|
||||
run: printf "${{ matrix.backend == 'ptx' && 'DEV=MOCK+CUDA:PTX' || matrix.backend == 'nv' && 'DEV=MOCK+NV\nSKIP_SLOW_TEST=1' }}" >> $GITHUB_ENV
|
||||
run: printf "${{ matrix.backend == 'ptx' && 'DEV=MOCK+CUDA:PTX' || matrix.backend == 'nv' && 'DEV=MOCK+NV\nSKIP_SLOW_TEST=1\nHCQ_RUNTIME_DEV=PYTHON' }}" >> $GITHUB_ENV
|
||||
- name: Check Device.DEFAULT and print some source
|
||||
run: |
|
||||
python3 -c "from tinygrad import Device; assert Device.DEFAULT in ['CUDA','NV'], Device.DEFAULT"
|
||||
@@ -629,175 +624,23 @@ jobs:
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
# ****** OSX Tests ******
|
||||
|
||||
unittestmacos:
|
||||
name: MacOS (unit)
|
||||
runs-on: macos-26
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: unittest-macos
|
||||
deps: testing_unit
|
||||
- name: Run unit tests
|
||||
run: DEV=METAL python -m pytest -n=auto test/unit/ --durations=20
|
||||
- name: Test tensor core ops (fake)
|
||||
run: DEV=METAL DEBUG=3 TC=2 python test/backend/test_ops.py TestOps.test_gemm
|
||||
- name: Test tensor core ops (real)
|
||||
run: DEV=METAL DEBUG=3 python test/backend/test_ops.py TestOps.test_big_gemm
|
||||
- name: Test Beam Search
|
||||
run: DEV=METAL IGNORE_BEAM_CACHE=1 python3 -m pytest extra/optimization/test_beam_search.py
|
||||
- name: Test Device Specific
|
||||
run: DEV=METAL python3 -m pytest test/device/test_metal.py
|
||||
#- name: Fuzz Test linearizer
|
||||
# run: DEV=METAL DEPTH=4 FUZZ_N=50 FUZZ_MAX_SIZE=1000000 python test/external/fuzz_linearizer.py
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
unittestmacosmock:
|
||||
name: MacOS (unit, mock)
|
||||
runs-on: macos-26
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: unittest-macos-mock
|
||||
deps: testing_unit
|
||||
amd: 'true'
|
||||
ocelot: 'true'
|
||||
- name: Run NULL backend tests
|
||||
run: SPEC=2 DEV=NULL python -m pytest -n=auto test/null/ --durations=20
|
||||
- name: Run pytest (amd)
|
||||
env:
|
||||
DEV: MOCKKFD+AMD
|
||||
FORWARD_ONLY: 1
|
||||
run: |
|
||||
python3 -m pytest -n=auto test/device/test_hcq.py test/test_tiny.py --durations=20
|
||||
- name: Run pytest (ptx)
|
||||
env:
|
||||
DEV: "MOCK+NV:PTX"
|
||||
FORWARD_ONLY: 1
|
||||
# TODO: failing due to library loading error
|
||||
CAPTURE_PROCESS_REPLAY: 0
|
||||
run: |
|
||||
python3 -m pytest -n=auto test/device/test_hcq.py test/test_tiny.py --durations=20
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
testmetal:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
group: [1, 2]
|
||||
name: MacOS (DEV=METAL) (${{ matrix.group }})
|
||||
runs-on: macos-26
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
DEV: METAL
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: macos-metal
|
||||
deps: testing_unit
|
||||
- name: Check Device.DEFAULT and print some source
|
||||
run: |
|
||||
python -c "from tinygrad import Device; assert Device.DEFAULT == 'METAL'"
|
||||
DEBUG=4 python test/test_tiny.py TestTiny.test_plus
|
||||
- name: Run backend tests
|
||||
run: python -m pytest -n=auto test/backend --durations=20 --splits 2 --group ${{ matrix.group }}
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
testmacos:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
dev:
|
||||
- 'CPU:CLANG'
|
||||
- 'CPU:LLVM'
|
||||
- 'CPU:LVP'
|
||||
- 'WEBGPU'
|
||||
|
||||
name: MacOS (DEV=${{ matrix.dev }})
|
||||
runs-on: macos-26
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: macos-${{ matrix.dev }}
|
||||
deps: "testing_unit${{ contains(matrix.dev, 'LVP') && ' mesa' || '' }}"
|
||||
llvm: ${{ contains(matrix.dev, 'LLVM') || contains(matrix.dev, 'LVP') }}
|
||||
webgpu: ${{ matrix.dev == 'WEBGPU' }}
|
||||
- name: Set env
|
||||
run: printf "DEV=${{ matrix.dev }}${{ matrix.dev == 'CPU:CLANG' && '\nCPU_COUNT=2' || '' }}" >> $GITHUB_ENV
|
||||
- name: Check Device.DEFAULT and print some source
|
||||
run: |
|
||||
python -c "from tinygrad import Device; from tinygrad.helpers import Target; assert Device.DEFAULT == Target.parse('${{ matrix.dev }}').device"
|
||||
DEBUG=4 python test/test_tiny.py TestTiny.test_plus
|
||||
- name: Run test_tiny
|
||||
run: python -m pytest -n=auto test/test_tiny.py --durations=20
|
||||
- name: Run process replay tests
|
||||
uses: ./.github/actions/process-replay
|
||||
|
||||
# ****** Windows Tests ******
|
||||
|
||||
testwindows:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
dev:
|
||||
- 'CPU:CLANG'
|
||||
- 'CPU:LLVM'
|
||||
- 'CPU:X86'
|
||||
- 'WEBGPU'
|
||||
|
||||
name: Windows (DEV=${{ matrix.dev }})
|
||||
runs-on: windows-2025
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: windows-${{ matrix.dev }}-minimal
|
||||
deps: testing_unit
|
||||
pydeps: ${{ matrix.dev == 'WEBGPU' && 'dawn-python' || '' }}
|
||||
- name: Set env
|
||||
shell: bash
|
||||
run: printf "DEV=${{ matrix.dev }}${{ matrix.dev == 'CPU:CLANG' && '\nCPU_COUNT=2' || '' }}" >> $GITHUB_ENV
|
||||
- name: Check Device.DEFAULT and print some source
|
||||
shell: bash
|
||||
run: |
|
||||
python -c "from tinygrad import Device; from tinygrad.helpers import Target; assert Device.DEFAULT == Target.parse('${{ matrix.dev }}').device"
|
||||
DEBUG=4 python test/test_tiny.py TestTiny.test_plus
|
||||
- name: Run test_tiny
|
||||
shell: bash
|
||||
run: python -m pytest -n=auto test/test_tiny.py --durations=20
|
||||
|
||||
# ****** Compile-only Tests ******
|
||||
|
||||
compiletests:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
backend: [ir3, nak]
|
||||
name: Compile-only (${{ matrix.backend }})
|
||||
runs-on: *linux
|
||||
dev:
|
||||
- 'NULL:IR3:a630'
|
||||
- 'NULL:QCOMCL:a630'
|
||||
- 'NULL:NAK:sm_120'
|
||||
name: Compile-only (DEV=${{ matrix.dev }})
|
||||
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
NULL_ALLOW_COPYOUT: 1
|
||||
DEV: ${{ matrix.dev }}${{ contains(matrix.dev, 'a630') && ',IMAGE_PITCH_ALIGNMENT=64' || '' }}
|
||||
IMAGE: ${{ contains(matrix.dev, 'a630') && '1' || '0' }}
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
@@ -806,51 +649,15 @@ jobs:
|
||||
with:
|
||||
key: compile-${{ matrix.backend }}
|
||||
deps: "testing_unit mesa"
|
||||
- name: Set env
|
||||
qemu: ${{ contains(matrix.dev, 'QCOMCL') }}
|
||||
- name: Test IMAGE
|
||||
shell: bash
|
||||
run: printf "NULL_ALLOW_COPYOUT=1\n${{ matrix.backend == 'ir3' && 'DEV=NULL:IR3:a630' || matrix.backend == 'nak' && 'DEV=NULL:NAK:sm_120' }}" >> $GITHUB_ENV
|
||||
if: contains(matrix.dev, 'a630')
|
||||
run: DEBUG=7 python3 test/backend/test_ops.py TestOps.test_gemm | grep isam
|
||||
- name: Run test_ops
|
||||
shell: bash
|
||||
run: |
|
||||
python -c "from tinygrad import Device; assert Device.DEFAULT == 'NULL'"
|
||||
DEBUG=4 python3 test/backend/test_ops.py TestOps.test_add
|
||||
python -m pytest -n=auto test/backend/test_ops.py --durations=20
|
||||
- name: Run test_ops (IMAGE)
|
||||
if: matrix.backend == 'ir3'
|
||||
shell: bash
|
||||
env:
|
||||
IMAGE: 1
|
||||
DEV: "NULL:IR3:a630,IMAGE_PITCH_ALIGNMENT=64"
|
||||
run: |
|
||||
DEBUG=4 python3 test/backend/test_ops.py TestOps.test_gemm | grep image_load
|
||||
python -m pytest -n=auto test/backend/test_ops.py --durations=20
|
||||
qcomclcompiletests:
|
||||
name: Compile-only (QCOM CL)
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: compile-qcomcl
|
||||
deps: testing_unit
|
||||
tinydreno: 'true'
|
||||
- name: Set env
|
||||
shell: bash
|
||||
run: printf "DEV=NULL:QCOMCL:a630\nNULL_ALLOW_COPYOUT=1" >> $GITHUB_ENV
|
||||
- name: Run test_ops
|
||||
shell: bash
|
||||
run: |
|
||||
python -c "from tinygrad import Device; assert Device.DEFAULT == 'NULL'"
|
||||
DEBUG=4 python3 test/backend/test_ops.py TestOps.test_add
|
||||
python -m pytest -n=auto test/backend/test_ops.py --durations=20
|
||||
- name: Run test_ops (IMAGE)
|
||||
shell: bash
|
||||
env:
|
||||
IMAGE: 1
|
||||
DEV: "NULL:QCOMCL:a630,IMAGE_PITCH_ALIGNMENT=64"
|
||||
run: |
|
||||
DEBUG=4 python test/backend/test_ops.py TestOps.test_gemm | grep read_imagef
|
||||
python -m pytest -n=auto test/backend/test_ops.py --durations=20
|
||||
# QCOMCL compiles in qemu, too slow for parallel workers
|
||||
${{ contains(matrix.dev, 'QCOMCL') && 'PARALLEL=0' || '' }} python -m pytest -n=auto test/backend/test_ops.py --durations=20
|
||||
|
||||
@@ -69,3 +69,4 @@ mutants
|
||||
dagre/
|
||||
graphlib/
|
||||
uv.lock
|
||||
pi_session_window0.jsonl
|
||||
|
||||
@@ -28,7 +28,7 @@ repos:
|
||||
pass_filenames: false
|
||||
- id: tests
|
||||
name: comprehensive test suite
|
||||
entry: env OMP_NUM_THREADS=1 SKIP_SLOW_TEST=1 PYTHONPATH="." python3 -m pytest -n=6 test/backend/test_ops.py test/backend/test_schedule.py test/unit/test_assign.py test/backend/test_tensor.py test/backend/test_jit.py test/unit/test_schedule_cache.py test/null/test_pattern_matcher.py test/null/test_uop_symbolic.py test/unit/test_helpers.py
|
||||
entry: env OMP_NUM_THREADS=1 SKIP_SLOW_TEST=1 PYTHONPATH="." python3 -m pytest -n=6 test/backend/test_ops.py test/backend/test_schedule.py test/backend/test_assign.py test/backend/test_tensor.py test/backend/test_jit.py test/unit/test_schedule_cache.py test/null/test_pattern_matcher.py test/null/test_uop_symbolic.py test/unit/test_helpers.py
|
||||
language: system
|
||||
always_run: true
|
||||
pass_filenames: false
|
||||
|
||||
@@ -4,3 +4,5 @@
|
||||
- Run `python -m mypy tinygrad/` to typecheck
|
||||
- Run `python -m ruff check .` to lint
|
||||
- Read `./tinygrad/viz/README.md` for profiling and debugging rewrite rules
|
||||
- Do not do amend commits. Always do a new commit if a force push to origin would be required.
|
||||
- tinygrad has user space PCI drivers for AMD and NVIDIA GPUs. Do not insert the unneeded kernel modules.
|
||||
|
||||
@@ -140,7 +140,7 @@ Documentation along with a quick start guide can be found on the [docs website](
|
||||
```python
|
||||
from tinygrad import Tensor
|
||||
|
||||
x = Tensor.eye(3)
|
||||
x = Tensor.eye(3).clone() # clone to make it a buffer
|
||||
y = Tensor([[2.0,0,-2.0]])
|
||||
z = y.matmul(x).sum()
|
||||
z.backward()
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import os, pytest, signal, threading
|
||||
|
||||
@pytest.hookimpl(wrapper=True)
|
||||
def pytest_runtest_call(item):
|
||||
t = threading.Timer(int(os.getenv("TEST_TIMEOUT", 300)), os.kill, args=(os.getpid(), signal.SIGABRT))
|
||||
t = threading.Timer(int(os.getenv("TEST_TIMEOUT", 90)), os.kill, args=(os.getpid(), signal.SIGABRT))
|
||||
t.start()
|
||||
try: yield
|
||||
finally:
|
||||
|
||||
@@ -122,7 +122,7 @@ def example_5_custom_assembly(a:Tensor, correct):
|
||||
offset_dwords = (self.labels[inst._target] - inst._pos - inst.size()) // 4
|
||||
if not -32768 <= offset_dwords <= 32767: raise ValueError(f"branch to '{inst._target}' offset {offset_dwords} exceeds simm16 range")
|
||||
inst.simm16 = offset_dwords
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple([UOp(Ops.INS, arg=x) for x in self.instructions]))))
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple([UOp(Ops.INS, arg=(x, dtypes.void)) for x in self.instructions]))))
|
||||
|
||||
CU_COUNT = 32
|
||||
LANES = 64
|
||||
|
||||
@@ -52,7 +52,7 @@ In `kernel.py` we have a set of `OptOps`, these control the parameters of the sp
|
||||
|
||||
The main bottleneck in most kernels is accessing memory. In a freshman algorithms class, you'll learn about cache aware matrix multiplication, and this is all forms of that. While the same math is run, the order in which you run it can have large impacts on the speed depending on if the data you are loading. OptOps will change this order.
|
||||
|
||||
Memory, even cache, is often much slower than accessing the register file. The amount of times data is used in math is called the "arithmetic intensity". For operations like BS=1 GEMV, the arithmetic intensity is 1, but for GEMMs and convs it can be much higher. OptOps like UPCAST and UNROLL can increase this, but be careful of making them too large, as if there's too much register pressure on the GPU the warp scheduler may not be able to fit many warps, or even worse, it could be spilling to local memory.
|
||||
Memory, even cache, is often much slower than accessing the register file. The amount of times data is used in math is called the "arithmetic intensity". For operations like BS=1 GEMV, the arithmetic intensity is 1, but for GEMMs and convs it can be much higher. Splitting an axis into UPCAST can increase this, but be careful of making them too large, as if there's too much register pressure on the GPU the warp scheduler may not be able to fit many warps, or even worse, it could be spilling to local memory.
|
||||
|
||||
4090s have 1 TB/s of ram bandwidth and ~160 TFLOPS of compute, so you need to use each loaded value ~100 times. The L1 cache has around 40 TB/s of bandwidth, so in order to get full compute utilization you need to use each value ~4 times.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from multiprocessing import Queue, Process, shared_memory, connection, Lock
|
||||
|
||||
import numpy as np
|
||||
from tinygrad import dtypes, Tensor
|
||||
from tinygrad.helpers import getenv, prod, Context, round_up, tqdm, OSX, NUM_CPU_THREADS
|
||||
from tinygrad.helpers import getenv, prod, Context, round_up, tqdm, OSX, CPU_COUNT
|
||||
from tinygrad.nn.state import TensorIO
|
||||
|
||||
### ResNet
|
||||
@@ -131,7 +131,7 @@ def batch_load_resnet(batch_size=64, val=False, shuffle=True, seed=None, pad_fir
|
||||
else: X = Tensor.empty(*sz, dtype=dtypes.uint8, device=f"disk:/dev/shm/{shm_name}")
|
||||
Y = [None] * (batch_size*BATCH_COUNT)
|
||||
|
||||
for _ in range(NUM_CPU_THREADS.value):
|
||||
for _ in range(CPU_COUNT):
|
||||
p = Process(target=loader_process, args=(q_in, q_out, X, seed))
|
||||
p.daemon = True
|
||||
p.start()
|
||||
@@ -212,7 +212,7 @@ def batch_load_train_bert(BS:int, seed:int|None=None):
|
||||
rng.shuffle(fs)
|
||||
train_files.append(fs.pop(0))
|
||||
|
||||
cycle_length = min(NUM_CPU_THREADS.value, len(train_files))
|
||||
cycle_length = min(CPU_COUNT, len(train_files))
|
||||
assert cycle_length > 0, "cycle_length must be greater than 0"
|
||||
|
||||
dataset = InterleavedDataset(train_files, cycle_length)
|
||||
@@ -301,7 +301,7 @@ def batch_load_unet3d(preprocessed_dataset_dir:Path, batch_size:int=6, val:bool=
|
||||
X = Tensor.empty(*sz, dtype=dtypes.float32, device=f"disk:/dev/shm/{shm_name_x}")
|
||||
Y = Tensor.empty(*sz, dtype=dtypes.uint8, device=f"disk:/dev/shm/{shm_name_y}")
|
||||
|
||||
for _ in range(NUM_CPU_THREADS.value):
|
||||
for _ in range(CPU_COUNT):
|
||||
proc = Process(target=load_unet3d_data, args=(preprocessed_dataset_dir, seed, queue_in, queue_out, X, Y))
|
||||
proc.daemon = True
|
||||
proc.start()
|
||||
@@ -437,7 +437,7 @@ def batch_load_retinanet(dataset, val:bool, base_dir:Path, batch_size:int=32, sh
|
||||
dataset_iter = iter(image_ids)
|
||||
|
||||
try:
|
||||
for _ in range(NUM_CPU_THREADS.value):
|
||||
for _ in range(CPU_COUNT):
|
||||
proc = Process(
|
||||
target=load_retinanet_data,
|
||||
args=(base_dir, val, queue_in, queue_out, imgs, boxes, labels),
|
||||
|
||||
@@ -1282,7 +1282,7 @@ def train_bert():
|
||||
previous_step = i
|
||||
|
||||
def train_llama3():
|
||||
from examples.mlperf.models.flat_llama import FlatTransformer, apply_grad, FP8_DTYPE, MXFP8
|
||||
from examples.mlperf.models.flat_llama import FlatTransformer, apply_grad, FP8_DTYPE, MXFP8, MXFP4
|
||||
from examples.llama3 import MODEL_PARAMS
|
||||
from examples.mlperf.lr_schedulers import CosineAnnealingLRWithWarmup
|
||||
from examples.mlperf.optim import GradAccClipAdamW, clip_grads
|
||||
@@ -1434,9 +1434,9 @@ def train_llama3():
|
||||
load_state_dict(scheduler, safe_load(fn), realize=False)
|
||||
|
||||
fp8_amax = [t for ts in model._fp8_amax.values() for t in ts]
|
||||
fp8_next_amax = [t for ts in model._fp8_next_amax.values() for t in ts] if hasattr(model, "_fp8_next_amax") else []
|
||||
fp8_grad_amax = [t for ts in model._fp8_grad_amax.values() for t in ts] if hasattr(model, "_fp8_grad_amax") else []
|
||||
fp8_next_grad_amax = [t for ts in model._fp8_next_grad_amax.values() for t in ts] if hasattr(model, "_fp8_next_grad_amax") else []
|
||||
fp8_next_amax = [t for ts in model._fp8_next_amax.values() for t in ts]
|
||||
fp8_grad_amax = [t for ts in model._fp8_grad_amax.values() for t in ts]
|
||||
fp8_next_grad_amax = [t for ts in model._fp8_next_grad_amax.values() for t in ts]
|
||||
fp8_inv_scales = list(model._fp8_inv_scale.values()) + list(model._fp8_next_inv_scale.values())
|
||||
|
||||
from tinygrad.nn.state import get_state_dict
|
||||
@@ -1458,12 +1458,12 @@ def train_llama3():
|
||||
|
||||
# realize everything here
|
||||
if optim.master_params: Tensor.realize(*optim.master_params)
|
||||
Tensor.realize(*optim.params, *fp8_inv_scales, *fp8_amax, *fp8_next_amax, *fp8_grad_amax, *fp8_next_grad_amax)
|
||||
loss_acc = Tensor.zeros(1, dtype=dtypes.float32, device=device)
|
||||
Tensor.realize(loss_acc, *optim.params, *fp8_inv_scales, *fp8_amax, *fp8_next_amax, *fp8_grad_amax, *fp8_next_grad_amax)
|
||||
|
||||
@TinyJit
|
||||
def minibatch(tokens:Tensor):
|
||||
for nxt in fp8_next_amax: nxt.assign(0)
|
||||
for nxt in fp8_next_grad_amax: nxt.assign(0)
|
||||
model.reset_amax()
|
||||
if is_dp: tokens = tokens.to(None).shard(device, 0)
|
||||
if is_mp: tokens = tokens.shard(device)
|
||||
if not is_sharding: tokens = tokens.to(None)
|
||||
@@ -1477,8 +1477,8 @@ def train_llama3():
|
||||
for g, new_g in zip(grads, loss.gradient(*optim.params)):
|
||||
apply_grad(g, new_g.uop)
|
||||
|
||||
loss_cpu = loss.flatten().float().to("CPU")
|
||||
return loss_cpu.realize(*grads, *fp8_amax, *fp8_next_amax, *fp8_grad_amax, *fp8_next_grad_amax)
|
||||
loss_acc.assign(loss_acc + loss.flatten().float())
|
||||
return loss_acc.realize(*grads, *fp8_amax, *fp8_next_amax, *fp8_grad_amax, *fp8_next_grad_amax)
|
||||
|
||||
@TinyJit
|
||||
def optim_step():
|
||||
@@ -1487,14 +1487,14 @@ def train_llama3():
|
||||
scheduler.step()
|
||||
|
||||
for g in grads: g.assign(0)
|
||||
for cur, nxt in zip(fp8_amax, fp8_next_amax): cur.assign(nxt)
|
||||
for cur, nxt in zip(fp8_grad_amax, fp8_next_grad_amax): cur.assign(nxt)
|
||||
model.update_amax()
|
||||
|
||||
lr_cpu = optim.lr.float().to("CPU")
|
||||
grad_norm_cpu = grad_norm.float().to("CPU")
|
||||
Tensor.realize(lr_cpu, grad_norm_cpu, *grads, *fp8_inv_scales, *fp8_amax, *fp8_grad_amax)
|
||||
loss_cpu = loss_acc.to("CPU")
|
||||
Tensor.realize(lr_cpu, grad_norm_cpu, loss_cpu, loss_acc.assign(0), *grads, *fp8_inv_scales, *fp8_amax, *fp8_grad_amax)
|
||||
|
||||
return lr_cpu, grad_norm_cpu
|
||||
return lr_cpu, grad_norm_cpu, loss_cpu
|
||||
|
||||
@TinyJit
|
||||
@Context(TRAINING=0)
|
||||
@@ -1549,8 +1549,8 @@ def train_llama3():
|
||||
st = time.perf_counter()
|
||||
|
||||
stopped = False
|
||||
losses, data_time, dev_time = [], 0, 0
|
||||
for _ in range(grad_acc if i >= 2 else 1):
|
||||
data_time, dev_time = 0, 0
|
||||
for _ in range(accum_steps:=grad_acc if i >= 2 else 1):
|
||||
ist = time.perf_counter()
|
||||
try: tokens = next(train_iter)
|
||||
except StopIteration:
|
||||
@@ -1558,16 +1558,15 @@ def train_llama3():
|
||||
break
|
||||
mst = time.perf_counter()
|
||||
data_time += mst - ist
|
||||
losses.append(minibatch(tokens).item())
|
||||
minibatch(tokens)
|
||||
dev_time += time.perf_counter() - mst
|
||||
if stopped: break
|
||||
|
||||
gt = time.perf_counter()
|
||||
ret = optim_step()
|
||||
lr, grad_norm = ret[0].item(), ret[1].item()
|
||||
lr, grad_norm, loss = ret[0].item(), ret[1].item(), ret[2].item() / accum_steps
|
||||
et = time.perf_counter()
|
||||
|
||||
loss = sum(losses) / len(losses)
|
||||
optim_time = et - gt
|
||||
dev_time += optim_time
|
||||
step_time = et - st
|
||||
@@ -1579,7 +1578,7 @@ def train_llama3():
|
||||
|
||||
mem_gb = GlobalCounters.mem_used / 1e9
|
||||
gflops = GlobalCounters.global_ops / 1e9 / dev_time
|
||||
mfu = ((6 * num_params * SEQLEN * GBS) / (dev_time * device_count * 4.6e15)) * 100
|
||||
mfu = ((6 * num_params * SEQLEN * GBS) / (dev_time * device_count * (9.2e15 if MXFP4 else 4.6e15))) * 100
|
||||
tqdm.write(
|
||||
f"{i:5} {step_time:.3f} s step, {gbs_time:.3f} s gbs, {optim_time:.3f} s optim, {data_time:.3f} s data, {loss:.4f} loss, " \
|
||||
f"{lr:.12f} LR, {grad_norm:.6f} grad_norm, {mem_gb:.2f} GB used, {gflops:9.2f} GFLOPS, {mfu:5.2f}% MFU")
|
||||
@@ -1668,15 +1667,14 @@ def train_llama3():
|
||||
def train_gptoss():
|
||||
from examples.mlperf.models.gpt_oss import GPTOSS, GPT_OSS_20B, apply_grad, FP8_DTYPE
|
||||
from examples.mlperf.lr_schedulers import CosineAnnealingLRWithWarmup
|
||||
from examples.mlperf.optim import GradAccClipAdamW, GradAccClipAdamWGroup, clip_grads
|
||||
from examples.mlperf.optim import GradAccClipAdamW, GradAccClipAdamWGroup, fclip_grads
|
||||
|
||||
BENCHMARK = getenv("BENCHMARK")
|
||||
|
||||
config = {}
|
||||
BASEDIR = config["BASEDIR"] = Path(getenv("BASEDIR", "/raid/datasets/c4-8b/"))
|
||||
BS = config["BS"] = getenv("BS", 16)
|
||||
grad_acc = config["GRADIENT_ACC_STEPS"] = getenv("GRADIENT_ACC_STEPS", 1)
|
||||
GBS = config["GLOBAL_BATCH_SIZE"] = BS * grad_acc
|
||||
GBS = config["GLOBAL_BATCH_SIZE"] = BS
|
||||
SEED = config["SEED"] = getenv("SEED", 5760)
|
||||
DATA_SEED = config["DATA_SEED"] = getenv("DATA_SEED", SEED)
|
||||
SEQLEN = config["SEQLEN"] = getenv("SEQLEN", 8192)
|
||||
@@ -1738,13 +1736,13 @@ def train_gptoss():
|
||||
params_wd = [p for p in params if p.ndim >= 3]
|
||||
params_no_wd = [p for p in params if p.ndim < 3]
|
||||
optim = GradAccClipAdamWGroup(
|
||||
GradAccClipAdamW(params_wd, lr=0.0, b1=opt_adamw_beta_1, b2=opt_adamw_beta_2, eps=opt_adamw_epsilon, weight_decay=opt_adamw_weight_decay, grad_acc=grad_acc, device=optim_device),
|
||||
GradAccClipAdamW(params_no_wd, lr=0.0, b1=opt_adamw_beta_1, b2=opt_adamw_beta_2, eps=opt_adamw_epsilon, weight_decay=0.0, grad_acc=grad_acc, device=optim_device),
|
||||
GradAccClipAdamW(params_wd, lr=0.0, b1=opt_adamw_beta_1, b2=opt_adamw_beta_2, eps=opt_adamw_epsilon, weight_decay=opt_adamw_weight_decay, grad_acc=1, device=optim_device),
|
||||
GradAccClipAdamW(params_no_wd, lr=0.0, b1=opt_adamw_beta_1, b2=opt_adamw_beta_2, eps=opt_adamw_epsilon, weight_decay=0.0, grad_acc=1, device=optim_device),
|
||||
)
|
||||
|
||||
for p in optim.params:
|
||||
grad_dtype = dtypes.bfloat16 if p.dtype == FP8_DTYPE else p.dtype
|
||||
p.grad = p.zeros_like(dtype=grad_dtype).contiguous()
|
||||
p.grad = p.zeros_like(dtype=dtypes.bfloat16 if p.dtype == FP8_DTYPE else p.dtype).contiguous()
|
||||
if getattr(p, "_zero2", False): p.grad = optim.optimizers[0]._zero_shard(p.grad)
|
||||
grads = [p.grad for p in optim.params]
|
||||
|
||||
from extra.gemm.cdna_asm_gemm import _mx_block_scale
|
||||
@@ -1771,31 +1769,32 @@ def train_gptoss():
|
||||
|
||||
@TinyJit
|
||||
@Context(TRAINING=1)
|
||||
def minibatch(tokens:Tensor):
|
||||
def step(tokens:Tensor):
|
||||
if is_dp: tokens = tokens.to(None).shard(device, 0)
|
||||
if not is_sharding: tokens = tokens.to(None)
|
||||
|
||||
logits:Tensor = model(tokens[:, :-1], save=True)
|
||||
loss = logits.sparse_categorical_crossentropy(tokens[:, 1:])
|
||||
if getenv("FUSED_CE", 0):
|
||||
from extra.llama_kernels.fused_ce import fused_ce_loss
|
||||
loss = fused_ce_loss(logits.cast(dtypes.bfloat16), tokens[:, 1:], label_smoothing=0.0)
|
||||
else:
|
||||
loss = logits.sparse_categorical_crossentropy(tokens[:, 1:])
|
||||
|
||||
for g, new_g in zip(grads, loss.gradient(*optim.params)):
|
||||
apply_grad(g, new_g.uop)
|
||||
|
||||
loss_cpu = loss.flatten().float().to("CPU")
|
||||
return loss_cpu.realize(*grads)
|
||||
Tensor.realize(loss, *grads)
|
||||
|
||||
@TinyJit
|
||||
def optim_step():
|
||||
grad_norm = clip_grads(grads, grad_acc, 1.0)
|
||||
optim.fstep(grads, grad_norm)
|
||||
clipped_grads, grad_norm = fclip_grads(grads, 1.0)
|
||||
optim.fstep(clipped_grads, grad_norm)
|
||||
scheduler.step()
|
||||
|
||||
for g in grads: g.assign(0)
|
||||
|
||||
loss_cpu = loss.flatten().float().to("CPU")
|
||||
lr_cpu = optim.lr.float().to("CPU")
|
||||
grad_norm_cpu = grad_norm.float().to("CPU")
|
||||
Tensor.realize(lr_cpu, grad_norm_cpu, *grads, *fp8_inv_scales)
|
||||
Tensor.realize(loss_cpu, lr_cpu, grad_norm_cpu, *grads, *fp8_inv_scales)
|
||||
|
||||
return lr_cpu, grad_norm_cpu
|
||||
return loss_cpu, lr_cpu, grad_norm_cpu
|
||||
|
||||
@TinyJit
|
||||
@Context(TRAINING=0)
|
||||
@@ -1844,30 +1843,20 @@ def train_gptoss():
|
||||
profile_marker(f"train @ {i}")
|
||||
st = time.perf_counter()
|
||||
|
||||
stopped = False
|
||||
losses, data_time, dev_time = [], 0, 0
|
||||
for _ in range(grad_acc if i >= 2 else 1):
|
||||
ist = time.perf_counter()
|
||||
try: tokens = next(train_iter)
|
||||
except StopIteration:
|
||||
stopped = True
|
||||
break
|
||||
mst = time.perf_counter()
|
||||
data_time += mst - ist
|
||||
losses.append(minibatch(tokens).item())
|
||||
dev_time += time.perf_counter() - mst
|
||||
if stopped: break
|
||||
ist = time.perf_counter()
|
||||
|
||||
gt = time.perf_counter()
|
||||
ret = optim_step()
|
||||
lr, grad_norm = ret[0].item(), ret[1].item()
|
||||
try: tokens = next(train_iter)
|
||||
except StopIteration: break
|
||||
mst = time.perf_counter()
|
||||
data_time = mst - ist
|
||||
|
||||
ret = step(tokens)
|
||||
dev_time = time.perf_counter() - mst
|
||||
|
||||
loss, lr, grad_norm = ret[0].item(), ret[1].item(), ret[2].item()
|
||||
et = time.perf_counter()
|
||||
|
||||
loss = sum(losses) / len(losses)
|
||||
optim_time = et - gt
|
||||
dev_time += optim_time
|
||||
step_time = et - st
|
||||
gbs_time = gt - st
|
||||
if BENCHMARK: step_times.append(step_time)
|
||||
|
||||
i += 1
|
||||
@@ -1877,7 +1866,7 @@ def train_gptoss():
|
||||
gflops = GlobalCounters.global_ops / 1e9 / dev_time
|
||||
mfu = ((6 * num_params * SEQLEN * GBS) / (dev_time * device_count * 4.6e15)) * 100
|
||||
tqdm.write(
|
||||
f"{i:5} {step_time:.3f} s step, {gbs_time:.3f} s gbs, {optim_time:.3f} s optim, {data_time:.3f} s data, {loss:.4f} loss, " \
|
||||
f"{i:5} {step_time:.3f} s step, {dev_time:.3f} s dev, {data_time:.3f} s data, {loss:.4f} loss, " \
|
||||
f"{lr:.12f} LR, {grad_norm:.6f} grad_norm, {mem_gb:.2f} GB used, {gflops:9.2f} GFLOPS, {mfu:5.2f}% MFU")
|
||||
if DEBUG >= 1: tqdm.write(" mem per device: " + ', '.join(f"{dev}: {mem/1e9:.2f} GB" for dev, mem in sorted(GlobalCounters.mem_used_per_device.items())))
|
||||
|
||||
@@ -1887,8 +1876,6 @@ def train_gptoss():
|
||||
"train/lr": lr,
|
||||
"train/grad_norm": grad_norm,
|
||||
"train/step_time": step_time,
|
||||
"train/gbs_time": gbs_time,
|
||||
"train/optim_time": optim_time,
|
||||
"train/dev_time": dev_time,
|
||||
"train/data_time": data_time,
|
||||
"train/mem": mem_gb,
|
||||
|
||||
@@ -83,8 +83,8 @@ def matmul(x:Tensor, w:Tensor, fp8:bool=True, amax_x:Tensor|None=None, w_inv_sca
|
||||
return out, x_fp8
|
||||
return (x_fp8.dot(w.T, dtype=dtypes.float) * ((amax_x.float() + 1e-8) / FP8_MAX) * w_inv_scale).cast(dtypes.bfloat16), x_fp8
|
||||
|
||||
def norm_quantize_matmul(x:Tensor, norm:Tensor, w:Tensor, w_inv_scale:Tensor, eps:float, amax_x:Tensor,
|
||||
next_amax_x:Tensor, grad_amax_state:Tensor, next_grad_amax_state:Tensor):
|
||||
def norm_quantize_matmul(x:Tensor, norm:Tensor, w:Tensor, w_inv_scale:Tensor, eps:float, amax_x:Tensor|None,
|
||||
next_amax_x:Tensor|None, grad_amax_state:Tensor|None, next_grad_amax_state:Tensor|None):
|
||||
if FUSED_ADD_NORM_MUL_QUANTIZE and not MXFP4:
|
||||
from extra.llama_kernels.fused_rmsnorm_mul_quantize_fp8 import fused_rmsnorm_mul_quantize_fp8
|
||||
x_fp8, x_normed, rrms = fused_rmsnorm_mul_quantize_fp8(x, norm, amax_x, eps, FP8_DTYPE, next_amax_x)
|
||||
@@ -96,8 +96,8 @@ def norm_quantize_matmul(x:Tensor, norm:Tensor, w:Tensor, w_inv_scale:Tensor, ep
|
||||
next_grad_amax_state=next_grad_amax_state, next_amax_x=next_amax_x)
|
||||
return out, x_normed, rrms, ret
|
||||
|
||||
def add_norm_quantize_matmul(x:Tensor, residual:Tensor, norm:Tensor, w:Tensor, w_inv_scale:Tensor, eps:float, amax_x:Tensor,
|
||||
next_amax_x:Tensor, grad_amax_state:Tensor|None=None, next_grad_amax_state:Tensor|None=None):
|
||||
def add_norm_quantize_matmul(x:Tensor, residual:Tensor, norm:Tensor, w:Tensor, w_inv_scale:Tensor, eps:float, amax_x:Tensor|None,
|
||||
next_amax_x:Tensor|None, grad_amax_state:Tensor|None=None, next_grad_amax_state:Tensor|None=None):
|
||||
if FUSED_ADD_NORM_MUL_QUANTIZE and not MXFP4:
|
||||
from extra.llama_kernels.fused_rmsnorm_mul_quantize_fp8 import fused_add_rmsnorm_mul_quantize_fp8
|
||||
x_fp8, h, x_normed, rrms = fused_add_rmsnorm_mul_quantize_fp8(x, residual, norm, amax_x, eps, FP8_DTYPE, next_amax_x)
|
||||
@@ -111,9 +111,14 @@ def add_norm_quantize_matmul(x:Tensor, residual:Tensor, norm:Tensor, w:Tensor, w
|
||||
return out, h, x_normed, rrms, ret
|
||||
|
||||
def silu_w13_quantize_matmul(x_w13:Tensor, w2:Tensor, s_2:Tensor,
|
||||
amax_x2:Tensor, next_amax_x2:Tensor,
|
||||
grad_amax_xw13:Tensor, next_grad_amax_xw13:Tensor,
|
||||
grad_amax_xout:Tensor, next_grad_amax_xout:Tensor):
|
||||
amax_x2:Tensor|None, next_amax_x2:Tensor|None,
|
||||
grad_amax_xw13:Tensor|None, next_grad_amax_xw13:Tensor|None,
|
||||
grad_amax_xout:Tensor|None, next_grad_amax_xout:Tensor|None):
|
||||
if FUSED_SILU_W13 and MXFP4:
|
||||
from extra.llama_kernels.swiglu import swiglu
|
||||
out, *ret = matmul(swiglu(x_w13), w2, amax_x=amax_x2, w_inv_scale=s_2, grad_amax_state=grad_amax_xout,
|
||||
next_grad_amax_state=next_grad_amax_xout, next_amax_x=next_amax_x2)
|
||||
return out, ret
|
||||
if FUSED_SILU_W13 and not MXFP4:
|
||||
from extra.llama_kernels.cast_amax import fused_quantize_fp8_w13
|
||||
x2_fp8 = fused_quantize_fp8_w13(x_w13, amax_x2, FP8_DTYPE, grad_amax_state=grad_amax_xw13,
|
||||
@@ -164,14 +169,15 @@ class FlatTransformer:
|
||||
self.freqs_cis = precompute_freqs_cis(dim // n_heads, max_context * 2, rope_theta).clone().is_param_(False)
|
||||
|
||||
def _amax(): return Tensor.full((), FP8_MAX, dtype=dtypes.float32).contiguous().is_param_(False)
|
||||
n_amax = 0 if MXFP4 else n_layers
|
||||
names = ["xqkv", "xo", "x2"]
|
||||
names += ["x1", "x3"] if SPLIT_W13 else ["x13"]
|
||||
self._fp8_amax = {name: [_amax() for _ in range(n_layers)] for name in names}
|
||||
self._fp8_next_amax = {name: [_amax() for _ in range(n_layers)] for name in names}
|
||||
self._fp8_amax = {name: [_amax() for _ in range(n_amax)] for name in names}
|
||||
self._fp8_next_amax = {name: [_amax() for _ in range(n_amax)] for name in names}
|
||||
grad_names = ["xqkv", "xo", "xout"]
|
||||
grad_names += ["xw1", "xw3"] if SPLIT_W13 else ["xw13"]
|
||||
self._fp8_grad_amax = {name: [_amax() for _ in range(n_layers)] for name in grad_names}
|
||||
self._fp8_next_grad_amax = {name: [_amax() for _ in range(n_layers)] for name in grad_names}
|
||||
self._fp8_grad_amax = {name: [_amax() for _ in range(n_amax)] for name in grad_names}
|
||||
self._fp8_next_grad_amax = {name: [_amax() for _ in range(n_amax)] for name in grad_names}
|
||||
w_scales = [("wqkv", s_qkv), ("wo", s_o), ("w2", s_2)]
|
||||
w_scales += [("w1", s_1), ("w3", s_3)] if SPLIT_W13 else [("w13", s_13)]
|
||||
self._fp8_inv_scale = {name: (s if MXFP8 else s.float()).contiguous().is_param_(False) for name, s in w_scales}
|
||||
@@ -195,9 +201,10 @@ class FlatTransformer:
|
||||
return (w * scale_b).clamp(-FP8_MAX, FP8_MAX).cast(FP8_DTYPE), inv_scale
|
||||
|
||||
def attention(self, x:Tensor, freqs_cis:Tensor, *, attention_norm:Tensor, wqkv:Tensor, wo:Tensor,
|
||||
amax_xqkv:Tensor, amax_xo:Tensor, s_qkv:Tensor, s_o:Tensor,
|
||||
next_amax_xqkv:Tensor, next_amax_xo:Tensor,
|
||||
grad_amax_xqkv:Tensor, grad_amax_xo:Tensor, next_grad_amax_xqkv:Tensor, next_grad_amax_xo:Tensor):
|
||||
amax_xqkv:Tensor|None, amax_xo:Tensor|None, s_qkv:Tensor, s_o:Tensor,
|
||||
next_amax_xqkv:Tensor|None, next_amax_xo:Tensor|None,
|
||||
grad_amax_xqkv:Tensor|None, grad_amax_xo:Tensor|None,
|
||||
next_grad_amax_xqkv:Tensor|None, next_grad_amax_xo:Tensor|None):
|
||||
bsz, seqlen, _ = x.shape
|
||||
saves = []
|
||||
|
||||
@@ -319,28 +326,33 @@ class FlatTransformer:
|
||||
for i in range(len(amax_dict[name])):
|
||||
amax_dict[name][i] = amax_dict[name][i].to(device).contiguous().is_param_(False)
|
||||
|
||||
def reset_amax(self):
|
||||
for st in (self._fp8_next_amax, self._fp8_next_grad_amax):
|
||||
for ts in st.values():
|
||||
for t in ts: t.assign(0)
|
||||
|
||||
def update_amax(self):
|
||||
for cur, nxt in ((self._fp8_amax, self._fp8_next_amax), (self._fp8_grad_amax, self._fp8_next_grad_amax)):
|
||||
for name in cur:
|
||||
for c, n in zip(cur[name], nxt[name]): c.assign(n)
|
||||
|
||||
def __call__(self, tokens:Tensor, save:bool=True):
|
||||
h = self.tok_embeddings(tokens)
|
||||
freqs_cis = self.freqs_cis.cast(h.dtype)
|
||||
if not getenv("HK_FLASH_ATTENTION"): freqs_cis = freqs_cis[:, :tokens.shape[1], :, :, :]
|
||||
a, na, ga, nga, s = self._fp8_amax, self._fp8_next_amax, self._fp8_grad_amax, self._fp8_next_grad_amax, self._fp8_inv_scale
|
||||
def amax_kwargs(i:int, act_names:tuple[str, ...], grad_names:tuple[str, ...]) -> dict[str, Tensor|None]:
|
||||
specs = (("amax_", a, act_names), ("next_amax_", na, act_names), ("grad_amax_", ga, grad_names), ("next_grad_amax_", nga, grad_names))
|
||||
if MXFP4: return dict.fromkeys(f"{prefix}{name}" for prefix, _, names in specs for name in names)
|
||||
return {f"{prefix}{name}":val[name][i] for prefix, val, names in specs for name in names}
|
||||
for i in range(self.n_layers):
|
||||
attn_kwargs = dict(attention_norm=self.attention_norm[i], wqkv=self.wqkv[i], wo=self.wo[i],
|
||||
amax_xqkv=a["xqkv"][i], amax_xo=a["xo"][i], s_qkv=s["wqkv"][i], s_o=s["wo"][i],
|
||||
next_amax_xqkv=na["xqkv"][i], next_amax_xo=na["xo"][i],
|
||||
grad_amax_xqkv=ga["xqkv"][i], grad_amax_xo=ga["xo"][i],
|
||||
next_grad_amax_xqkv=nga["xqkv"][i], next_grad_amax_xo=nga["xo"][i])
|
||||
ffn_kwargs = dict(ffn_norm=self.ffn_norm[i], w2=self.w2[i],
|
||||
amax_x2=a["x2"][i], s_2=s["w2"][i], grad_amax_xout=ga["xout"][i], next_grad_amax_xout=nga["xout"][i],
|
||||
next_amax_x2=na["x2"][i])
|
||||
attn_kwargs = dict(attention_norm=self.attention_norm[i], wqkv=self.wqkv[i], wo=self.wo[i], s_qkv=s["wqkv"][i], s_o=s["wo"][i],
|
||||
**amax_kwargs(i, ("xqkv", "xo"), ("xqkv", "xo")))
|
||||
ffn_kwargs = dict(ffn_norm=self.ffn_norm[i], w2=self.w2[i], s_2=s["w2"][i], **amax_kwargs(i, ("x2",), ("xout",)))
|
||||
if SPLIT_W13:
|
||||
ffn_kwargs.update(w1=self.w1[i], w3=self.w3[i], amax_x1=a["x1"][i], amax_x3=a["x3"][i],
|
||||
next_amax_x1=na["x1"][i], next_amax_x3=na["x3"][i],
|
||||
s_1=s["w1"][i], s_3=s["w3"][i], grad_amax_xw1=ga["xw1"][i], grad_amax_xw3=ga["xw3"][i],
|
||||
next_grad_amax_xw1=nga["xw1"][i], next_grad_amax_xw3=nga["xw3"][i])
|
||||
ffn_kwargs.update(w1=self.w1[i], w3=self.w3[i], s_1=s["w1"][i], s_3=s["w3"][i], **amax_kwargs(i, ("x1", "x3"), ("xw1", "xw3")))
|
||||
else:
|
||||
ffn_kwargs.update(w13=self.w13[i], amax_x13=a["x13"][i], s_13=s["w13"][i], grad_amax_xw13=ga["xw13"][i],
|
||||
next_grad_amax_xw13=nga["xw13"][i], next_amax_x13=na["x13"][i])
|
||||
ffn_kwargs.update(w13=self.w13[i], s_13=s["w13"][i], **amax_kwargs(i, ("x13",), ("xw13",)))
|
||||
h, *_ = self.run_layer(h, freqs_cis, attn_kwargs, ffn_kwargs, save=save)
|
||||
|
||||
logits = matmul(self.norm(h), self.output[0], fp8=False)[0]
|
||||
@@ -424,9 +436,7 @@ if __name__ == "__main__":
|
||||
@TinyJit
|
||||
def fwd_bwd(tokens:Tensor):
|
||||
with Timing("python forward: "):
|
||||
for amax_dict in (model._fp8_next_amax, model._fp8_next_grad_amax):
|
||||
for ts in amax_dict.values():
|
||||
for nxt in ts: nxt.assign(0)
|
||||
model.reset_amax()
|
||||
logits = model(tokens[:, :-1], save=llama_size=="8B")
|
||||
loss = vocab_mask.where(-1e9, logits).sparse_categorical_crossentropy(tokens[:, 1:])
|
||||
with Timing("python backward: "):
|
||||
|
||||
@@ -12,9 +12,9 @@ from tinygrad.helpers import Timing, colored, GlobalCounters, profile_marker
|
||||
from tinygrad.uop.ops import Ops, UOp
|
||||
from extra.models.llama import apply_rotary_emb
|
||||
from extra.llama_kernels.rmsnorm import rmsnorm
|
||||
from extra.gemm.cdna_asm_gemm import _mx_block_scale, _mx_block_scale_3d, quantize_mxfp8
|
||||
from extra.gemm.cdna_asm_gemm import _mx_block_scale, _mx_block_scale_3d, quantize_mxfp8, asm_gemm, can_use_asm_gemm, mx_pack
|
||||
from extra.gemm.moe_gemm import grouped_mx_gemm
|
||||
from extra.gemm.moe_routing import route, dispatch, combine
|
||||
from extra.gemm.moe_routing import route, dispatch, combine, router_mfma
|
||||
|
||||
FP8_DTYPE = dtypes.fp8e4m3
|
||||
FP8_MAX = 448.0
|
||||
@@ -41,7 +41,7 @@ def _quant_dequant_bwd(grad:UOp, call:UOp) -> tuple:
|
||||
|
||||
def quant_dequant_mx(x:Tensor) -> Tensor:
|
||||
fxn = _quant_dequant_fwd_fxn(x.as_param(0).uop, x.device)
|
||||
return Tensor(UOp.maketuple(fxn.uop).call(x.uop, grad_fxn=_quant_dequant_bwd).gettuple(0))
|
||||
return Tensor(fxn.uop.call_with_output(x.uop, grad_fxn=_quant_dequant_bwd))
|
||||
|
||||
def _mx_scale(e8:Tensor) -> Tensor:
|
||||
return _mx_block_scale(e8) if e8.ndim == 2 else _mx_block_scale_3d(e8)
|
||||
@@ -58,10 +58,27 @@ def _dequant_bwd(grad:UOp, call:UOp) -> tuple:
|
||||
|
||||
def dequant_weight(w_q:Tensor, w_scale:Tensor) -> Tensor:
|
||||
fxn = _dequant_fwd_fxn(w_q.as_param(0).uop, w_scale.as_param(1).uop, w_q.device)
|
||||
call = UOp.maketuple(fxn.uop).call(w_q.uop, w_scale.uop, grad_fxn=_dequant_bwd)
|
||||
return Tensor(call.gettuple(0))
|
||||
return Tensor(fxn.uop.call_with_output(w_q.uop, w_scale.uop, grad_fxn=_dequant_bwd))
|
||||
|
||||
def matmul_mx(x:Tensor, w_q:Tensor, w_scale:Tensor) -> Tensor:
|
||||
def matmul_mx(x:Tensor|tuple[Tensor, Tensor], w_q:Tensor, w_scale:Tensor) -> Tensor:
|
||||
if isinstance(x, tuple):
|
||||
assert ASM_GEMM, "pre-quantized MXFP8 input requires ASM_GEMM"
|
||||
from extra.gemm.cdna_asm_gemm import asm_gemm, can_use_asm_gemm, mx_pack
|
||||
x_q, x_e8 = x
|
||||
l_shape, padded = x_q.shape[:-1], x_q.shape[-1]
|
||||
x_q, x_e8 = x_q.reshape(-1, padded), x_e8.reshape(-1, padded // 32)
|
||||
K, N = w_q.shape[1], w_q.shape[0]
|
||||
assert padded >= K and (padded - K) % 32 == 0 and x_e8.shape[-1] == padded // 32
|
||||
wq, ws = w_q, w_scale
|
||||
if (pad := padded - K):
|
||||
wq = wq.pad(((0, 0), (0, pad)))
|
||||
ws = ws.pad(((0, 0), (0, pad // 32)), value=127).cast(dtypes.uint8)
|
||||
if (npad := (-N) % 256):
|
||||
wq = wq.pad(((0, npad), (0, 0)))
|
||||
ws = ws.pad(((0, npad), (0, 0)), value=127).cast(dtypes.uint8)
|
||||
assert can_use_asm_gemm(x_q, wq.T)
|
||||
out = asm_gemm(x_q, wq.T, mx=True, mx_scales=(mx_pack(x_e8), x_e8, mx_pack(ws), ws), mx_w_stored=True)
|
||||
return (out[:, :N] if npad else out).reshape(*l_shape, N).cast(dtypes.bfloat16)
|
||||
l_shape = x.shape[:-1]
|
||||
if ASM_GEMM:
|
||||
from extra.gemm.cdna_asm_gemm import asm_gemm, can_use_asm_gemm, mx_pack
|
||||
@@ -146,6 +163,7 @@ class GPTOSS:
|
||||
return w_q, w_e8.is_param_(False)
|
||||
if moe:
|
||||
qs = [_one(*shape[1:]) for _ in range(shape[0])]
|
||||
for q in qs: q[0]._zero2 = True # grad arrives sharded on the expert axis under ZeRO-2 (moe_gemm)
|
||||
return [q[0] for q in qs], [q[1] for q in qs]
|
||||
return _one(*shape)
|
||||
|
||||
@@ -174,20 +192,36 @@ class GPTOSS:
|
||||
def attention(self, x:Tensor, freqs_cis:Tensor, mask:Tensor, sliding:bool, *, attention_norm:Tensor, wqkv:Tensor,
|
||||
wqkv_scale:Tensor, wqkv_bias:Tensor, wo:Tensor, wo_scale:Tensor, wo_bias:Tensor, sinks:Tensor):
|
||||
bsz, seqlen, _ = x.shape
|
||||
x_normed, rrms = rmsnorm(x, self.norm_eps)
|
||||
qkv = matmul_mx(x_normed * attention_norm, wqkv, wqkv_scale) + wqkv_bias
|
||||
|
||||
if getenv("FUSED_RMSNORM_MX", 0):
|
||||
from extra.gptoss_kernels.rmsnorm import rmsnorm_mul_quantize_mxfp8
|
||||
x_q, x_e8, rrms = rmsnorm_mul_quantize_mxfp8(x, attention_norm, self.norm_eps)
|
||||
qkv = matmul_mx((x_q, x_e8), wqkv, wqkv_scale) + wqkv_bias
|
||||
norm_saves = [x_q, x_e8, rrms]
|
||||
if getenv("FUSED_RMSNORM_MUL", 0):
|
||||
from extra.gptoss_kernels.rmsnorm import rmsnorm_mul
|
||||
x_normed, rrms = rmsnorm_mul(x, attention_norm, self.norm_eps)
|
||||
qkv = matmul_mx(x_normed, wqkv, wqkv_scale) + wqkv_bias
|
||||
norm_saves = [x_normed, rrms]
|
||||
else:
|
||||
x_normed, rrms = rmsnorm(x, self.norm_eps)
|
||||
qkv = matmul_mx(x_normed * attention_norm, wqkv, wqkv_scale) + wqkv_bias
|
||||
norm_saves = [x_normed, rrms]
|
||||
|
||||
qkv = qkv.reshape(bsz, seqlen, self.n_kv_heads, self.n_rep + 2, self.head_dim)
|
||||
xq = qkv[:, :, :, :self.n_rep].reshape(bsz, seqlen, self.n_heads, self.head_dim)
|
||||
xk, xv = qkv[:, :, :, self.n_rep], qkv[:, :, :, self.n_rep + 1]
|
||||
xq, xk = apply_rotary_emb(xq, xk, freqs_cis)
|
||||
xq, xk, xv = xq.cast(dtypes.bfloat16), xk.cast(dtypes.bfloat16), xv.cast(dtypes.bfloat16) # (B,N,H,D)/(B,N,KV,D)
|
||||
|
||||
if sliding:
|
||||
attn = self._sliding_attention(xq, xk, xv, sinks)
|
||||
elif getenv("HK_FLASH_ATTENTION"):
|
||||
fa_saves = []
|
||||
if getenv("HK_FLASH_ATTENTION"):
|
||||
from extra.thunder.amd.fa import flash_attention
|
||||
attn, *_ = flash_attention(xq, xk, xv, is_causal=True, write_flat=True, sinks=sinks)
|
||||
attn, _, l_vec = flash_attention(xq, xk, xv, is_causal=True, write_flat=True, sinks=sinks, window=self.sliding_window if sliding else 0)
|
||||
attn = attn.reshape(bsz, seqlen, self.n_heads * self.head_dim)
|
||||
fa_saves = [xq, xk, xv, l_vec]
|
||||
elif sliding:
|
||||
attn = self._sliding_attention(xq, xk, xv, sinks)
|
||||
else:
|
||||
xqm = xq.reshape(bsz, seqlen, self.n_kv_heads, self.n_rep, self.head_dim).permute(0, 2, 3, 1, 4)
|
||||
xkm, xvm = xk.permute(0, 2, 1, 3).unsqueeze(2), xv.permute(0, 2, 1, 3).unsqueeze(2)
|
||||
@@ -199,14 +233,20 @@ class GPTOSS:
|
||||
attn = (w @ xvm).permute(0, 3, 1, 2, 4).reshape(bsz, seqlen, self.n_heads * self.head_dim)
|
||||
|
||||
out = matmul_mx(attn, wo, wo_scale) + wo_bias
|
||||
return out, [x_normed, rrms, attn]
|
||||
return out, [attn] + norm_saves + fa_saves
|
||||
|
||||
def feed_forward(self, x:Tensor, *, ffn_norm:Tensor, gate:Tensor, gate_bias:Tensor,
|
||||
w_gate_up:Tensor, w_gate_up_scale:Tensor, w_gate_up_bias:Tensor,
|
||||
w_down:Tensor, w_down_scale:Tensor, w_down_bias:Tensor):
|
||||
x_normed, rrms = rmsnorm(x, self.norm_eps)
|
||||
inp = x_normed * ffn_norm
|
||||
logits = inp.float() @ gate.float().T + gate_bias.float()
|
||||
if getenv("FUSED_RMSNORM_MUL", 0):
|
||||
from extra.gptoss_kernels.rmsnorm import rmsnorm_mul
|
||||
x_normed, rrms = rmsnorm_mul(x, ffn_norm, self.norm_eps)
|
||||
inp = x_normed
|
||||
else:
|
||||
x_normed, rrms = rmsnorm(x, self.norm_eps)
|
||||
inp = x_normed * ffn_norm
|
||||
|
||||
logits = router_mfma(inp, gate, gate_bias) if getenv("ROUTER_MFMA", 0) else inp.float() @ gate.float().T + gate_bias.float()
|
||||
dim, inter = self.dim, self.intermediate_size
|
||||
|
||||
if getenv("GROUPED_MOE", 0):
|
||||
@@ -220,6 +260,7 @@ class GPTOSS:
|
||||
z = grouped_mx_gemm(_pad_cols(y.cast(dtypes.bfloat16)), (w_down, w_down_scale), r.off)[:, :dim] \
|
||||
+ (onehot @ w_down_bias.float()).cast(dtypes.bfloat16)
|
||||
out = combine(z, r, inp.shape[0], self.experts_per_tok).reshape(bsz, seqlen, dim)
|
||||
return out, [x_normed, rrms, xg, h, y, z, r.weights, r.dest_row, r.off]
|
||||
else:
|
||||
thresh = logits.topk(self.experts_per_tok)[0][..., -1:]
|
||||
weights = (logits >= thresh).where(logits, -float("inf")).softmax(-1)
|
||||
@@ -263,7 +304,26 @@ class GPTOSS:
|
||||
w_down=self.w_down[i], w_down_scale=self.w_down_scale[i], w_down_bias=self.w_down_bias[i])
|
||||
h, *_ = self.run_layer(h, freqs_cis, mask_full, i % 2 == 0, attn_kwargs, ffn_kwargs, save=save)
|
||||
|
||||
logits = self.norm(h) @ self.output.T
|
||||
h_normed = self.norm(h)
|
||||
|
||||
if getenv("FP8_LMHEAD", 0) and ASM_GEMM:
|
||||
pad = (-self.dim) % 256
|
||||
h2 = h_normed.reshape(-1, self.dim).pad(((0, 0), (0, pad)))
|
||||
w2 = self.output.pad(((0, 0), (0, pad)))
|
||||
hq, he8, hsi = quantize_mxfp8(h2)
|
||||
oq, oe8, _ = quantize_mxfp8(w2)
|
||||
if hsi is not None and can_use_asm_gemm(hq, oq.T):
|
||||
logits = asm_gemm(hq, oq.T, mx=True, mx_scales=(hsi, he8, mx_pack(oe8), oe8), mx_w_stored=False)
|
||||
logits = logits.reshape(bsz, seqlen, self.vocab_size).cast(dtypes.bfloat16)
|
||||
else:
|
||||
logits = h_normed @ self.output.T
|
||||
elif ASM_GEMM:
|
||||
pad = (-self.dim) % 256
|
||||
h_padded, w_padded = h_normed.pad((None, None, (0, pad))), self.output.pad(((0, 0), (0, pad)))
|
||||
logits = asm_gemm(h_padded, w_padded.T) if can_use_asm_gemm(h_padded, w_padded.T) and getenv("VOCAB_ASM", 1) else h_normed @ self.output.T
|
||||
else:
|
||||
logits = h_normed @ self.output.T
|
||||
|
||||
return logits
|
||||
|
||||
def _get_pads(uop:UOp) -> list[UOp]:
|
||||
@@ -274,14 +334,14 @@ def apply_grad(grad_buf:Tensor, new_grad:UOp):
|
||||
pads = _get_pads(new_grad)
|
||||
if len(pads) <= 1:
|
||||
new_grad = new_grad.cast(grad_buf.dtype)
|
||||
grad_buf.uop = grad_buf.uop.after(grad_buf.uop.store(grad_buf.uop + new_grad))
|
||||
grad_buf.uop = grad_buf.uop.after(grad_buf.uop.store(new_grad))
|
||||
return
|
||||
cur = grad_buf.uop
|
||||
for pad in sorted(pads, key=lambda p: p.marg[0][0] if p.op == Ops.PAD else 0, reverse=True):
|
||||
if pad.op == Ops.PAD:
|
||||
grad_shrink = tuple([(p[0], s+p[0]) for s,p in zip(pad.src[0].shape, pad.marg)])
|
||||
grad_shrink = tuple((p[0], s+p[0]) for s,p in zip(pad.src[0].shape, pad.marg))
|
||||
buf_slice = cur.shrink(grad_shrink)
|
||||
cur = cur.after(buf_slice.store(buf_slice + pad.src[0].cast(cur.dtype)))
|
||||
cur = cur.after(buf_slice.store(pad.src[0].cast(cur.dtype)))
|
||||
else:
|
||||
cur = cur.after(cur.store(cur + pad.cast(cur.dtype)))
|
||||
grad_buf.uop = cur
|
||||
|
||||
@@ -15,7 +15,7 @@ def stochastic_round_bf16(x:Tensor) -> Tensor:
|
||||
bits = x.bitcast(dtypes.uint32)
|
||||
if isinstance(x.device, tuple):
|
||||
shape = x.uop.shard_shape if x.uop.axis is not None else x.shape
|
||||
noise = Tensor(UOp(Ops.MSTACK, dtypes.default_float, tuple(Tensor.rand(*shape, device=d).uop for d in x.device)))
|
||||
noise = Tensor(UOp(Ops.MSTACK, src=tuple(Tensor.rand(*shape, device=d).uop for d in x.device)))
|
||||
else:
|
||||
noise = x.rand_like()
|
||||
noise = (noise * 0xFFFF).cast(dtypes.uint32)
|
||||
@@ -27,6 +27,11 @@ def clip_grads(grads:list[Tensor], grad_acc, clip_norm) -> Tensor:
|
||||
for g in grads: g.assign((g * (clip_norm / (total_norm + 1e-6)).clamp(max_=1.0)).cast(g.dtype))
|
||||
return total_norm
|
||||
|
||||
def fclip_grads(grads:list[Tensor], clip_norm) -> Tensor:
|
||||
total_norm = Tensor.stack(*[g.float().square().sum() for g in grads]).sum().sqrt().contiguous()
|
||||
scale = (clip_norm / (total_norm + 1e-6)).clamp(max_=1.0)
|
||||
return [(g * scale).cast(g.dtype) for g in grads], total_norm
|
||||
|
||||
class GradAccClipAdamW(Optimizer):
|
||||
def __init__(self, params:list[Tensor], lr=0.001, b1=0.9, b2=0.999, eps=1e-6, weight_decay=0.0, grad_acc=1, clip_norm=1.0, device=None, fused=FUSE_OPTIM):
|
||||
super().__init__(params, lr, device, fused)
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
from pathlib import Path
|
||||
|
||||
from examples.mlperf.dataloader import get_llama3_dataset
|
||||
from tinygrad.helpers import getenv
|
||||
|
||||
BASEDIR = Path(getenv("BASEDIR", "/raid/datasets/c4-8b/"))
|
||||
SAMPLES = getenv("SAMPLES", 1_200_000 * 32)
|
||||
EVAL_SAMPLES = getenv("EVAL_SAMPLES", 1024)
|
||||
SEQLEN = getenv("SEQLEN", 8192)
|
||||
DATA_SEED = getenv("DATA_SEED", 5760)
|
||||
|
||||
get_llama3_dataset(SAMPLES, SEQLEN, BASEDIR, seed=DATA_SEED, val=False, small=True)
|
||||
get_llama3_dataset(EVAL_SAMPLES, SEQLEN, BASEDIR, seed=0, val=True, small=True)
|
||||
+6
-6
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
export PYTHONPATH="."
|
||||
export PATH="/opt/rocm-7.1.1/bin:$PATH"
|
||||
export ROCM_PATH="/opt/rocm-7.1.1"
|
||||
export ROCM_PATH=${ROCM_PATH:-/opt/rocm-7.1.1}
|
||||
export PATH="$ROCM_PATH/bin:$PATH"
|
||||
export DEV=${DEV:-AMD}
|
||||
export CHECK_OOB=0
|
||||
export REWRITE_STACK_LIMIT=5000000 HCQDEV_WAIT_TIMEOUT_MS=240000
|
||||
@@ -16,7 +16,7 @@ export USE_ATOMICS=${USE_ATOMICS:-1}
|
||||
export ASM_GEMM=${ASM_GEMM:-1}
|
||||
export WQKV=${WQKV:-1}
|
||||
export MASTER_WEIGHTS=${MASTER_WEIGHTS:-1}
|
||||
export FP8=${FP8:-1}
|
||||
export MXFP4=${MXFP4:-1}
|
||||
export ALLREDUCE_CAST=${ALLREDUCE_CAST:-1}
|
||||
export FAST_CE=${FAST_CE:-1}
|
||||
export FUSED_INPUT_QUANTIZE=${FUSED_INPUT_QUANTIZE:-1}
|
||||
@@ -26,7 +26,7 @@ export FUSED_SILU_W13=${FUSED_SILU_W13:-1}
|
||||
export SPLIT_W13=${SPLIT_W13:-0}
|
||||
export OFFLOAD_OPTIM=${OFFLOAD_OPTIM:-0}
|
||||
|
||||
export DEFAULT_FLOAT="bfloat16" OPTIM_DTYPE="bfloat16"
|
||||
export DEFAULT_FLOAT="bfloat16" OPTIM_DTYPE="float32"
|
||||
export DP=${DP:-8} MP=${MP:-1} BS=${BS:-16} EVAL_BS=${EVAL_BS:-8} GRADIENT_ACC_STEPS=${GRADIENT_ACC_STEPS:-2}
|
||||
export GBS=$((BS * GRADIENT_ACC_STEPS))
|
||||
|
||||
@@ -35,7 +35,7 @@ export BASEDIR="/raid/datasets/c4-8b/"
|
||||
export SMALL=1
|
||||
export LLAMA3_SIZE=${LLAMA3_SIZE:-"8B"}
|
||||
export EVAL_TARGET=3.3 EVAL_FREQ=12288
|
||||
export LR="1e-3" END_LR="1e-4" WARMUP_SAMPLES=4096 MAX_STEPS=1200000
|
||||
export LR="1e-3" END_LR="1e-4" WARMUP_SAMPLES=2048 MAX_STEPS=1200000
|
||||
export WARMUP_STEPS=$((WARMUP_SAMPLES / GBS))
|
||||
export SAMPLES=$((MAX_STEPS * GBS))
|
||||
export SEQLEN=${SEQLEN:-8192}
|
||||
@@ -44,7 +44,7 @@ export SEED=${SEED:-5760}
|
||||
export DATA_SEED=${DATA_SEED:-5760}
|
||||
|
||||
export JITBEAM=${JITBEAM:-3}
|
||||
export BEAM_UOPS_MAX=6000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5 BEAM_PADTO=1
|
||||
export BEAM_UOPS_MAX=6000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5 BEAM_PADTO=0
|
||||
|
||||
export FAKEDATA=${FAKEDATA:-1} BENCHMARK=${BENCHMARK:-10}
|
||||
if [ -z "$FULL_LAYERS" ]; then
|
||||
|
||||
+6
-6
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
export PYTHONPATH="."
|
||||
export PATH="/opt/rocm-7.1.1/bin:$PATH"
|
||||
export ROCM_PATH="/opt/rocm-7.1.1"
|
||||
export ROCM_PATH=${ROCM_PATH:-/opt/rocm-7.1.1}
|
||||
export PATH="$ROCM_PATH/bin:$PATH"
|
||||
export DEV=${DEV:-AMD}
|
||||
export CHECK_OOB=0
|
||||
export REWRITE_STACK_LIMIT=5000000 HCQDEV_WAIT_TIMEOUT_MS=240000
|
||||
@@ -16,7 +16,7 @@ export USE_ATOMICS=${USE_ATOMICS:-1}
|
||||
export ASM_GEMM=${ASM_GEMM:-1}
|
||||
export WQKV=${WQKV:-1}
|
||||
export MASTER_WEIGHTS=${MASTER_WEIGHTS:-1}
|
||||
export FP8=${FP8:-1}
|
||||
export MXFP4=${MXFP4:-1}
|
||||
export ALLREDUCE_CAST=${ALLREDUCE_CAST:-1}
|
||||
export FAST_CE=${FAST_CE:-1}
|
||||
export FUSED_INPUT_QUANTIZE=${FUSED_INPUT_QUANTIZE:-1}
|
||||
@@ -26,7 +26,7 @@ export FUSED_SILU_W13=${FUSED_SILU_W13:-1}
|
||||
export SPLIT_W13=${SPLIT_W13:-0}
|
||||
export OFFLOAD_OPTIM=${OFFLOAD_OPTIM:-0}
|
||||
|
||||
export DEFAULT_FLOAT="bfloat16" OPTIM_DTYPE="bfloat16"
|
||||
export DEFAULT_FLOAT="bfloat16" OPTIM_DTYPE="float32"
|
||||
export DP=${DP:-8} MP=${MP:-1} BS=${BS:-16} EVAL_BS=${EVAL_BS:-8} GRADIENT_ACC_STEPS=${GRADIENT_ACC_STEPS:-2}
|
||||
export GBS=$((BS * GRADIENT_ACC_STEPS))
|
||||
|
||||
@@ -35,7 +35,7 @@ export BASEDIR="/raid/datasets/c4-8b/"
|
||||
export SMALL=1
|
||||
export LLAMA3_SIZE=${LLAMA3_SIZE:-"8B"}
|
||||
export EVAL_TARGET=3.3 EVAL_FREQ=12288
|
||||
export LR="1e-3" END_LR="1e-4" WARMUP_SAMPLES=4096 MAX_STEPS=1200000
|
||||
export LR="1e-3" END_LR="1e-4" WARMUP_SAMPLES=2048 MAX_STEPS=1200000
|
||||
export WARMUP_STEPS=$((WARMUP_SAMPLES / GBS))
|
||||
export SAMPLES=$((MAX_STEPS * GBS))
|
||||
export SEQLEN=${SEQLEN:-8192}
|
||||
@@ -44,6 +44,6 @@ export SEED=${SEED:-$RANDOM}
|
||||
export DATA_SEED=${DATA_SEED:-5760}
|
||||
|
||||
export JITBEAM=${JITBEAM:-3}
|
||||
export BEAM_UOPS_MAX=6000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5 BEAM_PADTO=1
|
||||
export BEAM_UOPS_MAX=6000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5 BEAM_PADTO=0
|
||||
|
||||
python3 examples/mlperf/model_train.py
|
||||
|
||||
+2
@@ -1,4 +1,6 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
export BENCHMARK=${BENCHMARK:-5}
|
||||
export EVAL_BS=0
|
||||
VIZ=${VIZ:--1} FULL_LAYERS=1 DEBUG=${DEBUG:--0} examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama31_8b/implementations/tinybox_8xMI350X/dev_beam.sh
|
||||
|
||||
+3
-3
@@ -17,7 +17,7 @@ export USE_ATOMICS=1
|
||||
export ASM_GEMM=1
|
||||
export WQKV=1
|
||||
export MASTER_WEIGHTS=1
|
||||
export FP8=1
|
||||
export MXFP4=1
|
||||
export ALLREDUCE_CAST=1
|
||||
export FAST_CE=1
|
||||
export FUSED_INPUT_QUANTIZE=1
|
||||
@@ -26,7 +26,7 @@ export FUSED_ADD_NORM_MUL_QUANTIZE=1
|
||||
export FUSED_SILU_W13=1
|
||||
export SPLIT_W13=0
|
||||
|
||||
export DEFAULT_FLOAT="bfloat16" OPTIM_DTYPE="bfloat16"
|
||||
export DEFAULT_FLOAT="bfloat16" OPTIM_DTYPE="float32"
|
||||
export DP=8 MP=1 BS=16 EVAL_BS=8 GRADIENT_ACC_STEPS=2
|
||||
export GBS=$((BS * GRADIENT_ACC_STEPS))
|
||||
|
||||
@@ -44,7 +44,7 @@ export SEED=$RANDOM
|
||||
export DATA_SEED=$SEED
|
||||
|
||||
export JITBEAM=3
|
||||
export BEAM_UOPS_MAX=6000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5 BEAM_PADTO=1
|
||||
export BEAM_UOPS_MAX=6000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5 BEAM_PADTO=0
|
||||
|
||||
export LOGMLPERF=1
|
||||
|
||||
|
||||
+2
@@ -10,6 +10,8 @@ export DEVICE_IN_FUNCTION_BUG=1
|
||||
|
||||
export DEBUG=${DEBUG:-2}
|
||||
export HK_FLASH_ATTENTION=${HK_FLASH_ATTENTION:-1}
|
||||
export ASM_GEMM=${ASM_GEMM:-1}
|
||||
export GROUPED_MOE=${GROUPED_MOE:-1}
|
||||
export ALL2ALL=${ALL2ALL:-1}
|
||||
export LATE_ALLREDUCE=${LATE_ALLREDUCE:-0}
|
||||
export ALLREDUCE_CAST=${ALLREDUCE_CAST:-1}
|
||||
|
||||
+2
@@ -10,6 +10,8 @@ export DEVICE_IN_FUNCTION_BUG=1
|
||||
|
||||
export DEBUG=${DEBUG:-0}
|
||||
export HK_FLASH_ATTENTION=${HK_FLASH_ATTENTION:-1}
|
||||
export ASM_GEMM=${ASM_GEMM:-1}
|
||||
export GROUPED_MOE=${GROUPED_MOE:-1}
|
||||
export ALL2ALL=${ALL2ALL:-1}
|
||||
export LATE_ALLREDUCE=${LATE_ALLREDUCE:-0}
|
||||
export ALLREDUCE_CAST=${ALLREDUCE_CAST:-1}
|
||||
|
||||
@@ -107,14 +107,21 @@ def compile(onnx_file):
|
||||
return inputs, test_val
|
||||
|
||||
def test_vs_compile(run, inputs, test_val=None):
|
||||
if (log:=bool(getenv("BENCHMARK_LOG", ""))): from extra.bench_log import WallTimeEvent, BenchEvent
|
||||
|
||||
# run 20 times
|
||||
step_times = []
|
||||
for _ in range(20):
|
||||
st = time.perf_counter()
|
||||
out = run(**inputs)
|
||||
mt = time.perf_counter()
|
||||
val = out.numpy()
|
||||
if log:
|
||||
with WallTimeEvent(BenchEvent.STEP):
|
||||
out = run(**inputs)
|
||||
mt = time.perf_counter()
|
||||
val = out.numpy()
|
||||
else:
|
||||
out = run(**inputs)
|
||||
mt = time.perf_counter()
|
||||
val = out.numpy()
|
||||
et = time.perf_counter()
|
||||
step_times.append((et-st)*1e3)
|
||||
print(f"enqueue {(mt-st)*1e3:6.2f} ms -- total run {step_times[-1]:6.2f} ms")
|
||||
@@ -160,12 +167,6 @@ def test_vs_onnx(new_inputs, test_val, onnx_file, tol):
|
||||
print("test vs onnx passed")
|
||||
return timings
|
||||
|
||||
def bench(run, inputs):
|
||||
from extra.bench_log import WallTimeEvent, BenchEvent
|
||||
for _ in range(10):
|
||||
with WallTimeEvent(BenchEvent.STEP):
|
||||
run(**inputs).numpy()
|
||||
|
||||
if __name__ == "__main__":
|
||||
if getenv("RUN_PICKLE"):
|
||||
with open(OUTPUT, "rb") as f: pickle_loaded = load_pickle(f)
|
||||
@@ -181,6 +182,3 @@ if __name__ == "__main__":
|
||||
test_vs_compile(pickle_loaded, inputs, outputs)
|
||||
if getenv("SELFTEST"):
|
||||
test_vs_onnx(inputs, outputs, onnx_file, 1e-4)
|
||||
|
||||
if getenv("BENCHMARK_LOG", ""):
|
||||
bench(pickle_loaded, inputs)
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
# Navi31 flash tools
|
||||
|
||||
Utilities for reading and recovering the 2 MiB SPI flash on Navi31 boards.
|
||||
Run them from the tinygrad repository root. No image is bundled; keep a verified
|
||||
full-ROM backup before performing any write.
|
||||
|
||||
`fw_live.py` accesses BAR5 through tinygrad's `PCIDevice.map_bar()` abstraction
|
||||
and supports either the custom ASM24 USB-PCIe bridge or native PCIe. Select the
|
||||
transport before the subcommand:
|
||||
|
||||
```sh
|
||||
python3 extra/amdflash/fw_live.py --transport usb probe
|
||||
python3 extra/amdflash/fw_live.py --transport pci probe
|
||||
```
|
||||
|
||||
The default, `--transport auto`, considers USB devices first and then native
|
||||
PCI devices. Native PCI access requires the usual tinygrad PCI permissions and
|
||||
an unbound kernel driver.
|
||||
|
||||
## Access paths and hardware state
|
||||
|
||||
The paths are state-dependent and are not interchangeable:
|
||||
|
||||
* **`romless.py`** drives SMUIO `ROM_SW_*` directly through the ASM24 bridge.
|
||||
Use it only when an empty or corrupt flash has stalled the PSP PBL. Healthy
|
||||
autonomous boot gates this engine; the usual gated status is
|
||||
`ROM_SW_STATUS=0x04000800`.
|
||||
* **`fw_live.py probe`** queries the early PSP boot-firmware mailbox.
|
||||
* Firmware-mediated write commands are retained for protocol documentation but
|
||||
are disabled because an exact stock reflash did not validate safely.
|
||||
* **`fw_live.py dump`** reads an exact 2 MiB raw image through
|
||||
`ROM_INDEX/ROM_DATA`. It refuses devices where the raw SMUIO controller is
|
||||
unavailable; the NBIO SOC15 function-ROM aperture is not a physical SPI
|
||||
mapping and is deliberately not used as a fallback.
|
||||
|
||||
The tools do not reset or power-cycle the board.
|
||||
|
||||
## Raw ROM_SW recovery
|
||||
|
||||
Identification and read-only operations:
|
||||
|
||||
```sh
|
||||
python3 extra/amdflash/romless.py info
|
||||
python3 extra/amdflash/romless.py read 0 0x40
|
||||
python3 extra/amdflash/romless.py dump spi.bin
|
||||
python3 extra/amdflash/romless.py verify known-good.bin
|
||||
```
|
||||
|
||||
Restore an exact 2 MiB image:
|
||||
|
||||
```sh
|
||||
python3 extra/amdflash/romless.py flash known-good.bin --yes
|
||||
```
|
||||
|
||||
If GD25 status-register bit `SR2.CMP` protects the complete array, clearing it
|
||||
requires separate authorization:
|
||||
|
||||
```sh
|
||||
python3 extra/amdflash/romless.py flash known-good.bin --clear-cmp --yes
|
||||
```
|
||||
|
||||
Programming is sector-granular. Every written 4 KiB sector is immediately read
|
||||
back and compared with the input. A range can be resumed independently:
|
||||
|
||||
```sh
|
||||
python3 extra/amdflash/romless.py flash known-good.bin \
|
||||
--start-sector 128 --sector-count 64 --yes
|
||||
```
|
||||
|
||||
Navi31 ROM_SW details used by the implementation:
|
||||
|
||||
* `ROM_SW_COMMAND = (address << 8) | opcode`
|
||||
* TX data uses big-endian stream dwords
|
||||
* `RETURN_DATA_EN` (bit 19) is clear for TX and set for RX
|
||||
* the RX window exposes the preceding transaction, so reads are primed once
|
||||
|
||||
## Firmware-mediated access
|
||||
|
||||
The read-only commands are:
|
||||
|
||||
```sh
|
||||
python3 extra/amdflash/fw_live.py probe
|
||||
python3 extra/amdflash/fw_live.py dump current-spi.bin
|
||||
```
|
||||
|
||||
`dump` produces exactly `0x200000` bytes, requires the raw IFWI magic at offset
|
||||
zero, rejects mirrored 1 MiB apertures, and restores the ROM controller/index
|
||||
state before writing output.
|
||||
|
||||
The validated early-firmware sequence is available as:
|
||||
|
||||
```sh
|
||||
python3 extra/amdflash/fw_live.py --transport usb ifwi-all full-ifwi.bin --yes
|
||||
```
|
||||
|
||||
It resolves at most Navi31's configured 19 items, streams the item associated
|
||||
with terminal phase `0x2xx`, and then stops. PSP selects the destination
|
||||
partition; item `0x08` always comes from the payload referenced by the first
|
||||
ISH descriptor, matching AMDVBFlash. A hard power cycle is required afterward.
|
||||
|
||||
A successful PSP update is not a byte-identical raw rewrite. On the validated
|
||||
stock test, both A/B payloads matched the source exactly, PSP selected and
|
||||
booted the updated B partition, and firmware changed only its update cookie,
|
||||
B descriptor counter/checksum, and generated metadata near `0x1ef000`.
|
||||
|
||||
The `stream`, `ifwi-step`, and `live-flash` commands remain disabled. Testing
|
||||
showed that the PSP live path parses a raw stock IFWI but fails with status
|
||||
`0xC` (`PSP Write To SPI Error`) after writing an `$AMDVBFL` cookie. Use the
|
||||
verified ROM_SW path for recovery.
|
||||
|
||||
## Safety
|
||||
|
||||
ROM_SW erase/program and `ifwi-all` commands require `--yes`; other
|
||||
firmware-streaming commands are disabled. Read-only commands still touch controller and mailbox registers but
|
||||
do not issue SPI program/erase or PSP transfer-start commands. Preserve a
|
||||
known-good full dump outside the repository.
|
||||
@@ -0,0 +1,53 @@
|
||||
from __future__ import annotations
|
||||
import struct, sys, time
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT))
|
||||
from tinygrad.runtime.support.usb import USB3
|
||||
from tinygrad.runtime.support.system import PCIDevice, System, USBPCIDevice
|
||||
|
||||
USB_IDS = ((0x3801, 0x0001), (0xADD1, 0x0001))
|
||||
NAVI31_DEVICES = ((0xffff, (0x744c,)),)
|
||||
|
||||
|
||||
def open_gpu(index: int = 0, transport: str = 'auto') -> PCIDevice:
|
||||
"""Open an AMD GPU through tinygrad's transport-independent PCI interface."""
|
||||
if transport not in ('auto', 'usb', 'pci'): raise ValueError(f"unsupported transport {transport!r}")
|
||||
candidates = []
|
||||
if transport in ('auto', 'usb'):
|
||||
for vendor, product in USB_IDS:
|
||||
candidates += [(USBPCIDevice, dev) for dev in USB3.list_devices(vendor, product)]
|
||||
if transport in ('auto', 'pci'):
|
||||
candidates += System.list_devices(0x1002, NAVI31_DEVICES)
|
||||
if not candidates: raise RuntimeError(f"no supported {transport} AMD GPU found")
|
||||
if not 0 <= index < len(candidates): raise RuntimeError(f"device index {index} out of range (found {len(candidates)})")
|
||||
cls, descriptor = candidates[index]
|
||||
return cls("AM", *descriptor) if cls is USBPCIDevice else cls("AM", descriptor)
|
||||
|
||||
|
||||
class MMIO:
|
||||
"""Transport-independent byte view of BAR5."""
|
||||
def __init__(self, pci_dev: PCIDevice): self.bar = pci_dev.map_bar(5, fmt='B')
|
||||
|
||||
def read32(self, offset: int) -> int:
|
||||
return struct.unpack('<I', bytes(self.bar[offset:offset+4]))[0]
|
||||
|
||||
def write32(self, offset: int, value: int):
|
||||
self.write(offset, struct.pack('<I', value & 0xffffffff))
|
||||
|
||||
def read(self, offset: int, size: int) -> bytes:
|
||||
return bytes(self.bar[offset:offset+size])
|
||||
|
||||
def write(self, offset: int, data: bytes):
|
||||
self.bar[offset:offset+len(data)] = data
|
||||
|
||||
|
||||
def wait_until(fn, timeout: float, message: str, interval: float = 0.001):
|
||||
if timeout <= 0 or timeout > 60: raise ValueError("timeout must be in (0, 60] seconds")
|
||||
end = time.monotonic() + timeout
|
||||
while True:
|
||||
value = fn()
|
||||
if value: return value
|
||||
if time.monotonic() >= end: raise TimeoutError(message)
|
||||
time.sleep(interval)
|
||||
Executable
+292
@@ -0,0 +1,292 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Navi31 firmware-mediated flash access and ROM aperture dumping.
|
||||
|
||||
Early item streaming must run after autonomous PSP boot but before a host
|
||||
driver or AMDev loads SOS. A fully initialized SOS rejects those commands.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import argparse, struct, sys, time
|
||||
from pathlib import Path
|
||||
from common import MMIO, open_gpu, wait_until
|
||||
|
||||
ROM_CNTL, ROM_INDEX, ROM_DATA = 0x5A380, 0x5A390, 0x5A394
|
||||
FLASH_SIZE, INDEX_PAGE = 0x200000, 0x10000
|
||||
|
||||
def bswap32(value: int) -> int: return int.from_bytes(value.to_bytes(4, 'little'), 'big')
|
||||
|
||||
COMMAND_DATA, COMMAND, DOORBELL = 0x582D0, 0x582CC, 0x58224
|
||||
GET_BOOT_PARTITION, GET_FB_STATE, GET_TRANSFER_TYPE = 0x01, 0x06, 0x07
|
||||
START_TRANSFER, DATA_TRANSFER, END_TRANSFER = 0x08, 0x09, 0x0A
|
||||
SPI_GET_MODEL_ID = 0x0B
|
||||
LIVE_ADDR_LO, LIVE_ADDR_HI, LIVE_UPDATE = 0x02, 0x03, 0x04
|
||||
PSP_ERRORS = {
|
||||
0x01: "generic error", 0x02: "out of bounds", 0x03: "invalid parameter",
|
||||
0x04: "off-chip boot error", 0x05: "address not set", 0x06: "parse off-chip error",
|
||||
0x07: "address map error", 0x08: "parse on-chip error", 0x09: "full update error",
|
||||
0x0A: "partition update error", 0x0B: "map on-chip error", 0x0C: "write to SPI error",
|
||||
0x0D: "signature validation error", 0x0E: "invalid command", 0x0F: "signature not found",
|
||||
0x10: "state machine not initialized", 0x11: "state machine transfer error",
|
||||
0x12: "initialization error",
|
||||
}
|
||||
|
||||
|
||||
class PSPFlashMailbox:
|
||||
def __init__(self, pci_dev): self.mmio = MMIO(pci_dev)
|
||||
|
||||
def command(self, command: int, data: int | None = None, *, timeout: float = 10.0) -> tuple[int, int]:
|
||||
status = self.mmio.read32(COMMAND)
|
||||
if not status & 0x80000000:
|
||||
raise RuntimeError(f"PSP mailbox is not ready before command {command:#x}: status={status:#010x}")
|
||||
if data is not None: self.mmio.write32(COMMAND_DATA, data)
|
||||
self.mmio.write32(COMMAND, command << 16)
|
||||
self.mmio.write32(DOORBELL, 1)
|
||||
wait_until(lambda: self.mmio.read32(COMMAND) & 0x80000000, timeout,
|
||||
f"PSP mailbox command {command:#x} timed out")
|
||||
value = self.mmio.read32(COMMAND)
|
||||
return value & 0xffff, self.mmio.read32(COMMAND_DATA)
|
||||
|
||||
def require(self, command: int, data: int | None = None, *, timeout: float = 10.0, name: str = '') -> int:
|
||||
error, response = self.command(command, data, timeout=timeout)
|
||||
if error:
|
||||
detail = PSP_ERRORS.get(error, "unknown error")
|
||||
raise RuntimeError(f"PSP {name or hex(command)} failed: error={error:#x} ({detail})")
|
||||
return response
|
||||
|
||||
def probe(self) -> dict[str, tuple[int, int]]:
|
||||
result = {}
|
||||
for name, command in (("boot_partition", GET_BOOT_PARTITION), ("fb_state", GET_FB_STATE),
|
||||
("model_id", SPI_GET_MODEL_ID), ("transfer_type", GET_TRANSFER_TYPE)):
|
||||
result[name] = self.command(command)
|
||||
return result
|
||||
|
||||
def stream(self, payload: bytes, item_type: int, transfer_type: int | None = None):
|
||||
if not payload: raise ValueError("payload is empty")
|
||||
if len(payload) > 0xFFFFFF: raise ValueError("payload exceeds the mailbox's 24-bit size field")
|
||||
if len(payload) & 3: raise ValueError("payload size must be divisible by four")
|
||||
if not 0 <= item_type <= 0xff: raise ValueError("item type must fit in eight bits")
|
||||
if transfer_type is None: transfer_type = self.require(GET_TRANSFER_TYPE, name="GET_TRANSFER_TYPE")
|
||||
requested = transfer_type & 0xff
|
||||
print(f"firmware transfer_type={transfer_type:#x}", flush=True)
|
||||
if requested != item_type:
|
||||
raise RuntimeError(f"firmware requests item {requested:#x}, not {item_type:#x}")
|
||||
self.require(START_TRANSFER, (len(payload) << 8) | item_type, name="START_TRANSFER")
|
||||
sent, started = 0, time.monotonic()
|
||||
try:
|
||||
for offset in range(0, len(payload), 4):
|
||||
word = struct.unpack_from('<I', payload, offset)[0]
|
||||
self.require(DATA_TRANSFER, word, name=f"DATA_TRANSFER@{offset:#x}")
|
||||
sent = offset + 4
|
||||
if sent % 0x1000 == 0:
|
||||
print(f"{sent:#x}/{len(payload):#x} ({sent/(time.monotonic()-started)/1024:.1f} KiB/s)", flush=True)
|
||||
self.require(END_TRANSFER, (sent << 8) | item_type, timeout=60.0, name="END_TRANSFER")
|
||||
except BaseException:
|
||||
# Give firmware a chance to terminate an interrupted partial session. Do
|
||||
# not submit END_TRANSFER twice if firmware rejected the original END.
|
||||
if sent != len(payload):
|
||||
try: self.command(END_TRANSFER, (sent << 8) | item_type, timeout=10.0)
|
||||
except Exception: pass
|
||||
raise
|
||||
print(f"stream complete: type={item_type:#x} size={sent:#x} elapsed={time.monotonic()-started:.1f}s")
|
||||
|
||||
|
||||
def resolve_ifwi_item(image: bytes, item_type: int) -> tuple[int, bytes]:
|
||||
"""Resolve AMDVBFlash recovery-layout item types to exact IFWI bytes."""
|
||||
if item_type == 0x01: offset, size = 0, 0x54
|
||||
elif item_type in (0x02, 0x03):
|
||||
offset = 0x2000 if item_type == 0x02 else 0x3000
|
||||
if image[offset:offset+4] != b'$PSP': raise ValueError(f"invalid PSP directory at {offset:#x}")
|
||||
size = (struct.unpack_from('<I', image, offset + 8)[0] + 1) * 0x10
|
||||
elif item_type == 0x04: offset, size = 0x10000, 0x1000
|
||||
elif item_type == 0x05: offset, size = 0x11000, 0x1000
|
||||
elif item_type == 0x06: offset, size = 0x12000, 0x20
|
||||
elif item_type == 0x07: offset, size = 0x13000, 0x20
|
||||
elif item_type == 0x80: offset, size = 0x1000, 4
|
||||
elif item_type == 0x81:
|
||||
offset = struct.unpack_from('<I', image, 0x1000)[0]
|
||||
if image[offset:offset+4] != b'$SGN': raise ValueError("invalid $SGN table pointer")
|
||||
size = (struct.unpack_from('<I', image, offset + 8)[0] + 1) * 0x10
|
||||
elif 0x82 <= item_type <= 0x88:
|
||||
table = struct.unpack_from('<I', image, 0x1000)[0]
|
||||
if image[table:table+4] != b'$SGN': raise ValueError("invalid $SGN table pointer")
|
||||
wanted = item_type - 0x81 # 82h..88h map to SIGN_TYPE 1..7
|
||||
count = struct.unpack_from('<I', image, table + 8)[0]
|
||||
entries = [struct.unpack_from('<IIII', image, table + 0x10 + i*0x10) for i in range(count)]
|
||||
match = [entry for entry in entries if entry[0] == wanted]
|
||||
if len(match) != 1: raise ValueError(f"missing $SGN type {wanted}")
|
||||
_, _, size, offset = match[0]
|
||||
elif item_type == 0x89: offset, size = 0x1f0000, 0x100
|
||||
elif item_type == 0x08:
|
||||
# AMDVBFlash's GetPartitionDetails follows the first ISH entry (firmware ID
|
||||
# 0x13c) and streams its payload. PSP, not the host resolver, selects the
|
||||
# destination partition.
|
||||
offset = struct.unpack_from('<I', image, 0x12000 + 0x10)[0]
|
||||
size = struct.unpack_from('<I', image, 0x12000 + 0x18)[0]
|
||||
else:
|
||||
raise ValueError(f"IFWI resolver does not yet support requested item {item_type:#x}")
|
||||
payload = image[offset:offset+size]
|
||||
if len(payload) != size: raise ValueError(f"item {item_type:#x} extends beyond IFWI")
|
||||
print(f"resolved requested item {item_type:#x}: offset={offset:#x} size={size:#x}")
|
||||
return offset, payload
|
||||
|
||||
|
||||
class LivePSPFlash:
|
||||
"""Linux psp_v13_0_update_spirom protocol, used with SOS and trained VRAM."""
|
||||
def __init__(self, pci_dev): self.mailbox = PSPFlashMailbox(pci_dev)
|
||||
|
||||
def command(self, command: int, data: int | None = None, timeout: float = 10.0):
|
||||
# Same C2PMSG registers, but the live PSP command set uses IDs 2/3/4.
|
||||
return self.mailbox.require(command, data, timeout=timeout, name=f"LIVE_SPI_{command:#x}")
|
||||
|
||||
def update(self, mc_address: int):
|
||||
status = self.mailbox.mmio.read32(COMMAND)
|
||||
if not status & 0x80000000: raise RuntimeError(f"live PSP mailbox is not ready: {status:#x}")
|
||||
self.command(LIVE_ADDR_LO, mc_address & 0xffffffff)
|
||||
self.command(LIVE_ADDR_HI, mc_address >> 32)
|
||||
self.command(LIVE_UPDATE, timeout=60.0)
|
||||
|
||||
|
||||
def open_mailbox(args): return PSPFlashMailbox(open_gpu(args.device, args.transport))
|
||||
|
||||
|
||||
def reject_unvalidated_firmware_write():
|
||||
raise RuntimeError("firmware writes are disabled: stock reflash validation failed; use romless.py for recovery")
|
||||
|
||||
|
||||
def cmd_probe(args):
|
||||
result = open_mailbox(args).probe()
|
||||
for name, (error, response) in result.items(): print(f"{name}: error={error:#x} response={response:#x}")
|
||||
if result['transfer_type'][0] == 0xA: print("update commands gated: reset card and do not initialize AMDev/SOS", file=sys.stderr)
|
||||
|
||||
|
||||
def cmd_stream(args):
|
||||
if not args.yes: raise RuntimeError("refusing to stream without --yes")
|
||||
reject_unvalidated_firmware_write()
|
||||
payload = Path(args.image).read_bytes()
|
||||
open_mailbox(args).stream(payload, args.item_type)
|
||||
|
||||
|
||||
def cmd_ifwi_step(args):
|
||||
if not args.yes: raise RuntimeError("refusing to stream without --yes")
|
||||
reject_unvalidated_firmware_write()
|
||||
image = Path(args.ifwi).read_bytes()
|
||||
if len(image) != 0x200000: raise ValueError("Navi31 IFWI image must be exactly 2 MiB")
|
||||
mailbox = open_mailbox(args)
|
||||
state = mailbox.require(GET_TRANSFER_TYPE, name="GET_TRANSFER_TYPE")
|
||||
request = state & 0xff
|
||||
_, payload = resolve_ifwi_item(image, request)
|
||||
mailbox.stream(payload, request, transfer_type=state)
|
||||
next_request = mailbox.require(GET_TRANSFER_TYPE, name="GET_TRANSFER_TYPE")
|
||||
print(f"next firmware transfer_type={next_request:#x}")
|
||||
|
||||
|
||||
def cmd_ifwi_all(args):
|
||||
if not args.yes: raise RuntimeError("refusing to stream without --yes")
|
||||
image = Path(args.ifwi).read_bytes()
|
||||
if len(image) != 0x200000: raise ValueError("Navi31 IFWI image must be exactly 2 MiB")
|
||||
mailbox = open_mailbox(args)
|
||||
current = mailbox.require(GET_TRANSFER_TYPE, name="GET_TRANSFER_TYPE")
|
||||
for step in range(19): # Navi31 ROMItemCount from AMDVBFlash ASICDetails.xml
|
||||
request, phase = current & 0xff, current >> 8
|
||||
print(f"IFWI step {step}: state={current:#x} item={request:#x} phase={phase}", flush=True)
|
||||
_, payload = resolve_ifwi_item(image, request)
|
||||
mailbox.stream(payload, request, transfer_type=current)
|
||||
# AMDVBFlash tests the high byte belonging to the item just streamed. Phase
|
||||
# 2 terminates the loop only after that item has completed successfully.
|
||||
if phase == 2:
|
||||
print(f"IFWI stream complete after terminal state {current:#x}; hard power cycle required")
|
||||
return
|
||||
current = mailbox.require(GET_TRANSFER_TYPE, name="GET_TRANSFER_TYPE")
|
||||
raise RuntimeError(f"IFWI stream did not reach terminal phase after 19 items (state={current:#x})")
|
||||
|
||||
|
||||
def cmd_live_flash(args):
|
||||
if not args.yes: raise RuntimeError("refusing to flash without --yes")
|
||||
reject_unvalidated_firmware_write()
|
||||
image = Path(args.ifwi).read_bytes()
|
||||
if not image or len(image) > 16 * 1024 * 1024 or len(image) & 3:
|
||||
raise ValueError("live PSP image must be non-empty, 4-byte aligned, and at most 16 MiB")
|
||||
pci_dev = open_gpu(args.device, args.transport)
|
||||
from tinygrad.runtime.support.am.amdev import AMDev
|
||||
started = time.monotonic()
|
||||
adev = AMDev(pci_dev)
|
||||
print(f"AMDev booted, SOS alive={adev.psp.is_sos_alive()}", flush=True)
|
||||
paddr = adev.mm.palloc(len(image), align=0x1000, zero=False)
|
||||
try:
|
||||
adev.vram.view(paddr, len(image), 'B')[:] = image
|
||||
adev.gmc.flush_hdp()
|
||||
mc_address = adev.paddr2mc(paddr)
|
||||
print(f"staged IFWI at VRAM paddr={paddr:#x} mc={mc_address:#x}", flush=True)
|
||||
LivePSPFlash(pci_dev).update(mc_address)
|
||||
print(f"live PSP flash update complete in {time.monotonic()-started:.1f}s")
|
||||
finally:
|
||||
adev.mm.pfree(paddr)
|
||||
|
||||
|
||||
def cmd_dump(args):
|
||||
import hashlib
|
||||
pci_dev = open_gpu(args.device, args.transport)
|
||||
mmio, output, started = MMIO(pci_dev), bytearray(), time.monotonic()
|
||||
original_cntl, original_index = mmio.read32(ROM_CNTL), mmio.read32(ROM_INDEX)
|
||||
if original_cntl == 0xFFFFFFFF:
|
||||
raise RuntimeError("raw SMUIO ROM controller is unavailable; the SOC15 function-ROM aperture is not a raw SPI dump")
|
||||
try:
|
||||
# ROM_DATA must be read one dword at a time; a block read increments MMIO
|
||||
# addresses rather than repeatedly reading the flash aperture register.
|
||||
mmio.write32(ROM_CNTL, bswap32(original_cntl | (1 << 29)))
|
||||
for page in range(0, FLASH_SIZE, INDEX_PAGE):
|
||||
mmio.write32(ROM_INDEX, bswap32(page >> 8))
|
||||
for _ in range(INDEX_PAGE // 4): output += struct.pack('<I', mmio.read32(ROM_DATA))
|
||||
print(f"{page+INDEX_PAGE:#08x}/{FLASH_SIZE:#08x}", flush=True)
|
||||
finally:
|
||||
mmio.write32(ROM_INDEX, bswap32(original_index))
|
||||
mmio.write32(ROM_CNTL, bswap32(original_cntl))
|
||||
if len(output) != FLASH_SIZE or output[:4] != b'\xaa\x55\xaa\x55':
|
||||
raise RuntimeError(f"invalid raw flash dump: size={len(output):#x} magic={output[:4].hex()}")
|
||||
if output[:FLASH_SIZE//2] == output[FLASH_SIZE//2:]:
|
||||
raise RuntimeError("ROM aperture contains mirrored 1 MiB halves; refusing to write a non-raw 2 MiB dump")
|
||||
Path(args.output).write_bytes(output)
|
||||
print(f"dumped {len(output):#x} bytes in {time.monotonic()-started:.1f}s sha256={hashlib.sha256(output).hexdigest()}")
|
||||
|
||||
|
||||
def parser():
|
||||
p = argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument('--device', type=int, default=0, help='device index for the selected transport')
|
||||
p.add_argument('--transport', choices=('auto', 'usb', 'pci'), default='auto', help='PCIe transport (default: USB first, then native PCI)')
|
||||
sub = p.add_subparsers(dest='command', required=True)
|
||||
sub.add_parser('probe', help='query firmware mailbox state without writing').set_defaults(func=cmd_probe)
|
||||
|
||||
s = sub.add_parser('stream', help='stream one exact PSP ROM-item payload')
|
||||
s.add_argument('item_type', type=lambda x:int(x, 0))
|
||||
s.add_argument('image')
|
||||
s.add_argument('--yes', action='store_true')
|
||||
s.set_defaults(func=cmd_stream)
|
||||
|
||||
v = sub.add_parser('ifwi-step', help='resolve and stream the next early-firmware-requested item from a 2 MiB IFWI')
|
||||
v.add_argument('ifwi')
|
||||
v.add_argument('--yes', action='store_true')
|
||||
v.set_defaults(func=cmd_ifwi_step)
|
||||
|
||||
a = sub.add_parser('ifwi-all', help='stream requested IFWI items until firmware reports completion')
|
||||
a.add_argument('ifwi')
|
||||
a.add_argument('--yes', action='store_true')
|
||||
a.set_defaults(func=cmd_ifwi_all)
|
||||
|
||||
l = sub.add_parser('live-flash', help='stage an image in VRAM and invoke the PSP v13 live-update command')
|
||||
l.add_argument('ifwi')
|
||||
l.add_argument('--yes', action='store_true')
|
||||
l.set_defaults(func=cmd_live_flash)
|
||||
|
||||
d = sub.add_parser('dump', help='dump the exact 2 MiB flash through ROM_INDEX/ROM_DATA')
|
||||
d.add_argument('output')
|
||||
d.set_defaults(func=cmd_dump)
|
||||
return p
|
||||
|
||||
|
||||
def main():
|
||||
args = parser().parse_args()
|
||||
try: args.func(args)
|
||||
except (RuntimeError, TimeoutError, ValueError, OSError) as error:
|
||||
print(f"error: {error}", file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
if __name__ == '__main__': main()
|
||||
Executable
+249
@@ -0,0 +1,249 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Direct Navi31 ROM_SW access for GD25LQ16E-class 2 MiB SPI flash."""
|
||||
from __future__ import annotations
|
||||
import argparse, hashlib, sys, time
|
||||
from pathlib import Path
|
||||
from common import MMIO, open_gpu, wait_until
|
||||
|
||||
FLASH_SIZE, SECTOR_SIZE, PAGE_SIZE, MAX_DATA = 0x200000, 0x1000, 0x100, 0x100
|
||||
ROM_CNTL, PAGE_MIRROR_CNTL = 0x5A380, 0x5A384
|
||||
ROM_SW_CNTL, ROM_SW_STATUS, ROM_SW_COMMAND, ROM_SW_DATA = 0x5A3A0, 0x5A3A4, 0x5A3A8, 0x5A3B0
|
||||
GPIO_PAD_MASK, GPIO_PAD_A, GPIO_PAD_EN = 0x5A504, 0x5A508, 0x5A510
|
||||
SPI_GPIO_BITS, RETURN_DATA_EN = 0x780, 0x80000
|
||||
EXPECTED_JEDEC = b'\xc8\x60\x15'
|
||||
|
||||
|
||||
class Navi31SPI:
|
||||
def __init__(self, pci_dev, prescale: int = 8):
|
||||
if not 0 <= prescale <= 15: raise ValueError("prescale must be 0..15")
|
||||
self.mmio = MMIO(pci_dev)
|
||||
rc = self.mmio.read32(ROM_CNTL)
|
||||
# Select the prescaler instead of inheriting a potentially unusable BL value.
|
||||
self.mmio.write32(ROM_CNTL, (rc & 0xE0FFFFFF) | (1 << 28) | (prescale << 24) | 1)
|
||||
|
||||
def transfer(self, opcode: int, *, address: int = 0, address_len: int = 0,
|
||||
data_out: bytes = b'', data_in: int = 0, timeout: float = 2.0) -> bytes:
|
||||
if data_out and data_in: raise ValueError("simultaneous TX and RX is unsupported")
|
||||
if not 0 <= address_len <= 3: raise ValueError("address_len must be 0..3")
|
||||
count = len(data_out) if data_out else data_in
|
||||
if not 0 <= count <= MAX_DATA: raise ValueError(f"transfer data must be <= {MAX_DATA} bytes")
|
||||
ncmd = 1 + address_len
|
||||
m = self.mmio
|
||||
gpio_mask, gpio_a, gpio_en = m.read32(GPIO_PAD_MASK), m.read32(GPIO_PAD_A), m.read32(GPIO_PAD_EN)
|
||||
page_mirror, rom_cntl = m.read32(PAGE_MIRROR_CNTL), m.read32(ROM_CNTL)
|
||||
try:
|
||||
m.write32(GPIO_PAD_MASK, gpio_mask & ~SPI_GPIO_BITS)
|
||||
m.write32(GPIO_PAD_A, gpio_a & ~SPI_GPIO_BITS)
|
||||
m.write32(GPIO_PAD_EN, gpio_en & ~SPI_GPIO_BITS)
|
||||
m.write32(PAGE_MIRROR_CNTL, (page_mirror & 0xF1FFFFFF) | 0x06000000)
|
||||
m.write32(ROM_CNTL, (rom_cntl & ~0xF) | 8)
|
||||
m.write32(ROM_SW_CNTL, 0)
|
||||
m.write32(ROM_SW_STATUS, 0)
|
||||
if m.read32(ROM_SW_STATUS) != 0: raise RuntimeError("ROM_SW_STATUS did not clear")
|
||||
|
||||
# Navi31 serializes the low instruction byte first, followed by ADDRESS[23:0].
|
||||
m.write32(ROM_SW_COMMAND, ((address & 0xFFFFFF) << 8) | (opcode & 0xFF))
|
||||
for offset in range(0, len(data_out), 4):
|
||||
word = data_out[offset:offset+4].ljust(4, b'\0')
|
||||
m.write32(ROM_SW_DATA + offset, int.from_bytes(word, 'big'))
|
||||
|
||||
control = ((ncmd - 1) << 16) | (RETURN_DATA_EN if data_in else 0) | count
|
||||
m.write32(ROM_SW_CNTL, control)
|
||||
m.read32(ROM_SW_CNTL) # posted-write flush
|
||||
wait_until(lambda: m.read32(ROM_SW_STATUS) & 1, timeout,
|
||||
f"ROM_SW transaction timeout (status={m.read32(ROM_SW_STATUS):#x}); engine may be gated after SOS boot")
|
||||
return m.read(ROM_SW_DATA, (data_in + 3) & ~3)[:data_in] if data_in else b''
|
||||
finally:
|
||||
m.write32(ROM_SW_CNTL, 0)
|
||||
m.write32(ROM_SW_STATUS, 0)
|
||||
m.write32(ROM_CNTL, rom_cntl)
|
||||
m.write32(PAGE_MIRROR_CNTL, page_mirror)
|
||||
m.write32(GPIO_PAD_A, gpio_a)
|
||||
m.write32(GPIO_PAD_EN, gpio_en)
|
||||
m.write32(GPIO_PAD_MASK, gpio_mask)
|
||||
|
||||
|
||||
class GD25LQ16E:
|
||||
def __init__(self, spi: Navi31SPI): self.spi = spi
|
||||
|
||||
def read_register(self, opcode: int, count: int = 1) -> bytes:
|
||||
# Navi31 exposes the preceding transaction's RX capture. Prime identically.
|
||||
self.spi.transfer(opcode, data_in=max(2, count))
|
||||
return self.spi.transfer(opcode, data_in=count)
|
||||
|
||||
def status(self, opcode: int = 0x05) -> int: return self.read_register(opcode)[0]
|
||||
def rdid(self) -> bytes: return self.read_register(0x9F, 4)
|
||||
|
||||
def sfdp(self, count: int = 20) -> bytes:
|
||||
# 5Ah has one dummy byte after its 24-bit address; retain it for diagnostics.
|
||||
self.spi.transfer(0x5A, address_len=3, data_in=count)
|
||||
return self.spi.transfer(0x5A, address_len=3, data_in=count)
|
||||
|
||||
def wait_idle(self, timeout: float = 2.0) -> int:
|
||||
end = time.monotonic() + timeout
|
||||
while time.monotonic() < end:
|
||||
sr1 = self.status()
|
||||
if not sr1 & 1: return sr1
|
||||
time.sleep(0.002)
|
||||
raise TimeoutError(f"flash remained busy for {timeout}s")
|
||||
|
||||
def write_enable(self):
|
||||
self.spi.transfer(0x06)
|
||||
sr1 = self.status()
|
||||
if not sr1 & 2: raise RuntimeError(f"WREN failed (SR1={sr1:#04x})")
|
||||
|
||||
def clear_cmp(self):
|
||||
sr1, sr2 = self.status(), self.status(0x35)
|
||||
if not sr2 & 0x40: return False
|
||||
self.write_enable()
|
||||
# BUSY/WEL are not writable; preserve all protection/QE fields except CMP.
|
||||
self.spi.transfer(0x01, data_out=bytes((sr1 & 0xFC, sr2 & ~0x40)))
|
||||
self.wait_idle(1.0)
|
||||
new_sr2 = self.status(0x35)
|
||||
if new_sr2 & 0x40: raise RuntimeError(f"failed to clear CMP (SR2={new_sr2:#04x})")
|
||||
return True
|
||||
|
||||
def erase_sector(self, address: int):
|
||||
if address & (SECTOR_SIZE - 1): raise ValueError("sector address is not 4 KiB aligned")
|
||||
self.write_enable()
|
||||
self.spi.transfer(0x20, address=address, address_len=3)
|
||||
self.wait_idle(2.0)
|
||||
|
||||
def program_page(self, address: int, data: bytes):
|
||||
if not data or len(data) > PAGE_SIZE or (address & 0xFF) + len(data) > PAGE_SIZE:
|
||||
raise ValueError("page program crosses a 256-byte boundary")
|
||||
self.write_enable()
|
||||
self.spi.transfer(0x02, address=address, address_len=3, data_out=data)
|
||||
self.wait_idle(1.0)
|
||||
|
||||
def read(self, address: int, count: int) -> bytes:
|
||||
if address < 0 or count < 0 or address + count > FLASH_SIZE: raise ValueError("read outside 2 MiB flash")
|
||||
output = bytearray()
|
||||
while count:
|
||||
size = min(count, MAX_DATA)
|
||||
self.spi.transfer(0x03, address=address, address_len=3, data_in=size)
|
||||
output += self.spi.transfer(0x03, address=address, address_len=3, data_in=size)
|
||||
address, count = address + size, count - size
|
||||
return bytes(output)
|
||||
|
||||
|
||||
def has_jedec(raw: bytes) -> bool:
|
||||
return EXPECTED_JEDEC in raw + raw[:2]
|
||||
|
||||
|
||||
def open_flash(args) -> GD25LQ16E:
|
||||
flash = GD25LQ16E(Navi31SPI(open_gpu(args.device, 'usb'), args.prescale))
|
||||
raw = flash.rdid()
|
||||
if not has_jedec(raw): raise RuntimeError(f"unexpected GD25LQ16E JEDEC capture: {raw.hex()}")
|
||||
return flash
|
||||
|
||||
|
||||
def cmd_info(args):
|
||||
f = open_flash(args)
|
||||
sr1, sr2, sr3 = f.status(), f.status(0x35), f.status(0x15)
|
||||
sfdp = f.sfdp(24)
|
||||
pos = sfdp.find(b'SFDP')
|
||||
print(f"JEDEC capture: {f.rdid().hex()} (C8 60 15 detected)")
|
||||
print(f"SR1/SR2/SR3: {sr1:02x}/{sr2:02x}/{sr3:02x} CMP={'set' if sr2 & 0x40 else 'clear'}")
|
||||
print(f"SFDP capture: {sfdp.hex()} signature_offset={pos}")
|
||||
|
||||
|
||||
def cmd_read(args):
|
||||
data = open_flash(args).read(args.address, args.size)
|
||||
if args.output: Path(args.output).write_bytes(data)
|
||||
else: print(data.hex())
|
||||
|
||||
|
||||
def cmd_dump(args):
|
||||
f = open_flash(args)
|
||||
out = Path(args.output)
|
||||
digest = hashlib.sha256()
|
||||
with out.open('wb') as file:
|
||||
for address in range(0, FLASH_SIZE, SECTOR_SIZE):
|
||||
data = f.read(address, SECTOR_SIZE)
|
||||
file.write(data)
|
||||
digest.update(data)
|
||||
if not (address & 0xFFFF): print(f"{address + SECTOR_SIZE:#08x}/{FLASH_SIZE:#08x}", flush=True)
|
||||
print(f"wrote {out} sha256={digest.hexdigest()}")
|
||||
|
||||
|
||||
def cmd_verify(args):
|
||||
expected = Path(args.image).read_bytes()
|
||||
if len(expected) != FLASH_SIZE: raise ValueError(f"image must be exactly {FLASH_SIZE:#x} bytes")
|
||||
f = open_flash(args)
|
||||
digest = hashlib.sha256()
|
||||
for address in range(0, FLASH_SIZE, SECTOR_SIZE):
|
||||
got, wanted = f.read(address, SECTOR_SIZE), expected[address:address+SECTOR_SIZE]
|
||||
digest.update(got)
|
||||
if got != wanted:
|
||||
index = next(i for i, (a, b) in enumerate(zip(got, wanted)) if a != b)
|
||||
raise RuntimeError(f"verify mismatch at {address+index:#x}: flash={got[index]:02x} image={wanted[index]:02x}")
|
||||
print(f"verified {FLASH_SIZE:#x} bytes sha256={digest.hexdigest()}")
|
||||
|
||||
|
||||
def cmd_flash(args):
|
||||
if not args.yes: raise RuntimeError("refusing to write without --yes")
|
||||
image = Path(args.image).read_bytes()
|
||||
if len(image) != FLASH_SIZE: raise ValueError(f"image must be exactly {FLASH_SIZE:#x} bytes")
|
||||
total_sectors = FLASH_SIZE // SECTOR_SIZE
|
||||
start, count = args.start_sector, args.sector_count if args.sector_count is not None else total_sectors - args.start_sector
|
||||
if not 0 <= start < total_sectors or not 1 <= count <= total_sectors - start: raise ValueError("invalid sector range")
|
||||
f = open_flash(args)
|
||||
if f.status(0x35) & 0x40:
|
||||
if not args.clear_cmp: raise RuntimeError("CMP protects the full array; rerun with --clear-cmp")
|
||||
f.clear_cmp()
|
||||
print("cleared SR2.CMP", flush=True)
|
||||
begin = time.monotonic()
|
||||
for sector in range(start, start + count):
|
||||
address = sector * SECTOR_SIZE
|
||||
wanted = image[address:address+SECTOR_SIZE]
|
||||
f.erase_sector(address)
|
||||
for offset in range(0, SECTOR_SIZE, PAGE_SIZE):
|
||||
page = wanted[offset:offset+PAGE_SIZE]
|
||||
if page != b'\xff' * PAGE_SIZE: f.program_page(address + offset, page)
|
||||
got = f.read(address, SECTOR_SIZE)
|
||||
if got != wanted:
|
||||
index = next(i for i, (a, b) in enumerate(zip(got, wanted)) if a != b)
|
||||
raise RuntimeError(f"verify mismatch at {address+index:#x}: flash={got[index]:02x} image={wanted[index]:02x}")
|
||||
print(f"OK sector {sector:03d}/{total_sectors-1} @{address:#07x} elapsed={time.monotonic()-begin:.1f}s", flush=True)
|
||||
|
||||
|
||||
def parser():
|
||||
p = argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument('--device', type=int, default=0, help='USB bridge device index')
|
||||
p.add_argument('--prescale', type=int, default=8, help='SCK prescaler 0..15 (default: 8)')
|
||||
sub = p.add_subparsers(dest='command', required=True)
|
||||
sub.add_parser('info', help='read JEDEC, status and SFDP').set_defaults(func=cmd_info)
|
||||
|
||||
r = sub.add_parser('read', help='read a flash range')
|
||||
r.add_argument('address', type=lambda x:int(x, 0))
|
||||
r.add_argument('size', type=lambda x:int(x, 0))
|
||||
r.add_argument('-o', '--output')
|
||||
r.set_defaults(func=cmd_read)
|
||||
|
||||
d = sub.add_parser('dump', help='dump the complete 2 MiB flash')
|
||||
d.add_argument('output')
|
||||
d.set_defaults(func=cmd_dump)
|
||||
|
||||
v = sub.add_parser('verify', help='compare the complete flash with an image')
|
||||
v.add_argument('image')
|
||||
v.set_defaults(func=cmd_verify)
|
||||
|
||||
w = sub.add_parser('flash', help='erase, program, and verify one or more sectors')
|
||||
w.add_argument('image')
|
||||
w.add_argument('--start-sector', type=lambda x:int(x, 0), default=0)
|
||||
w.add_argument('--sector-count', type=lambda x:int(x, 0))
|
||||
w.add_argument('--clear-cmp', action='store_true')
|
||||
w.add_argument('--yes', action='store_true')
|
||||
w.set_defaults(func=cmd_flash)
|
||||
return p
|
||||
|
||||
|
||||
def main():
|
||||
args = parser().parse_args()
|
||||
try: args.func(args)
|
||||
except (RuntimeError, TimeoutError, ValueError, OSError) as error:
|
||||
print(f"error: {error}", file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
if __name__ == '__main__': main()
|
||||
@@ -66,6 +66,8 @@ class AMSMI(AMDev):
|
||||
def __init__(self, pcibus, vram_bar:MMIOInterface, doorbell_bar:MMIOInterface, mmio_bar:MMIOInterface):
|
||||
self.pcibus, self.devfmt = pcibus, pcibus
|
||||
self.vram, self.doorbell64, self.mmio = vram_bar, doorbell_bar, mmio_bar
|
||||
self.is_vf = bool(self.mmio[am.mmRCC_IOV_FUNC_IDENTIFIER] & 1)
|
||||
self.vf_rlc_gated:list[tuple[int, int]] = []
|
||||
self.pci_state = self.read_pci_state()
|
||||
if self.pci_state == "D0": self._init_from_d0()
|
||||
|
||||
@@ -84,7 +86,8 @@ class AMSMI(AMDev):
|
||||
with open(f"/sys/bus/pci/devices/{self.pcibus}/power_state", "r") as f: return f.read().strip().rstrip()
|
||||
|
||||
class SMICtx:
|
||||
def __init__(self):
|
||||
def __init__(self, dev_filter=None):
|
||||
self.dev_filter = dev_filter
|
||||
self.devs = []
|
||||
self.opened_pcidevs = []
|
||||
self.opened_pci_resources = {}
|
||||
@@ -135,6 +138,7 @@ class SMICtx:
|
||||
pattern = os.path.join('/tmp', 'am_*.lock')
|
||||
for d in [f[8:-5] for f in glob.glob(pattern)]:
|
||||
if d.startswith("usb"): continue
|
||||
if self.dev_filter is not None and d != self.dev_filter: continue
|
||||
if d not in self.opened_pcidevs:
|
||||
self._open_am_device(d)
|
||||
|
||||
@@ -406,7 +410,7 @@ if __name__ == "__main__":
|
||||
|
||||
try:
|
||||
if not args.list: os.system('clear')
|
||||
smi_ctx = SMICtx()
|
||||
smi_ctx = SMICtx(args.dev)
|
||||
while True:
|
||||
smi_ctx.rescan_devs()
|
||||
smi_ctx.draw(args.list)
|
||||
|
||||
+9
-9
@@ -35,7 +35,7 @@ class WallTimeEvent:
|
||||
return self
|
||||
def __exit__(self, *_):
|
||||
self.time = time.monotonic() - self.start
|
||||
_events[self.event]["wall"].append(self.time)
|
||||
_events[self.event]["wall"].append((self.time, BENCHMARK_LOG.value))
|
||||
return False
|
||||
|
||||
class KernelTimeEvent:
|
||||
@@ -47,19 +47,19 @@ class KernelTimeEvent:
|
||||
self.start = GlobalCounters.time_sum_s
|
||||
return self
|
||||
def __exit__(self, *_):
|
||||
_events[self.event]["kernel"].append(GlobalCounters.time_sum_s - self.start)
|
||||
_events[self.event]["kernel"].append((GlobalCounters.time_sum_s - self.start, BENCHMARK_LOG.value))
|
||||
return False
|
||||
|
||||
def log_event_instant(event:InstantBenchEvent, value:float):
|
||||
_events[event].append(value)
|
||||
_events[event].append((value, BENCHMARK_LOG.value))
|
||||
|
||||
if BENCHMARK_LOG:
|
||||
INFLUXDB_HOST = getenv("INFLUXDB_HOST", "")
|
||||
INFLUXDB_ORG = getenv("INFLUXDB_ORG", "tiny")
|
||||
INFLUXDB_TOKEN = getenv("INFLUXDB_TOKEN", "")
|
||||
|
||||
def _create_point(run_id, i, attempt, ref, commit, name, value, run):
|
||||
point = Point(BENCHMARK_LOG.value).tag("id", run_id).tag("index", i)
|
||||
def _create_point(run_id, i, attempt, ref, commit, name, value, log_name, run):
|
||||
point = Point(log_name.replace(':', '_').replace('.', '_')).tag("id", run_id).tag("index", i)
|
||||
point = point.tag("device", Device.DEFAULT)
|
||||
point = point.tag("attempt", attempt).tag("ref", ref).tag("commit", commit)
|
||||
point = point.field(name, value).field("x", run)
|
||||
@@ -91,12 +91,12 @@ if BENCHMARK_LOG:
|
||||
run_id = str(uuid.uuid4())
|
||||
if isinstance(event, BenchEvent):
|
||||
for event_type, values in _events[event].items():
|
||||
for i, value in enumerate(values):
|
||||
point = _create_point(run_id, i, attempt, ref, commit, f"{event.value}_{event_type}", value, run)
|
||||
for i, (value, log_name) in enumerate(values):
|
||||
point = _create_point(run_id, i, attempt, ref, commit, f"{event.value}_{event_type}", value, log_name, run)
|
||||
points.append(point)
|
||||
else:
|
||||
for i, value in enumerate(_events[event]):
|
||||
point = _create_point(run_id, i, attempt, ref, commit, event.value, value, run)
|
||||
for i, (value, log_name) in enumerate(_events[event]):
|
||||
point = _create_point(run_id, i, attempt, ref, commit, event.value, value, log_name, run)
|
||||
points.append(point)
|
||||
|
||||
write_options = WriteOptions(write_type=WriteType.synchronous, retry_interval=5000, max_retries=5, max_retry_delay=30000, exponential_base=2)
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import argparse, time
|
||||
from tinygrad.llm.model import Transformer
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--model", required=True, help="path to gguf model")
|
||||
parser.add_argument("--max-context", type=int, default=8192, help="max context length (default: %(default)s)")
|
||||
parser.add_argument("--prompt-tokens", type=int, default=1024, help="number of prompt tokens (default: %(default)s)")
|
||||
parser.add_argument("--decode-tokens", type=int, default=16, help="number of tokens to decode (default: %(default)s)")
|
||||
parser.add_argument("--chunk-size", type=int, default=32, help="chunk size for prefill (default: %(default)s)")
|
||||
args = parser.parse_args()
|
||||
|
||||
st = time.perf_counter()
|
||||
model, _ = Transformer.from_gguf(args.model, args.max_context)
|
||||
print(f"load {time.perf_counter()-st:.3f}s", flush=True)
|
||||
|
||||
st = time.perf_counter()
|
||||
model.warmup()
|
||||
print(f"warm {time.perf_counter()-st:.3f}s", flush=True)
|
||||
|
||||
prompt = [257] + [1000+i%1000 for i in range(args.prompt_tokens-1)]
|
||||
gen = model.generate(prompt, chunk_size=args.chunk_size)
|
||||
st = time.perf_counter()
|
||||
# first token is time-to-first-token; counted as part of prefill
|
||||
output = [next(gen)]
|
||||
pt = time.perf_counter()
|
||||
print(f"prefill {args.prompt_tokens/(pt-st):.3f} tok/s", flush=True)
|
||||
|
||||
for _ in range(args.decode_tokens): output.append(next(gen))
|
||||
et = time.perf_counter()
|
||||
print(f"decode {args.decode_tokens/(et-pt):.3f} tok/s output {output}", flush=True)
|
||||
@@ -0,0 +1,238 @@
|
||||
import ctypes, struct
|
||||
from tinygrad.helpers import ceildiv, getenv, wait_cond, DEBUG
|
||||
from tinygrad.runtime.autogen import bnxt, pci
|
||||
from tinygrad.runtime.support.system import PCIDevice, System, ipv4_to_gid
|
||||
|
||||
BNXT_DEBUG = getenv("BNXT_DEBUG", 0)
|
||||
BNXT_ACCESS, BNXT_INIT_MASK, BNXT_RTR_MASK, BNXT_RTS_MASK = 3, 0xd, 0x41515ad, 0xae005
|
||||
BNXT_CHIMP_COMM, BNXT_CHIMP_COMM_TRIGGER = 0x0, 0x100
|
||||
BNXT_BACKING_STORE = ((0, 2), (1, 0), (2, 2), (3, 0), (4, 2), (5, 0), (6, 0), (14, 2), (15, 0))
|
||||
|
||||
def db_value(xid, typ, index, epoch):
|
||||
return (xid & bnxt.DBC_DBC_XID_MASK | bnxt.DBC_DBC_PATH_ROCE | typ | bnxt.BNXT_QPLIB_DBR_VALID) << 32 | \
|
||||
index & bnxt.DBC_DBC_INDEX_MASK | epoch << bnxt.BNXT_QPLIB_DBR_EPOCH_SHIFT
|
||||
|
||||
def _pbl(dev, paddrs, queue=False):
|
||||
if len(paddrs) == 1: return 0, paddrs[0]
|
||||
values = [p | bnxt.PTU_PTE_VALID for p in paddrs]
|
||||
if queue:
|
||||
values[-1] |= bnxt.PTU_PTE_LAST
|
||||
if len(values) > 1: values[-2] |= bnxt.PTU_PTE_NEXT_TO_LAST
|
||||
table, table_paddrs = dev.pci_dev.alloc_sysmem(ceildiv(len(values), 512) * 0x1000)
|
||||
table[:len(values) * 8] = struct.pack(f"<{len(values)}Q", *values)
|
||||
if len(table_paddrs) == 1: return 1, table_paddrs[0]
|
||||
top, top_paddrs = dev.pci_dev.alloc_sysmem(0x1000)
|
||||
top[:len(table_paddrs) * 8] = struct.pack(f"<{len(table_paddrs)}Q", *(p | bnxt.PTU_PTE_VALID for p in table_paddrs))
|
||||
return 2, top_paddrs[0]
|
||||
|
||||
def _queue(dev, stride:int=16, aux=False):
|
||||
mem, paddrs = dev.pci_dev.alloc_sysmem(0x1000 + aux * 0x400)
|
||||
level, base = _pbl(dev, paddrs, queue=True)
|
||||
return {"mem":mem, "paddrs":paddrs, "stride":stride, "prod":0, "cons":0, "level":level, "base":base}
|
||||
|
||||
def _qread(q, i):
|
||||
off = (i & 15) * q["stride"]
|
||||
return q["mem"][off:off + q["stride"]]
|
||||
|
||||
def _qwrite(q, i, data, aux=False):
|
||||
off = 0x1000 + i % 128 * 8 if aux else (i & 15) * q["stride"]
|
||||
q["mem"][off:off + len(data)] = data
|
||||
|
||||
class BNXTDev:
|
||||
def __init__(self, pci_dev:PCIDevice, ip:str=getenv("BNXT_IP", "10.0.0.1")):
|
||||
self.pci_dev, self.devfmt = pci_dev, pci_dev.pcibus
|
||||
self.bar0, self.db = pci_dev.map_bar(0, fmt='I'), pci_dev.map_bar(2, fmt='Q')
|
||||
pci_dev.write_config(pci.PCI_COMMAND, pci_dev.read_config(pci.PCI_COMMAND, 2) | pci.PCI_COMMAND_MASTER, 2)
|
||||
self.resp, self.resp_pa = pci_dev.alloc_sysmem(0x1000)
|
||||
self.seq = 0
|
||||
|
||||
ver = self.hwrm("ver_get")
|
||||
if DEBUG >= 2: print(f"bnxt {self.devfmt}: firmware {ver.hwrm_fw_maj_8b}.{ver.hwrm_fw_min_8b}.{ver.hwrm_fw_bld_8b}")
|
||||
self.hwrm("func_reset", timeout_ms=40000)
|
||||
caps = self.hwrm("func_qcaps", fid=0xffff)
|
||||
self.mac, self.port_id = int.from_bytes(bytes(caps.mac_address), 'big'), caps.port_id
|
||||
self.hwrm("func_drv_rgtr")
|
||||
self.db_off = self.hwrm("func_qcfg", fid=0xffff).legacy_l2_db_size_kb * 1024
|
||||
|
||||
self.setup_backing_store()
|
||||
self._open_rcfw()
|
||||
self._open_l2()
|
||||
self.local_gid = ipv4_to_gid(ip)
|
||||
gids, mac = (ctypes.c_uint32 * 4)(*(int.from_bytes(self.local_gid[i:i + 4], 'big') for i in (12, 8, 4, 0))), self.mac.to_bytes(6, 'big')
|
||||
smac = (ctypes.c_uint16 * 3)(*(int.from_bytes(mac[i:i + 2], 'big') for i in (0, 2, 4)))
|
||||
self.gid_id = self.rcfw("add_gid", gid=gids, src_mac=smac).xid
|
||||
|
||||
if DEBUG >= 2: print(f"bnxt {self.devfmt}: booted mac={self.mac.to_bytes(6, 'big').hex(':')} gid={self.local_gid.hex()}")
|
||||
|
||||
def hwrm(self, name, timeout_ms=10000, **fields):
|
||||
inp, out = getattr(bnxt, f"struct_hwrm_{name}_input"), getattr(bnxt, f"struct_hwrm_{name}_output")
|
||||
opcode = getattr(bnxt, f"HWRM_{name.upper()}")
|
||||
self.seq = (self.seq + 1) & 0xffff
|
||||
data = bytes(inp(req_type=opcode, cmpl_ring=bnxt.BNXT_HWRM_NO_CMPL_RING, seq_id=self.seq, target_id=bnxt.BNXT_HWRM_TARGET,
|
||||
resp_addr=self.resp_pa[0], **fields))
|
||||
self.resp[:] = bytes(len(self.resp))
|
||||
System.memory_barrier()
|
||||
for i, w in enumerate(memoryview(bytearray(data.ljust(bnxt.HWRM_MAX_REQ_LEN, b'\0'))).cast('I')):
|
||||
self.bar0[BNXT_CHIMP_COMM // 4 + i] = w
|
||||
self.bar0[BNXT_CHIMP_COMM_TRIGGER // 4] = 1
|
||||
def hdr(): return bnxt.struct_hwrm_resp_hdr.from_buffer_copy(bytes(self.resp[:8]))
|
||||
wait_cond(lambda: (n := hdr().resp_len) and hdr().seq_id == self.seq and self.resp[n - 1], timeout_ms=timeout_ms, msg=f"HWRM {name}")
|
||||
ret = out.from_buffer_copy(bytes(self.resp[:ctypes.sizeof(out)]))
|
||||
assert ret.error_code == 0, f"HWRM {name}: {ret.error_code}"
|
||||
return ret
|
||||
|
||||
def setup_backing_store(self):
|
||||
counts: dict[int, int] = {}
|
||||
for typ, extra in BNXT_BACKING_STORE:
|
||||
caps = self.hwrm("func_backing_store_qcaps_v2", type=typ)
|
||||
size, splits = caps.entry_size, tuple(getattr(caps, f"split_entry_{j}") for j in range(caps.subtype_valid_cnt))
|
||||
counts[typ] = n = counts[0] if typ == 15 else max(caps.min_num_entries, sum(splits) + extra)
|
||||
# a zero bitmap means the type has a single instance 0
|
||||
for instance in [i for i in range(8) if caps.instance_bit_map >> i & 1] or [0]:
|
||||
mem, paddrs = self.pci_dev.alloc_sysmem(ceildiv(n * size, 0x1000) * 0x1000)
|
||||
if caps.ctx_init_value:
|
||||
for off in range(caps.ctx_init_offset, len(mem), size): mem[off] = caps.ctx_init_value
|
||||
lvl, base = _pbl(self, paddrs)
|
||||
self.hwrm("func_backing_store_cfg_v2", type=typ, instance=instance, entry_size=size, num_entries=n, page_dir=base,
|
||||
page_size_pbl_level=lvl, subtype_valid_cnt=len(splits),
|
||||
flags=bnxt.FUNC_BACKING_STORE_CFG_V2_REQ_FLAGS_BS_CFG_ALL_DONE if typ == 15 else 0,
|
||||
**{f"split_entry_{j}": v for j, v in enumerate(splits)})
|
||||
|
||||
def _open_rcfw(self):
|
||||
self.rcfw_first = True
|
||||
|
||||
self.creq = _queue(self)
|
||||
self.creq_id = self.hwrm("ring_alloc", ring_type=bnxt.RING_ALLOC_REQ_RING_TYPE_NQ, page_tbl_addr=self.creq["base"],
|
||||
page_size=12, page_tbl_depth=self.creq["level"], length=16, int_mode=bnxt.RING_ALLOC_REQ_INT_MODE_MSIX).ring_id
|
||||
|
||||
self.cmdq = _queue(self)
|
||||
self.doorbell(self.creq_id, bnxt.DBC_DBC_TYPE_NQ_ARM, 0, 0)
|
||||
init = bnxt.struct_cmdq_init(cmdq_pbl=self.cmdq["base"], creq_ring_id=self.creq_id,
|
||||
cmdq_size_cmdq_lvl=16 << bnxt.CMDQ_INIT_CMDQ_SIZE_SFT)
|
||||
|
||||
System.memory_barrier()
|
||||
for i, w in enumerate(memoryview(bytearray(bytes(init))).cast('I')): self.bar0[bnxt.RCFW_COMM_BASE_OFFSET // 4 + i] = w
|
||||
|
||||
_, p = self.pci_dev.alloc_sysmem(0x1000)
|
||||
self.rcfw("initialize_fw", stat_ctx_id=self.hwrm("stat_ctx_alloc", stats_dma_addr=p[0], stats_dma_length=176).stat_ctx_id,
|
||||
flags=bnxt.CMDQ_INITIALIZE_FW_FLAGS_HW_REQUESTER_RETX_SUPPORTED)
|
||||
|
||||
# RoCE notification ring: never armed or serviced, but CQ and L2 ring allocation require one
|
||||
nq = _queue(self)
|
||||
self.nq_id = self.hwrm("ring_alloc", ring_type=bnxt.RING_ALLOC_REQ_RING_TYPE_NQ, page_tbl_addr=nq["base"],
|
||||
page_size=12, page_tbl_depth=nq["level"], length=16, logical_id=1, int_mode=bnxt.RING_ALLOC_REQ_INT_MODE_MSIX).ring_id
|
||||
|
||||
def rcfw(self, name, timeout_ms=20000, **fields):
|
||||
req_t, resp_t = getattr(bnxt, f"struct_cmdq_{name}"), getattr(bnxt, f"struct_creq_{name}_resp")
|
||||
op = getattr(bnxt, f"CMDQ_BASE_OPCODE_{name.upper()}")
|
||||
data = bytes(req_t(opcode=op, cmd_size=(slots := ceildiv(ctypes.sizeof(req_t), 16)), **fields)).ljust(slots * 16, b'\0')
|
||||
for i in range(slots): _qwrite(self.cmdq, self.cmdq["prod"] + i, data[i * 16:(i + 1) * 16])
|
||||
|
||||
self.cmdq["prod"] += slots
|
||||
prod = self.cmdq["prod"] & 0xffff
|
||||
if self.rcfw_first: prod, self.rcfw_first = prod | 1 << bnxt.FIRMWARE_FIRST_FLAG, False
|
||||
|
||||
System.memory_barrier()
|
||||
|
||||
self.bar0[(bnxt.RCFW_COMM_BASE_OFFSET + bnxt.RCFW_PF_VF_COMM_PROD_OFFSET) // 4] = prod
|
||||
self.bar0[(bnxt.RCFW_COMM_BASE_OFFSET + bnxt.RCFW_COMM_TRIG_OFFSET) // 4] = bnxt.RCFW_CMDQ_TRIG_VAL
|
||||
|
||||
def poll():
|
||||
h = bnxt.struct_creq_base.from_buffer_copy(bytes(_qread(self.creq, self.creq["cons"])))
|
||||
return bool(h.v & bnxt.CREQ_BASE_V) != bool((self.creq["cons"] // 16) & 1)
|
||||
wait_cond(poll, timeout_ms=timeout_ms, msg=f"RCFW {name}")
|
||||
|
||||
ret = resp_t.from_buffer_copy(bytes(_qread(self.creq, self.creq["cons"])))
|
||||
self.creq["cons"] += 1
|
||||
|
||||
# NQ_ARM also publishes the CREQ consumer index, which is what frees ring space for the next command
|
||||
self.doorbell(self.creq_id, bnxt.DBC_DBC_TYPE_NQ_ARM, self.creq["cons"] & 15, (self.creq["cons"] // 16) & 1)
|
||||
assert ret.status == 0, f"RCFW {name}: {ret.status}"
|
||||
|
||||
if BNXT_DEBUG >= 1: print(f"bnxt {self.devfmt}: rcfw {name} xid={getattr(ret, 'xid', 0):#x}")
|
||||
return ret
|
||||
|
||||
def doorbell(self, xid, typ, index, epoch):
|
||||
System.memory_barrier()
|
||||
self.db[self.db_off // 8] = db_value(xid, typ, index, epoch)
|
||||
|
||||
# L2 receive path, required for RoCE ingress even though no ethernet receive buffers are posted
|
||||
def _open_l2(self):
|
||||
cq = _queue(self)
|
||||
ci = self.hwrm("ring_alloc", enables=bnxt.RING_ALLOC_REQ_ENABLES_NQ_RING_ID_VALID, ring_type=bnxt.RING_ALLOC_REQ_RING_TYPE_L2_CMPL,
|
||||
page_tbl_addr=cq["base"], page_size=12, page_tbl_depth=cq["level"], length=16, nq_ring_id=self.nq_id).ring_id
|
||||
rx = _queue(self)
|
||||
ri = self.hwrm("ring_alloc", enables=bnxt.RING_ALLOC_REQ_ENABLES_NQ_RING_ID_VALID |
|
||||
bnxt.RING_ALLOC_REQ_ENABLES_RX_BUF_SIZE_VALID, ring_type=bnxt.RING_ALLOC_REQ_RING_TYPE_RX, page_tbl_addr=rx["base"],
|
||||
page_size=12, page_tbl_depth=rx["level"], length=16, rx_buf_size=640, nq_ring_id=self.nq_id).ring_id
|
||||
vi = self.hwrm("vnic_alloc").vnic_id
|
||||
self.hwrm("vnic_cfg", enables=bnxt.VNIC_CFG_REQ_ENABLES_MRU | bnxt.VNIC_CFG_REQ_ENABLES_DEFAULT_RX_RING_ID |
|
||||
bnxt.VNIC_CFG_REQ_ENABLES_DEFAULT_CMPL_RING_ID, vnic_id=vi, mru=9018,
|
||||
default_rx_ring_id=ri, default_cmpl_ring_id=ci)
|
||||
self.hwrm("cfa_l2_filter_alloc", flags=bnxt.CFA_L2_FILTER_ALLOC_REQ_FLAGS_PATH_RX,
|
||||
enables=bnxt.CFA_L2_FILTER_ALLOC_REQ_ENABLES_L2_ADDR | bnxt.CFA_L2_FILTER_ALLOC_REQ_ENABLES_L2_ADDR_MASK |
|
||||
bnxt.CFA_L2_FILTER_ALLOC_REQ_ENABLES_DST_ID, l2_addr=tuple(self.mac.to_bytes(6, 'big')), l2_addr_mask=(0xff,) * 6, dst_id=vi)
|
||||
|
||||
def register_mem(self, paddrs:list[int], size:int, log_page_size:int=12) -> int:
|
||||
level, base = _pbl(self, paddrs[:ceildiv(size, 1 << log_page_size)])
|
||||
return self.rcfw("register_mr", flags=bnxt.CMDQ_REGISTER_MR_FLAGS_ALLOC_MR,
|
||||
log2_pg_size_lvl=level << bnxt.CMDQ_REGISTER_MR_LVL_SFT | log_page_size << bnxt.CMDQ_REGISTER_MR_LOG2_PG_SIZE_SFT,
|
||||
access=bnxt.CMDQ_REGISTER_MR_ACCESS_LOCAL_WRITE | bnxt.CMDQ_REGISTER_MR_ACCESS_REMOTE_WRITE,
|
||||
log2_pbl_pg_size=12, pbl=base, va=paddrs[0], mr_size=size).xid
|
||||
|
||||
class BNXTQP:
|
||||
def __init__(self, dev:BNXTDev):
|
||||
self.dev, self.sq_psn, self.msn = dev, 0, 0
|
||||
|
||||
self.cqq = _queue(dev, ctypes.sizeof(bnxt.struct_cq_base))
|
||||
self.cq_id = dev.rcfw("create_cq", cq_size=16, pbl=self.cqq["base"],
|
||||
pg_size_lvl=self.cqq["level"], cq_fco_cnq_id=dev.nq_id).xid
|
||||
|
||||
self.sq = _queue(dev, aux=True)
|
||||
self.qpn = dev.rcfw("create_qp", type=bnxt.CMDQ_CREATE_QP_TYPE_RC,
|
||||
sq_size=16, sq_fwo_sq_sge=1, scq_cid=self.cq_id, rcq_cid=self.cq_id,
|
||||
sq_pbl=self.sq["base"], sq_pg_size_sq_lvl=self.sq["level"]).xid
|
||||
self.qp_op(1, BNXT_INIT_MASK, access=BNXT_ACCESS, pkey=0xffff)
|
||||
|
||||
def qp_op(self, state, mask, network_type=0, **fields):
|
||||
self.dev.rcfw("modify_qp", qp_cid=self.qpn, modify_mask=mask,
|
||||
network_type_en_sqd_async_notify_new_state=state | network_type, **fields)
|
||||
|
||||
def connect(self, qpn:int, gid:bytes, mac:int):
|
||||
network_type = bnxt.CMDQ_MODIFY_QP_NETWORK_TYPE_ROCEV2_IPV4
|
||||
dgid = (ctypes.c_uint32 * 4)(*(int.from_bytes(gid[i:i + 4], 'little') for i in (0, 4, 8, 12)))
|
||||
dmac = (ctypes.c_uint16 * 3)(*(int.from_bytes(mac.to_bytes(6, 'big')[i:i + 2], 'little') for i in (0, 2, 4)))
|
||||
|
||||
self.qp_op(2, BNXT_RTR_MASK, network_type=network_type, qp_type=bnxt.CMDQ_MODIFY_QP_QP_TYPE_RC, access=BNXT_ACCESS,
|
||||
pkey=0xffff, dgid=dgid, sgid_index=self.dev.gid_id, hop_limit=64, dest_mac=dmac,
|
||||
path_mtu_pingpong_push_enable=bnxt.CMDQ_MODIFY_QP_PATH_MTU_MTU_1024, max_dest_rd_atomic=4,
|
||||
dest_qp_id=qpn)
|
||||
self.qp_op(3, BNXT_RTS_MASK, network_type=network_type, qp_type=bnxt.CMDQ_MODIFY_QP_QP_TYPE_RC, access=BNXT_ACCESS,
|
||||
max_rd_atomic=1)
|
||||
|
||||
if BNXT_DEBUG >= 1: print(f"bnxt: QP {self.qpn:#x} connected (remote={qpn:#x})")
|
||||
|
||||
def _poll(self, timeout):
|
||||
def poll():
|
||||
base = bnxt.struct_cq_base.from_buffer_copy(bytes(_qread(self.cqq, self.cqq["cons"])))
|
||||
return bool(base.cqe_type_toggle & bnxt.CQ_BASE_TOGGLE) == (not bool((self.cqq["cons"] // 16) & 1))
|
||||
wait_cond(poll, timeout_ms=timeout, msg="BNXT CQ")
|
||||
raw = bytes(_qread(self.cqq, self.cqq["cons"]))
|
||||
self.cqq["cons"] += 1
|
||||
self.dev.doorbell(self.cq_id, bnxt.DBC_DBC_TYPE_CQ, self.cqq["cons"] & 15, (self.cqq["cons"] // 16) & 1)
|
||||
return raw
|
||||
|
||||
def rdma_write(self, rva, rkey, lva, lkey, size, timeout_ms=20000):
|
||||
start = self.sq["prod"] & 15
|
||||
hdr = bytes(bnxt.struct_sq_rdma_hdr(wqe_type=bnxt.SQ_RDMA_HDR_WQE_TYPE_WRITE_WQE,
|
||||
flags=bnxt.SQ_SEND_FLAGS_SIGNAL_COMP, wqe_size=3, length=size, remote_va=rva, remote_key=rkey))
|
||||
for i, data in enumerate((hdr[:16], hdr[16:32], bytes(bnxt.struct_sq_sge(va_or_pa=lva, l_key=lkey, size=size)))):
|
||||
_qwrite(self.sq, start + i, data)
|
||||
nxt = (self.sq_psn + max(1, ceildiv(size, 1024))) & 0xffffff
|
||||
value = start << bnxt.SQ_MSN_SEARCH_START_IDX_SFT | nxt << bnxt.SQ_MSN_SEARCH_NEXT_PSN_SFT | self.sq_psn
|
||||
_qwrite(self.sq, self.msn, struct.pack("<Q", value), aux=True)
|
||||
|
||||
self.msn, self.sq_psn, self.sq["prod"] = (self.msn + 1) % 128, nxt, self.sq["prod"] + 3
|
||||
self.dev.doorbell(self.qpn, bnxt.DBC_DBC_TYPE_SQ, self.sq["prod"] & 15, (self.sq["prod"] // 16) & 1)
|
||||
cqe = bnxt.struct_cq_req.from_buffer_copy(self._poll(timeout_ms))
|
||||
assert cqe.status == 0
|
||||
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Send and validate one RDMA WRITE between two Broadcom BNXT hosts.
|
||||
|
||||
This follows ``extra/mlx_driver/connect.py``: sync the driver, start the remote
|
||||
endpoint over SSH, exchange QP/GID/MAC/MR metadata, move both RC QPs to RTS,
|
||||
write bytes into the remote MR, and verify the bytes on the remote host.
|
||||
|
||||
Both PCI functions must be unbound from bnxt_en/bnxt_re first.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Any, IO
|
||||
|
||||
TINYGRAD = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../.."))
|
||||
sys.path.insert(0, TINYGRAD)
|
||||
|
||||
from extra.bnxt_driver.bnxtdev import BNXTDev, BNXTQP
|
||||
from tinygrad.runtime.support.system import PCIDevice
|
||||
|
||||
REMOTE_HOST = os.getenv("REMOTE_HOST", "192.168.52.213")
|
||||
REMOTE_USER = os.getenv("REMOTE_USER", "nimlgen")
|
||||
LOCAL_PCI = os.getenv("BNXT_PCI", "0000:41:00.0")
|
||||
REMOTE_PCI = os.getenv("REMOTE_PCI", "0000:41:00.0")
|
||||
LOCAL_IP = os.getenv("LOCAL_IP", "10.0.200.5")
|
||||
REMOTE_IP = os.getenv("REMOTE_IP", "10.0.200.6")
|
||||
MESSAGE = os.getenv("RDMA_MESSAGE", "Test message, rdma works!").encode()
|
||||
REMOTE = f"{REMOTE_USER}@{REMOTE_HOST}"
|
||||
SSH = ["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10", "-o", "StrictHostKeyChecking=accept-new", REMOTE]
|
||||
SYNC_FILES = ("tinygrad/runtime/autogen/bnxt.py", "tinygrad/runtime/support/system.py",
|
||||
"extra/bnxt_driver/bnxtdev.py", "extra/bnxt_driver/connect.py")
|
||||
|
||||
def read_json(stream:IO[str], what:str) -> dict[str, Any]:
|
||||
for line in iter(stream.readline, ""):
|
||||
print(f" [remote] {line}", end="")
|
||||
try: value = json.loads(line)
|
||||
except json.JSONDecodeError: continue
|
||||
if isinstance(value, dict): return value
|
||||
raise RuntimeError(f"remote exited before publishing {what}")
|
||||
|
||||
def wait_line(stream:IO[str], text:str) -> str:
|
||||
for line in iter(stream.readline, ""):
|
||||
print(f" [remote] {line}", end="")
|
||||
if text in line: return line
|
||||
raise RuntimeError(f"remote exited before reporting {text!r}")
|
||||
|
||||
def send_line(stream:IO[str], value:str|dict[str, Any]):
|
||||
stream.write((json.dumps(value) if isinstance(value, dict) else value) + "\n")
|
||||
stream.flush()
|
||||
|
||||
def qp_info(dev:BNXTDev, qp:BNXTQP) -> dict[str, Any]:
|
||||
return {"qpn":qp.qpn, "mac":dev.mac.to_bytes(6, "big").hex(), "gid":dev.local_gid.hex()}
|
||||
|
||||
def server():
|
||||
dev = BNXTDev(PCIDevice("bnxt", os.getenv("BNXT_PCI", "0000:41:00.0")), ip=os.getenv("BNXT_IP", REMOTE_IP))
|
||||
qp = BNXTQP(dev)
|
||||
print(json.dumps(qp_info(dev, qp)), flush=True)
|
||||
|
||||
peer = json.loads(sys.stdin.readline())
|
||||
qp.connect(peer["qpn"], bytes.fromhex(peer["gid"]), int(peer["mac"], 16))
|
||||
print("connected", flush=True)
|
||||
|
||||
target, target_paddrs = dev.pci_dev.alloc_sysmem(0x1000)
|
||||
target[:0x1000] = bytes(0x1000)
|
||||
rkey = dev.register_mem(target_paddrs, 0x1000)
|
||||
print(json.dumps({"target_addr":target_paddrs[0], "rkey":rkey}), flush=True)
|
||||
|
||||
assert sys.stdin.readline().strip() == "done"
|
||||
received = bytes(target).rstrip(b"\0")
|
||||
print(f"AS TEXT: {received.decode(errors='replace')!r}", flush=True)
|
||||
print(json.dumps({"data":received.hex()}), flush=True)
|
||||
|
||||
def sync_remote():
|
||||
if os.getenv("SYNC", "1") == "0": return
|
||||
print("syncing BNXT driver to remote")
|
||||
subprocess.run(["rsync", "-azR", *SYNC_FILES, f"{REMOTE}:~/tinygrad/"], cwd=TINYGRAD, check=True)
|
||||
|
||||
def start_remote() -> subprocess.Popen[str]:
|
||||
print("booting remote")
|
||||
command = (f"cd ~/tinygrad && sudo env PYTHONPATH=. PYTHONUNBUFFERED=1 BNXT_DEBUG={os.getenv('BNXT_DEBUG', '0')} "
|
||||
f"BNXT_PCI={REMOTE_PCI} BNXT_IP={REMOTE_IP} python3 extra/bnxt_driver/connect.py --server")
|
||||
return subprocess.Popen(SSH + [command], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=sys.stderr, text=True)
|
||||
|
||||
def client():
|
||||
assert 0 < len(MESSAGE) <= 0x1000
|
||||
sync_remote()
|
||||
remote = start_remote()
|
||||
assert remote.stdin is not None and remote.stdout is not None
|
||||
remote_info = read_json(remote.stdout, "QP information")
|
||||
print("booting local")
|
||||
dev = BNXTDev(PCIDevice("bnxt", LOCAL_PCI), ip=LOCAL_IP)
|
||||
qp = BNXTQP(dev)
|
||||
|
||||
send_line(remote.stdin, qp_info(dev, qp))
|
||||
wait_line(remote.stdout, "connected")
|
||||
qp.connect(remote_info["qpn"], bytes.fromhex(remote_info["gid"]), int(remote_info["mac"], 16))
|
||||
print("both QPs in RTS")
|
||||
|
||||
remote_target = read_json(remote.stdout, "MR information")
|
||||
source, source_paddrs = dev.pci_dev.alloc_sysmem(0x1000)
|
||||
source[:len(MESSAGE)] = MESSAGE
|
||||
lkey = dev.register_mem(source_paddrs, 0x1000)
|
||||
print(f"RDMA WRITE {len(MESSAGE)}B to remote phys 0x{remote_target['target_addr']:x}")
|
||||
qp.rdma_write(remote_target["target_addr"], remote_target["rkey"], source_paddrs[0], lkey, len(MESSAGE))
|
||||
|
||||
send_line(remote.stdin, "done")
|
||||
wait_line(remote.stdout, "AS TEXT")
|
||||
result = read_json(remote.stdout, "RDMA result")
|
||||
assert bytes.fromhex(result["data"]) == MESSAGE
|
||||
print("RDMA WRITE data verified")
|
||||
|
||||
remote.stdin.close()
|
||||
assert remote.wait() == 0
|
||||
print("RDMA WRITE test complete")
|
||||
|
||||
if __name__ == "__main__":
|
||||
server() if "--server" in sys.argv else client()
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Local BNXT RoCEv2 RDMA WRITE loopback using the firmware's PHY loopback mode.
|
||||
|
||||
The kernel bnxt_en/bnxt_re modules must be unloaded first.
|
||||
|
||||
sudo PYTHONPATH=. BNXT_PCI=0000:41:00.0 BNXT_IP=10.0.200.5 python3 extra/bnxt_driver/loopback.py
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../.."))
|
||||
|
||||
from extra.bnxt_driver.bnxtdev import BNXTDev, BNXTQP
|
||||
from tinygrad.runtime.autogen import bnxt
|
||||
from tinygrad.runtime.support.system import PCIDevice
|
||||
|
||||
BUF_SIZE = 0x1000
|
||||
BNXT_PCI = os.getenv("BNXT_PCI", "0000:41:00.0")
|
||||
BNXT_IP = os.getenv("BNXT_IP", "10.0.200.5")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"[init] BNXT at {BNXT_PCI}")
|
||||
dev = BNXTDev(PCIDevice("bnxt", BNXT_PCI), ip=BNXT_IP)
|
||||
tx_qp, rx_qp = BNXTQP(dev), BNXTQP(dev)
|
||||
print(f"[init] loopback-connect TX QP 0x{tx_qp.qpn:x} <-> RX QP 0x{rx_qp.qpn:x}")
|
||||
tx_qp.connect(rx_qp.qpn, dev.local_gid, dev.mac)
|
||||
rx_qp.connect(tx_qp.qpn, dev.local_gid, dev.mac)
|
||||
|
||||
src, src_paddrs = dev.pci_dev.alloc_sysmem(BUF_SIZE)
|
||||
dst, dst_paddrs = dev.pci_dev.alloc_sysmem(BUF_SIZE)
|
||||
message = b"Hello from BNXT RoCE PHY loopback!"
|
||||
src[:BUF_SIZE], dst[:BUF_SIZE] = bytes(BUF_SIZE), bytes(BUF_SIZE)
|
||||
src[:len(message)] = message
|
||||
lkey = dev.register_mem(src_paddrs, BUF_SIZE)
|
||||
rkey = dev.register_mem(dst_paddrs, BUF_SIZE)
|
||||
|
||||
print("[loopback] enabling local PHY loopback")
|
||||
dev.hwrm("port_phy_cfg", port_id=dev.port_id, enables=bnxt.PORT_PHY_CFG_REQ_ENABLES_LPBK, lpbk=bnxt.PORT_PHY_CFG_REQ_LPBK_LOCAL)
|
||||
time.sleep(1)
|
||||
tx_qp.rdma_write(dst_paddrs[0], rkey, src_paddrs[0], lkey, len(message))
|
||||
got = bytes(dst[:len(message)])
|
||||
print(f"[result] {got!r}")
|
||||
assert got == message
|
||||
print("BNXT RoCE PHY loopback RDMA WRITE passed")
|
||||
dev.hwrm("port_phy_cfg", port_id=dev.port_id, enables=bnxt.PORT_PHY_CFG_REQ_ENABLES_LPBK, lpbk=bnxt.PORT_PHY_CFG_REQ_LPBK_NONE)
|
||||
@@ -35,7 +35,7 @@ def compile_net(linear:UOp, output_bufs:List[Buffer]) -> Tuple[Dict[str,str], Li
|
||||
return name
|
||||
|
||||
for call in iter_kernel_calls(linear):
|
||||
arg_uops = [b for b in call.src[1:] if b.op is not Ops.BIND]
|
||||
arg_uops = [b for b in call.src[1:] if not b.is_bound_var]
|
||||
prg = to_program(call.src[0], Device[arg_uops[0].device].renderer)
|
||||
info = prg.arg
|
||||
functions[info.function_name] = prg.src[2].arg
|
||||
@@ -241,8 +241,7 @@ export default {model_name};
|
||||
def export_model(model, target:str, *inputs, model_name: Optional[str] = "model", stream_weights=False):
|
||||
assert Device.DEFAULT in EXPORT_SUPPORTED_DEVICE, f"only {', '.join(EXPORT_SUPPORTED_DEVICE)} are supported"
|
||||
|
||||
# NOTE: NUM_CPU_THREADS=1, since export does not support threading
|
||||
with Context(JIT=2, NUM_CPU_THREADS=1): linear, output_bufs = jit_model(model, *inputs)
|
||||
with Context(JIT=2): linear, output_bufs = jit_model(model, *inputs)
|
||||
functions, statements, bufs, bufs_to_save = compile_net(linear, output_bufs)
|
||||
state = get_state_dict(model)
|
||||
weight_names = {(id(b), b.offset, b.size, b.dtype): name for name, x in state.items() if (b:=x.uop.base.realized) is not None}
|
||||
|
||||
@@ -462,7 +462,7 @@ def test_matmul():
|
||||
lds = UOp.placeholder((lds_size,), dtypes.uint8, 0, AddrSpace.LOCAL)
|
||||
sink = UOp.sink(A.base, B.base, C.base, lds, *gidxs, *lidxs, arg=KernelInfo(name=colored("kernel", "cyan"),
|
||||
estimates=Estimates(ops=N*N*N*2, mem=N*N*4*3)))
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple([UOp(Ops.INS, arg=x) for x in insts]))))
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple([UOp(Ops.INS, arg=(x, dtypes.void)) for x in insts]))))
|
||||
c = Tensor.custom_kernel(a, b, c, fxn=asm_kernel)[2]
|
||||
linear = c.schedule_linear()
|
||||
|
||||
|
||||
@@ -1,203 +0,0 @@
|
||||
from tinygrad import Tensor, UOp, getenv
|
||||
from tinygrad.uop.ops import AxisType, KernelInfo, Ops
|
||||
from tinygrad.dtype import AddrSpace, dtypes
|
||||
from tinygrad.helpers import DEBUG, GlobalCounters, Context
|
||||
import math
|
||||
|
||||
BLOCK_M, BLOCK_N = 64, 64
|
||||
WARP_SIZE = 32
|
||||
WMMA_M, WMMA_N, WMMA_K = 16, 16, 16
|
||||
WAVES_M, WAVES_N = 4, 1
|
||||
LANES_PER_WAVE_M, LANES_PER_WAVE_N = 2, 16
|
||||
WMMA_ACC = WMMA_M // LANES_PER_WAVE_M
|
||||
THREADS_PER_BLOCK = WARP_SIZE * WAVES_M * WAVES_N
|
||||
LDS_PAD = 4 # pad LDS rows to reduce bank conflicts
|
||||
|
||||
WMMA_ARG = (WMMA_M, WMMA_N, WMMA_K), 'AMD', 32
|
||||
LOG2E = math.log2(math.e)
|
||||
|
||||
def warp_shfl_xor(val, offset, lane):
|
||||
"""Read val from lane ^ offset using ds_bpermute."""
|
||||
idx = ((lane ^ offset) * 4).cast(dtypes.int)
|
||||
if val.op is Ops.INDEX and val.addrspace == AddrSpace.REG: val = val.load()
|
||||
return UOp(Ops.CUSTOM, dtypes.float, (idx, val),
|
||||
arg="__builtin_bit_cast(float, __builtin_amdgcn_ds_bpermute({0}, __builtin_bit_cast(int, {1})))")
|
||||
|
||||
def warp_reduce_max(val, lane):
|
||||
"""Tree reduce MAX across LANES_PER_WAVE_N=16 lanes."""
|
||||
for offset in [8, 4, 2, 1]:
|
||||
val = UOp(Ops.MAX, dtypes.float, (val, warp_shfl_xor(val, offset, lane)))
|
||||
return val
|
||||
|
||||
def warp_reduce_sum(val, lane):
|
||||
"""Tree reduce SUM across LANES_PER_WAVE_N=16 lanes."""
|
||||
for offset in [8, 4, 2, 1]:
|
||||
val = val + warp_shfl_xor(val, offset, lane)
|
||||
return val
|
||||
|
||||
def amd_flash_attention(o:UOp, q:UOp, k:UOp, v:UOp) -> UOp:
|
||||
# inputs are (B*H, N, D)
|
||||
BH, N, D = q.shape
|
||||
assert N % BLOCK_M == 0 and N % BLOCK_N == 0, f"N={N} must be divisible by BLOCK_M={BLOCK_M} and BLOCK_N={BLOCK_N}"
|
||||
assert D % WMMA_K == 0 and D % LANES_PER_WAVE_N == 0, f"D={D} must be divisible by WMMA_K={WMMA_K} and LANES_PER_WAVE_N={LANES_PER_WAVE_N}"
|
||||
assert BLOCK_M % (WAVES_M * WMMA_M) == 0 and BLOCK_N % LANES_PER_WAVE_N == 0
|
||||
TM = BLOCK_M // (WAVES_M * LANES_PER_WAVE_M)
|
||||
TN = BLOCK_N // (WAVES_N * LANES_PER_WAVE_N)
|
||||
TD = D // (WAVES_N * LANES_PER_WAVE_N)
|
||||
SCALE = 1.0 / math.sqrt(D)
|
||||
|
||||
block_bh = UOp.range(BH, 0, AxisType.GLOBAL)
|
||||
block_m = UOp.range(N // BLOCK_M, 1, AxisType.GLOBAL)
|
||||
|
||||
q = q.reshape(BH, N//BLOCK_M, BLOCK_M, D)[block_bh, block_m]
|
||||
k = k.reshape(BH, N//BLOCK_N, BLOCK_N, D)[block_bh]
|
||||
v = v.reshape(BH, N//BLOCK_N, BLOCK_N, D)[block_bh]
|
||||
o = o.reshape(BH, N//BLOCK_M, BLOCK_M, D)[block_bh, block_m]
|
||||
|
||||
wave_m = UOp.range(WAVES_M, 2, AxisType.LOCAL)
|
||||
wave_n = UOp.range(WAVES_N, 3, AxisType.LOCAL)
|
||||
lane = UOp.range(WARP_SIZE, -1, AxisType.WARP)
|
||||
tid = (wave_m * WAVES_N + wave_n) * WARP_SIZE + lane
|
||||
lane_m = lane // LANES_PER_WAVE_N
|
||||
lane_n = lane % LANES_PER_WAVE_N
|
||||
|
||||
# LDS allocation: slot 0 = Q then P (shared), slot 1 = K then V
|
||||
# TODO: the memory planner should be able to find this reuse
|
||||
ELEMS_PER_THREAD = BLOCK_M * D // THREADS_PER_BLOCK
|
||||
QP_lds = UOp.placeholder((BLOCK_M, D + LDS_PAD), dtypes.half, slot=0, addrspace=AddrSpace.LOCAL)
|
||||
KV_lds = UOp.placeholder((BLOCK_N, D + LDS_PAD), dtypes.half, slot=1, addrspace=AddrSpace.LOCAL)[:, :D]
|
||||
|
||||
# register state
|
||||
acc = UOp.placeholder((TM, TD), dtypes.float, slot=2, addrspace=AddrSpace.REG)
|
||||
m_i = UOp.placeholder((TM,), dtypes.float, slot=3, addrspace=AddrSpace.REG)
|
||||
l_i = UOp.placeholder((TM,), dtypes.float, slot=4, addrspace=AddrSpace.REG)
|
||||
acc = acc.after(acc.store(acc.const_like(0)))
|
||||
m_i = m_i.after(m_i.store(m_i.const_like(-math.inf)))
|
||||
l_i = l_i.after(l_i.store(l_i.const_like(0)))
|
||||
|
||||
# ====== KV tile loop ======
|
||||
n_tile = UOp.range(N // BLOCK_N, 100, AxisType.REDUCE)
|
||||
|
||||
# load Q + K into LDS (Q reloaded each iteration since P overwrites slot 0)
|
||||
Q_lds = QP_lds[:, :D]
|
||||
Q_store = Q_lds.after(n_tile).reshape(THREADS_PER_BLOCK, ELEMS_PER_THREAD)[tid].store(
|
||||
q.reshape(THREADS_PER_BLOCK, ELEMS_PER_THREAD)[tid])
|
||||
K_store = KV_lds.reshape(THREADS_PER_BLOCK, ELEMS_PER_THREAD)[tid].store(
|
||||
k[n_tile].reshape(THREADS_PER_BLOCK, ELEMS_PER_THREAD)[tid])
|
||||
# NOTE: no explicit barrier needed, the AFTER on the LOCAL buffers implies it in late codegen
|
||||
Q_lds = Q_lds.after(UOp.group(Q_store, K_store))
|
||||
KV_lds_k = KV_lds.after(UOp.group(Q_store, K_store))
|
||||
|
||||
# -- S = Q @ K^T via WMMA (re-init each n_tile) --
|
||||
S_reg = UOp.placeholder((TM, TN), dtypes.float, slot=6, addrspace=AddrSpace.REG)
|
||||
S_reg = S_reg.after(S_reg.after(n_tile).store(S_reg.const_like(0)))
|
||||
k_qk = UOp.range(D // WMMA_K, 101, AxisType.REDUCE)
|
||||
tm1 = UOp.range(TM // WMMA_ACC, 200)
|
||||
tn1 = UOp.range(TN, 201)
|
||||
S_frag = S_reg.reshape(TM // WMMA_ACC, WMMA_ACC, TN).permute(0, 2, 1)[tm1, tn1]
|
||||
q_frag = Q_lds.reshape(WAVES_M, TM // WMMA_ACC, WMMA_M, D // WMMA_K, WMMA_K)[wave_m, tm1, lane_n, k_qk]
|
||||
k_frag = KV_lds_k.reshape(WAVES_N, TN, WMMA_N, D // WMMA_K, WMMA_K)[wave_n, tn1, lane_n, k_qk]
|
||||
qk = UOp.wmma(q_frag, k_frag, S_frag.after(k_qk), *WMMA_ARG)
|
||||
qk_done = S_frag.store(qk).end(tm1, tn1).end(k_qk)
|
||||
S_reg = S_reg.after(qk_done)
|
||||
|
||||
# -- softmax in registers with warp shuffles --
|
||||
S_reg = S_reg.after(S_reg.store(S_reg * SCALE))
|
||||
|
||||
# per-thread local row max over TN=4 elements, then warp reduce across 16 lanes
|
||||
m_ij = UOp.placeholder((TM,), dtypes.float, slot=7, addrspace=AddrSpace.REG)
|
||||
m_ij = m_ij.after(m_ij.after(n_tile).store(m_ij.const_like(-math.inf)))
|
||||
rm2 = UOp.range(TN, 261, AxisType.REDUCE)
|
||||
m_ij = m_ij.after(m_ij.store(m_ij.after(rm2).maximum(S_reg[:, rm2])).end(rm2))
|
||||
# warp reduce max (in-place)
|
||||
ri_w = UOp.range(TM, 270)
|
||||
m_ij = m_ij.after(m_ij[ri_w].store(warp_reduce_max(m_ij[ri_w], lane)).end(ri_w))
|
||||
|
||||
# compute P = exp(S - m_ij) in S_reg
|
||||
S_reg = S_reg.after(S_reg.store(((S_reg - m_ij.reshape(TM, 1).expand(TM, TN)) * LOG2E).exp2()))
|
||||
|
||||
p_local = UOp.placeholder((TM,), dtypes.float, slot=8, addrspace=AddrSpace.REG)
|
||||
p_local = p_local.after(p_local.after(n_tile).store(p_local.const_like(0)))
|
||||
rp2 = UOp.range(TN, 291, AxisType.REDUCE)
|
||||
p_local = p_local.after(p_local.store(p_local.after(rp2) + S_reg[:, rp2]).end(rp2))
|
||||
ri_ws = UOp.range(TM, 295)
|
||||
p_sum = p_local.after(p_local[ri_ws].store(warp_reduce_sum(p_local[ri_ws], lane)).end(ri_ws))
|
||||
|
||||
# write P = exp(S - m_ij) to P_lds (reuses slot 0, Q no longer needed)
|
||||
P_lds = QP_lds[:, :BLOCK_N]
|
||||
P_write = P_lds.reshape(WAVES_M, TM // WMMA_ACC, WMMA_ACC, LANES_PER_WAVE_M, WAVES_N, TN, LANES_PER_WAVE_N)
|
||||
P_write = P_write.permute((0, 4, 3, 6, 1, 2, 5)).reshape(THREADS_PER_BLOCK, TM, TN)
|
||||
P_store = P_write[tid].store(S_reg.cast(dtypes.half))
|
||||
|
||||
# -- online softmax correction --
|
||||
ri4 = UOp.range(TM, 330)
|
||||
m_new_val = m_i[ri4].maximum(m_ij[ri4])
|
||||
alpha_val = ((m_i[ri4] - m_new_val) * LOG2E).exp2()
|
||||
beta_val = ((m_ij[ri4] - m_new_val) * LOG2E).exp2()
|
||||
rj4 = UOp.range(TD, 331)
|
||||
correction = UOp.group(
|
||||
acc[ri4, rj4].store(alpha_val * acc[ri4, rj4]).end(rj4),
|
||||
l_i[ri4].store(alpha_val * l_i[ri4] + beta_val * p_sum[ri4]),
|
||||
m_i[ri4].store(m_new_val),
|
||||
).end(ri4)
|
||||
acc = acc.after(correction)
|
||||
l_i = l_i.after(correction)
|
||||
m_i = m_i.after(correction)
|
||||
|
||||
# load V into KV_lds (must wait for QK WMMA to finish reading K from KV_lds)
|
||||
V_store = KV_lds.after(qk_done).reshape(THREADS_PER_BLOCK, ELEMS_PER_THREAD)[tid].store(
|
||||
v[n_tile].reshape(THREADS_PER_BLOCK, ELEMS_PER_THREAD)[tid])
|
||||
# NOTE: no explicit barrier needed, the AFTER on the LOCAL buffers implies it in late codegen
|
||||
P_lds = P_lds.after(UOp.group(P_store, V_store))
|
||||
KV_lds_v = KV_lds.after(UOp.group(P_store, V_store))
|
||||
|
||||
# -- acc += P @ V via WMMA --
|
||||
k_pv = UOp.range(BLOCK_N // WMMA_K, 400, AxisType.REDUCE)
|
||||
tm2 = UOp.range(TM // WMMA_ACC, 401)
|
||||
tn2 = UOp.range(TD, 402)
|
||||
acc_frag = acc.reshape(TM // WMMA_ACC, WMMA_ACC, TD).permute(0, 2, 1)[tm2, tn2]
|
||||
p_frag = P_lds.reshape(WAVES_M, TM // WMMA_ACC, WMMA_M, BLOCK_N // WMMA_K, WMMA_K)[wave_m, tm2, lane_n, k_pv]
|
||||
v_frag = KV_lds_v.reshape(WAVES_N, TD, WMMA_N, BLOCK_N // WMMA_K, WMMA_K)[wave_n, tn2, lane_n, k_pv]
|
||||
pv = UOp.wmma(p_frag, v_frag, acc_frag.after(k_pv), *WMMA_ARG)
|
||||
|
||||
# end KV tile loop
|
||||
n_tile_end = acc_frag.store(pv).end(tm2, tn2).end(k_pv).end(n_tile)
|
||||
acc = acc.after(n_tile_end)
|
||||
l_i = l_i.after(n_tile_end)
|
||||
m_i = m_i.after(n_tile_end)
|
||||
|
||||
# normalize: acc /= l_i
|
||||
acc = acc.after(acc.store(acc * (1 / l_i).reshape(TM, 1).expand(TM, TD)))
|
||||
|
||||
# store output
|
||||
o = o.reshape(WAVES_M, TM // WMMA_ACC, WMMA_ACC, LANES_PER_WAVE_M, WAVES_N, TD, LANES_PER_WAVE_N)
|
||||
o = o.permute((0, 4, 3, 6, 1, 2, 5)).reshape(THREADS_PER_BLOCK, TM, TD)
|
||||
return o[tid].store(acc).end(wave_m, wave_n, lane).end(block_m, block_bh).sink(arg=KernelInfo(opts_to_apply=()))
|
||||
|
||||
if __name__ == "__main__":
|
||||
B, H, N, D = getenv("B", 1), getenv("H", 32), getenv("N", 1024), getenv("D", 64)
|
||||
q = Tensor.rand(B, H, N, D).cast(dtypes.half)
|
||||
k = Tensor.rand(B, H, N, D).cast(dtypes.half)
|
||||
v = Tensor.rand(B, H, N, D).cast(dtypes.half)
|
||||
o = Tensor.empty(B, H, N, D, dtype=dtypes.float)
|
||||
with Context(DEBUG=0): Tensor.realize(q, k, v)
|
||||
|
||||
q_flat, k_flat, v_flat, o_flat = q.reshape(B*H, N, D), k.reshape(B*H, N, D), v.reshape(B*H, N, D), o.reshape(B*H, N, D)
|
||||
NUM_RUNS = getenv("CNT", 5)
|
||||
ets = []
|
||||
with Context(DEBUG=2):
|
||||
for _ in range(NUM_RUNS):
|
||||
GlobalCounters.reset()
|
||||
tst = Tensor.custom_kernel(o_flat, q_flat, k_flat, v_flat, fxn=amd_flash_attention)[0].realize()
|
||||
ets.append(GlobalCounters.time_sum_s)
|
||||
print(f"best time: {min(ets)*1e3:.2f}ms")
|
||||
|
||||
if getenv("VERIFY", 1):
|
||||
with Context(DEBUG=0):
|
||||
ref = q.float().scaled_dot_product_attention(k.float(), v.float()).reshape(B*H, N, D).realize()
|
||||
err = (ref - tst).square().mean().item()
|
||||
print(f"mean squared error {err}")
|
||||
if err > 1e-2:
|
||||
raise RuntimeError("flash attention is wrong!")
|
||||
else:
|
||||
print("flash attention is correct!")
|
||||
+36
-64
@@ -6,6 +6,7 @@ from tinygrad.renderer import Estimates
|
||||
from tinygrad.helpers import getenv, all_same, DEBUG, ceildiv
|
||||
from tinygrad.runtime.support.compiler_amd import HIPCCCompiler
|
||||
from examples.mlperf.models.flat_llama import FP8_DTYPE, quantize_fp8
|
||||
from extra.llama_kernels.quantize_mxfp4 import quantize_mxfp4
|
||||
|
||||
TILE_M, TILE_N, TILE_K = 256, 256, 64
|
||||
|
||||
@@ -121,9 +122,29 @@ def custom_mxfp4_gemm(C:UOp, A:UOp, B:UOp, scale_a:UOp, scale_b:UOp, *extra:UOp,
|
||||
groups_x, groups_y = UOp.special(ceildiv(N, tile_n), "gidx0"), UOp.special(ceildiv(M, tile_m), "gidx1")
|
||||
lds = UOp.placeholder((163840,), dtypes.uint8, 0, AddrSpace.LOCAL)
|
||||
sink = UOp.sink(C.base, A.base, B.base, scale_a.base, scale_b.base, *(x.base for x in extra), lds, threads, groups_x, groups_y,
|
||||
arg=KernelInfo(f"custom_mxfp4_gemm_{M}_{N}_{K}", estimates=Estimates(ops=2*M*N*K)))
|
||||
arg=KernelInfo(f"mxfp4_gemm_{M}_{N}_{K}",
|
||||
estimates=Estimates(ops=2*M*N*K, mem=(M*half_k+N*half_k)*A.dtype.itemsize+M*N*C.dtype.itemsize)))
|
||||
insts = build_kernel(M, N, K, tile_m, tile_n)
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple(UOp(Ops.INS, arg=x) for x in insts))))
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple(UOp(Ops.INS, arg=(x, dtypes.void)) for x in insts))))
|
||||
|
||||
def _mxfp4_gemm_quantized(a_q:Tensor, b_q:Tensor, scale_a:Tensor, scale_b:Tensor) -> Tensor:
|
||||
M, half_k = a_q.shape
|
||||
N, half_k_b = b_q.shape
|
||||
assert half_k == half_k_b
|
||||
is_multi = isinstance(a_q.device, tuple)
|
||||
reduce_out = is_multi and (a_q.uop.axis == 1 or b_q.uop.axis == 1)
|
||||
if not is_multi: out = Tensor.invalids(1, M, N, dtype=dtypes.bfloat16, device=a_q.device)
|
||||
elif reduce_out: out = Tensor(Tensor.invalids(1, M, N, dtype=dtypes.bfloat16, device=a_q.device).uop.unshard(0), device=a_q.device)
|
||||
elif a_q.uop.axis == 0:
|
||||
out = Tensor(Tensor.invalids(1, M//len(a_q.device), N, dtype=dtypes.bfloat16, device=a_q.device).uop.unshard(1), device=a_q.device)
|
||||
elif b_q.uop.axis == 0:
|
||||
out = Tensor(Tensor.invalids(1, M, N//len(a_q.device), dtype=dtypes.bfloat16, device=a_q.device).uop.unshard(2), device=a_q.device)
|
||||
else: out = Tensor.invalids(1, M, N, dtype=dtypes.bfloat16, device=a_q.device)
|
||||
tile_m, tile_n = next((tm, tn) for tm, tn in ((256, 256), (192, 256), (128, 512)) if M % tm == N % tn == 0)
|
||||
out = Tensor.custom_kernel(out, a_q, b_q, scale_a, scale_b,
|
||||
fxn=functools.partial(custom_mxfp4_gemm, tile_m=tile_m, tile_n=tile_n))[0]
|
||||
if reduce_out: out = out.sum(0)
|
||||
return out.squeeze(0)
|
||||
|
||||
def quantize_mxfp8(x:Tensor) -> tuple[Tensor, Tensor, Tensor]:
|
||||
# 1x32 block scaling along the last axis
|
||||
@@ -137,50 +158,6 @@ def quantize_mxfp8(x:Tensor) -> tuple[Tensor, Tensor, Tensor]:
|
||||
packed = mx_pack(e8) if len(batch) == 1 and scale_K % 4 == 0 else None
|
||||
return x_clamped.cast(FP8_DTYPE), e8, packed
|
||||
|
||||
def _mxfp4_shuffle_weight(x:Tensor) -> Tensor:
|
||||
# shuffle_weight(x, layout=(16, 16)) on the packed uint8 buffer.
|
||||
if x.ndim == 3:
|
||||
ndev, rows, half_k = x.shape
|
||||
return x.reshape(ndev, rows//16, 16, half_k//32, 2, 16).permute(0, 1, 3, 4, 2, 5).reshape(ndev, rows, half_k).contiguous()
|
||||
rows, half_k = x.shape
|
||||
return x.reshape(rows//16, 16, half_k//32, 2, 16).permute(0, 2, 3, 1, 4).reshape(rows, half_k).contiguous()
|
||||
|
||||
def _mxfp4_shuffle_scales(x:Tensor) -> Tensor:
|
||||
# e8m0_shuffle: each 256x8 scale tile is arranged for the raw MFMA scale loads.
|
||||
if x.ndim == 3:
|
||||
ndev, rows, scale_k = x.shape
|
||||
return x.reshape(ndev, rows//32, 2, 16, scale_k//8, 2, 4).permute(0, 1, 4, 6, 3, 5, 2).reshape(ndev, rows, scale_k).contiguous()
|
||||
rows, scale_k = x.shape
|
||||
return x.reshape(rows//32, 2, 16, scale_k//8, 2, 4).permute(0, 3, 5, 2, 4, 1).reshape(rows, scale_k).contiguous()
|
||||
|
||||
def quantize_mxfp4(x:Tensor) -> tuple[Tensor, Tensor, Tensor]:
|
||||
# OCP MXFP4: 1x32 blocks, E2M1 values packed low-nibble first, and E8M0 scales.
|
||||
*batch, K = x.shape
|
||||
rows = math.prod(batch)
|
||||
assert x.ndim >= 2 and K % 256 == 0 and rows % 32 == 0, \
|
||||
f"mxfp4 quantization needs rows%32 and K%256, got {x.shape}"
|
||||
xb = x.float().reshape(*batch, K//32, 32)
|
||||
amax = xb.abs().max(axis=-1)
|
||||
|
||||
# even scale rounding: round the fp32 significand before choosing 2^(floor(log2)-2).
|
||||
amax_rounded = ((amax.bitcast(dtypes.uint32) + 0x200000) & 0xFF800000).bitcast(dtypes.float32)
|
||||
scale_exp = (amax_rounded.maximum(2**-126).log2().floor() - 2).clamp(-127, 127)
|
||||
e8 = (scale_exp + 127).cast(dtypes.uint8)
|
||||
scaled = xb * (-scale_exp).exp2().reshape(*batch, K//32, 1)
|
||||
|
||||
mag = scaled.abs()
|
||||
code = sum(x.cast(dtypes.uint8) for x in
|
||||
(mag > .25, mag >= .75, mag > 1.25, mag >= 1.75, mag > 2.5, mag >= 3.5, mag > 5.0))
|
||||
code = code | ((scaled < 0).cast(dtypes.uint8) << 3)
|
||||
code = code.reshape(*batch, K)
|
||||
packed = code[..., 0::2] | (code[..., 1::2] << 4)
|
||||
if isinstance(x.device, tuple) and x.uop.axis == x.ndim-2 and x.shape[x.uop.axis] == len(x.device):
|
||||
axis = x.uop.axis
|
||||
order = (axis, *range(axis), *range(axis+1, e8.ndim))
|
||||
e8_local = e8.permute(order)
|
||||
return packed, e8, _mxfp4_shuffle_scales(e8_local.reshape(e8_local.shape[0], -1, K//32))
|
||||
return packed, e8, _mxfp4_shuffle_scales(e8.reshape(rows, K//32))
|
||||
|
||||
def mx_pack(e8:Tensor) -> Tensor:
|
||||
rows, scale_K = e8.shape
|
||||
return e8.reshape(rows, scale_K // 4, 4).bitcast(dtypes.uint32).reshape(rows, scale_K // 4).permute(1, 0).contiguous()
|
||||
@@ -238,7 +215,7 @@ def custom_uop_gemm(C:UOp, A:UOp, B:UOp) -> UOp:
|
||||
k = UOp.range(K, 0, AxisType.REDUCE)
|
||||
mul = (A.flatten().index((m*UOp.const(K)+k))*
|
||||
B.flatten().index((k*UOp.const(N)+n))).cast(dtypes.float32)
|
||||
red = mul.reduce(k, arg=Ops.ADD, dtype=dtypes.float32).cast(C.dtype)
|
||||
red = mul.reduce(k, arg=Ops.ADD).cast(C.dtype)
|
||||
store = C.flatten().index((m*UOp.const(N)+n)).store(red).end(m, n)
|
||||
return store.sink(arg=KernelInfo(name=f'uop_gemm_{M}_{N}_{K}'))
|
||||
|
||||
@@ -405,15 +382,16 @@ def custom_mx_gemm_bw(gradient:UOp, kernel:UOp, has_w_post:bool, w_stored:bool=F
|
||||
# ** mxfp4 gemm backward
|
||||
|
||||
def custom_mxfp4_gemm_bw(gradient:UOp, kernel:UOp):
|
||||
# The raw kernel consumes quantized buffers, while the final two inputs retain the BF16 operands for STE gradients.
|
||||
inputs = kernel.src[1:] # (out, a_q, b_q, scale_a, scale_b, a, w)
|
||||
assert len(inputs) == 7
|
||||
inputs = kernel.src[1:] # out, row operands/scales, BF16 operands, column operands/scales
|
||||
assert len(inputs) == 11
|
||||
a, w = Tensor(inputs[5], device=inputs[5].device), Tensor(inputs[6], device=inputs[6].device)
|
||||
a_col, scale_a_col = Tensor(inputs[7], device=a.device), Tensor(inputs[8], device=a.device)
|
||||
w_col, scale_w_col = Tensor(inputs[9], device=a.device), Tensor(inputs[10], device=a.device)
|
||||
g = Tensor(gradient, device=a.device)[:a.shape[0]].cast(dtypes.bfloat16)
|
||||
grad_a = asm_gemm(g, w, mxfp4=True)
|
||||
a_flat, g_flat = a.reshape(-1, a.shape[-1]), g.reshape(-1, g.shape[-1])
|
||||
grad_w = asm_gemm(g_flat.T, a_flat, mxfp4=True)
|
||||
return (None, None, None, None, None, grad_a.uop, grad_w.uop)
|
||||
g_row, scale_g_row, g_col, scale_g_col = quantize_mxfp4(g, flatten_row=True)
|
||||
grad_a = _mxfp4_gemm_quantized(g_row, w_col, scale_g_row, scale_w_col).reshape(*a.shape[:-1], w.shape[-1])
|
||||
grad_w = _mxfp4_gemm_quantized(g_col, a_col, scale_g_col, scale_a_col).reshape(w.shape)
|
||||
return (None, None, None, None, None, grad_a.uop, grad_w.uop, None, None, None, None)
|
||||
|
||||
# ** main gemm function
|
||||
|
||||
@@ -459,16 +437,10 @@ def asm_gemm(a:Tensor, b:Tensor, x_scale:Tensor|None=None, w_scale:Tensor|None=N
|
||||
tile_m, tile_n = next((tm, tn) for tm, tn in ((256, 256), (192, 256), (128, 512)) if (batch*M) % tm == N % tn == 0)
|
||||
fxn = functools.partial(custom_mxfp4_gemm, tile_m=tile_m, tile_n=tile_n)
|
||||
w = b.T
|
||||
if k_sharded:
|
||||
ndev = len(a.device)
|
||||
a_q, _, scale_a = quantize_mxfp4(a.reshape(batch, M, ndev, K))
|
||||
b_q, _, scale_b = quantize_mxfp4(w.reshape(w.shape[0], ndev, K))
|
||||
b_q = _mxfp4_shuffle_weight(b_q.permute(1, 0, 2))
|
||||
else:
|
||||
a_q, _, scale_a = quantize_mxfp4(a.reshape(batch*M, K))
|
||||
b_q, _, scale_b = quantize_mxfp4(w)
|
||||
a_q, b_q = a_q.reshape(batch, M, K//2).contiguous(), _mxfp4_shuffle_weight(b_q)
|
||||
out = Tensor.custom_kernel(out, a_q, b_q, scale_a, scale_b, a, w, fxn=fxn, grad_fxn=custom_mxfp4_gemm_bw)[0]
|
||||
a_q, scale_a, a_col, scale_a_col = quantize_mxfp4(a, shuffle_col=True)
|
||||
b_q, scale_b, b_col, scale_b_col = quantize_mxfp4(w, shuffle_row=True, shuffle_col=True)
|
||||
out = Tensor.custom_kernel(out, a_q, b_q, scale_a, scale_b, a, w,
|
||||
a_col, scale_a_col, b_col, scale_b_col, fxn=fxn, grad_fxn=custom_mxfp4_gemm_bw)[0]
|
||||
elif mx:
|
||||
# mxfp8 1x32 block scaling
|
||||
if mx_scales is not None:
|
||||
|
||||
+68
-794
File diff suppressed because it is too large
Load Diff
@@ -113,7 +113,6 @@ if __name__ == "__main__":
|
||||
}
|
||||
elif GEMM_VARIATION == "hcopt" and M == N == K == 4096 and DTYPE_IN == dtypes.half and DTYPE_OUT == dtypes.half and DTYPE_ACC == dtypes.float:
|
||||
print("Using CUDA and generated hcopt")
|
||||
# [Opt(op=OptOps.TC, axis=0, amt=0), Opt(op=OptOps.UPCAST, axis=0, amt=4), Opt(op=OptOps.UPCAST, axis=1, amt=4), Opt(op=OptOps.LOCAL, axis=1, amt=4)]
|
||||
prog = CUDAProgram(device, "wmma_example", compiler.compile(open(os.path.join(script_dir, 'max_kernels/nv.fp16_fp32_fp16.hcopt.cu')).read()))
|
||||
args = (c, a, b)
|
||||
kwargs = {
|
||||
|
||||
+25
-2
@@ -1,10 +1,32 @@
|
||||
import functools, pathlib
|
||||
from tinygrad import Tensor, dtypes
|
||||
from tinygrad.uop.ops import UOp, Ops, KernelInfo
|
||||
from tinygrad.uop.ops import UOp, Ops, KernelInfo, AxisType
|
||||
from tinygrad.helpers import getenv
|
||||
from tinygrad.renderer import Estimates
|
||||
from tinygrad.runtime.support.compiler_amd import HIPCCCompiler
|
||||
from extra.gemm.cdna_asm_gemm import quantize_mxfp8, _mx_block_scale, _mx_block_scale_3d
|
||||
|
||||
ZERO_OPTIM = getenv("ZERO_OPTIM", 0)
|
||||
|
||||
def reduce_scatter_devaxis(out:Tensor, shard_axis:int=0) -> Tensor:
|
||||
# out: sharded on the device axis, shape (ndev, *rest); return the device-axis sum left sharded on shard_axis.
|
||||
u = out.uop
|
||||
devs, rest = u.device, u.shape[1:]
|
||||
assert rest[shard_axis] % len(devs) == 0, f"reduce_scatter needs even shards: {rest[shard_axis]} % {len(devs)}"
|
||||
# reach the raw per-device buffer below the UNSHARD, keeping the AFTERs so reads stay ordered after the kernel writes
|
||||
node, barriers = u, []
|
||||
while node.op is not Ops.UNSHARD:
|
||||
if node.op is Ops.AFTER: barriers += node.src[1:]
|
||||
node = node.src[0]
|
||||
mbuf = node.src[0].after(*barriers) if barriers else node.src[0]
|
||||
sz = rest[shard_axis] // len(devs)
|
||||
shards = []
|
||||
for i in range(len(devs)):
|
||||
bounds = tuple((0,s) if a != shard_axis else (i*sz,(i+1)*sz) for a,s in enumerate(rest))
|
||||
contribs = [mbuf.mselect(j).reshape(rest).shrink(bounds).copy_to_device(devs[i]) for j in range(len(devs))]
|
||||
shards.append(functools.reduce(lambda a,b: a.alu(Ops.ADD, b), contribs))
|
||||
return Tensor(UOp.mstack(*shards).unshard(shard_axis, UOp.range(len(devs), -1, AxisType.DEVICE)), device=devs)
|
||||
|
||||
@functools.cache
|
||||
def custom_hk_grouped_mxfp8_gemm(C:UOp, A:UOp, B:UOp, scale_A:UOp, scale_B:UOp, *extra:UOp, dname:str, n_experts:int) -> UOp:
|
||||
M, K = A.shape
|
||||
@@ -58,7 +80,8 @@ def grouped_mx_wgrad(g:Tensor, xg:Tensor, expert_off:Tensor, n_experts:int) -> T
|
||||
out = Tensor(inv.uop.unshard(0), device=g.device) if is_multi else inv
|
||||
out = Tensor.custom_kernel(out, gT, xT, g_si, x_si, expert_off,
|
||||
fxn=functools.partial(custom_hk_grouped_mxfp8_wgrad, dname=dname, n_experts=n_experts))[0]
|
||||
out = out.sum(0) if is_multi else out.squeeze(0)
|
||||
if is_multi and ZERO_OPTIM: out = reduce_scatter_devaxis(out, 0)
|
||||
else: out = out.sum(0) if is_multi else out.squeeze(0)
|
||||
return out.reshape(n_experts, N, K)
|
||||
|
||||
def mx_pack_3d(e8:Tensor) -> Tensor:
|
||||
|
||||
@@ -1,11 +1,57 @@
|
||||
import functools, math, pathlib
|
||||
from tinygrad import Tensor, dtypes
|
||||
from tinygrad.helpers import getenv
|
||||
from tinygrad.uop.ops import UOp, Ops, KernelInfo, AxisType
|
||||
from tinygrad.renderer import Estimates
|
||||
from tinygrad.runtime.support.compiler_amd import HIPCCCompiler
|
||||
|
||||
BLOCK_ROW = 256
|
||||
|
||||
@functools.cache
|
||||
def _router_mfma_fwd(out:UOp, x:UOp, weight:UOp, bias:UOp, *, dname:str) -> UOp:
|
||||
*lead, K = x.shape
|
||||
M = math.prod(lead)
|
||||
E = weight.shape[0]
|
||||
threads = UOp.special(256, "lidx0")
|
||||
workgroups = UOp.special((M + 63) // 64, "gidx0")
|
||||
sink = UOp.sink(out.base, x.base, weight.base, bias.base, threads, workgroups,
|
||||
arg=KernelInfo(f"moe_router_mfma_{M}_{K}_{E}", estimates=Estimates(ops=2*M*E*K, mem=(M*K+E*K+E)*2+M*E*4)))
|
||||
amd = pathlib.Path(__file__).parent.parent/"thunder"/"amd"
|
||||
src = (amd/"moe_router_mfma.cpp").read_text()
|
||||
lib = HIPCCCompiler("gfx950", [f"-I{(amd/'include').as_posix()}", "-std=c++20", "-DKITTENS_CDNA4", "-DHIP_ENABLE_WARP_SYNC_BUILTINS",
|
||||
f"-DROUTER_M={M}", f"-DROUTER_K={K}", f"-DROUTER_E={E}"]).compile_cached(src)
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src), UOp(Ops.BINARY, arg=lib)))
|
||||
|
||||
def _router_mfma_bwd(gradient:UOp, kernel:UOp) -> tuple:
|
||||
_, x_u, weight_u, bias_u = kernel.src[1:5]
|
||||
x, weight, bias = (Tensor(u, device=u.device) for u in (x_u, weight_u, bias_u))
|
||||
reference = x.float() @ weight.float().T + bias.float()
|
||||
grad_x, grad_weight, grad_bias = reference.gradient(x, weight, bias, gradient=Tensor(gradient, device=x_u.device))
|
||||
return None, grad_x.uop, grad_weight.uop, grad_bias.uop
|
||||
|
||||
def router_mfma(x:Tensor, weight:Tensor, bias:Tensor) -> Tensor:
|
||||
assert x.ndim >= 2 and weight.ndim == 2 and bias.ndim == 1
|
||||
K = x.shape[-1]
|
||||
E = weight.shape[0]
|
||||
assert weight.shape == (E, K) and bias.shape == (E,)
|
||||
assert x.dtype == weight.dtype == bias.dtype == dtypes.bfloat16
|
||||
assert E == 32 and K % 64 == 0
|
||||
if isinstance(x.device, tuple):
|
||||
assert x.uop.axis == 0, f"router MFMA requires axis-0 sharding, got axis={x.uop.axis}"
|
||||
local_shape = x.uop.shard_shape
|
||||
assert local_shape[-1] == K and math.prod(local_shape[:-1]) % 64 == 0, f"unsupported local router shape {local_shape}"
|
||||
else:
|
||||
assert math.prod(x.shape[:-1]) % 64 == 0
|
||||
x, weight, bias = x.contiguous(), weight.contiguous(), bias.contiguous()
|
||||
out = _sharded_invalids((*x.shape[:-1], E), dtypes.float32, x.device)
|
||||
out, *_ = Tensor.custom_kernel(out, x, weight, bias,
|
||||
fxn=functools.partial(_router_mfma_fwd, dname=str(x.device)), grad_fxn=_router_mfma_bwd)
|
||||
return out
|
||||
|
||||
def _sharded_invalids(shape:tuple[int, ...], dtype, device) -> Tensor:
|
||||
if isinstance(device, tuple):
|
||||
return Tensor(Tensor.invalids(shape[0] // len(device), *shape[1:], dtype=dtype, device=device).uop.multi(0), device=device)
|
||||
per = Tensor.invalids(shape[0]//len(device), *shape[1:], dtype=dtype, device=device)
|
||||
return Tensor(per.uop.unshard(0), device=device)
|
||||
return Tensor.invalids(*shape, dtype=dtype, device=device)
|
||||
|
||||
def _atomic_add(device:str) -> str:
|
||||
@@ -52,7 +98,7 @@ def _ggather_bwd(gradient:UOp, kernel:UOp) -> tuple:
|
||||
g, m, j, jo, ji = _kv_ranges(Gk, M, Dk, _blk_for(Dk))
|
||||
row = idx.index(g, m).cast(dtypes.weakint)
|
||||
val = gout.index(g, m, j).load().cast(dtypes.float32)
|
||||
atomic = UOp(Ops.CUSTOM, dtypes.void, (gtab.index(g, row, j), val), arg=atomic_str)
|
||||
atomic = UOp(Ops.CUSTOM, src=(gtab.index(g, row, j), val), arg=(atomic_str, dtypes.void))
|
||||
return atomic.end(g, m, jo, ji).sink(arg=KernelInfo(name=f"ggather_bwd_{M}_{Dk}", opts_to_apply=()))
|
||||
grad_table = Tensor.custom_kernel(gt, go, Tensor(idx_u, device=dev), fxn=_bwd_kernel)[0]
|
||||
return (None, grad_table.cast(table_u.dtype).uop, None)
|
||||
|
||||
@@ -223,7 +223,7 @@ def test_matmul():
|
||||
lds = UOp.placeholder((lds_size,), dtypes.uint8, 0, AddrSpace.LOCAL)
|
||||
sink = UOp.sink(A.base, B.base, C.base, lds, *gidxs, *lidxs,
|
||||
arg=KernelInfo(name=colored("kernel","cyan"), estimates=Estimates(ops=N*N*N*2, mem=N*N*2*3)))
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple([UOp(Ops.INS, arg=x) for x in insts]))))
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple([UOp(Ops.INS, arg=(x, dtypes.void)) for x in insts]))))
|
||||
|
||||
c = Tensor.custom_kernel(a, b, c, fxn=asm_kernel)[2]
|
||||
linear = c.schedule_linear()
|
||||
|
||||
@@ -2,7 +2,7 @@ import numpy as np
|
||||
from tinygrad import dtypes, Tensor
|
||||
from tinygrad.helpers import getenv, get_single_element
|
||||
from tinygrad.dtype import _to_np_dtype
|
||||
from tinygrad.engine.realize import compile_linear
|
||||
from tinygrad.engine.realize import lower_and_compile
|
||||
from tinygrad.codegen.opt import OptOps
|
||||
|
||||
dtype_in = (dtypes.half if getenv("HALF") else dtypes.bfloat16 if getenv("BFLOAT16") else
|
||||
@@ -39,7 +39,7 @@ if __name__ == "__main__":
|
||||
c = a.matmul(b, dtype=acc_dtype).realize()
|
||||
|
||||
if getenv("SHOULD_USE_TC"):
|
||||
linear = compile_linear(a.matmul(b, dtype=acc_dtype).schedule_linear())
|
||||
linear = lower_and_compile(a.matmul(b, dtype=acc_dtype).schedule_linear())
|
||||
call = get_single_element(list(linear.src))
|
||||
applied_opts = call.src[0].src[0].arg.applied_opts
|
||||
assert any(opt.op is OptOps.TC for opt in applied_opts), f"TC not triggered, {applied_opts}"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from tinygrad import Tensor, dtypes, Context
|
||||
from tinygrad.helpers import getenv
|
||||
from tinygrad.codegen.opt import Opt, OptOps
|
||||
from tinygrad.uop.ops import AxisType
|
||||
from tinygrad.engine.realize import run_linear
|
||||
from dataclasses import replace
|
||||
|
||||
@@ -13,17 +14,17 @@ if __name__ == "__main__":
|
||||
C = A.matmul(B)
|
||||
if getenv("GEMV"):
|
||||
opts = [
|
||||
Opt(op=OptOps.UNROLL, axis=0, amt=8),
|
||||
Opt(op=OptOps.GROUP, axis=0, amt=32),
|
||||
Opt(op=OptOps.SPLIT, axis=1, arg=(8, AxisType.UNROLL)),
|
||||
Opt(op=OptOps.SPLIT, axis=1, arg=(32, AxisType.GROUP_REDUCE)),
|
||||
]
|
||||
else:
|
||||
opts = [
|
||||
Opt(op=OptOps.TC, axis=0, amt=0),
|
||||
Opt(op=OptOps.UPCAST, axis=0, amt=4),
|
||||
Opt(op=OptOps.UPCAST, axis=1, amt=8),
|
||||
Opt(op=OptOps.LOCAL, axis=0, amt=2),
|
||||
Opt(op=OptOps.LOCAL, axis=1, amt=2),
|
||||
Opt(op=OptOps.LOCAL, axis=0, amt=2),
|
||||
Opt(op=OptOps.SPLIT, axis=0, arg=(4, AxisType.UPCAST)),
|
||||
Opt(op=OptOps.SPLIT, axis=1, arg=(8, AxisType.UPCAST)),
|
||||
Opt(op=OptOps.SPLIT, axis=0, arg=(2, AxisType.LOCAL)),
|
||||
Opt(op=OptOps.SPLIT, axis=1, arg=(2, AxisType.LOCAL)),
|
||||
Opt(op=OptOps.SPLIT, axis=0, arg=(2, AxisType.LOCAL)),
|
||||
]
|
||||
linear = C.schedule_linear()
|
||||
call = linear.src[-1]
|
||||
|
||||
@@ -79,7 +79,7 @@ if __name__ == "__main__":
|
||||
linear, var_vals = C.linear_with_vars()
|
||||
last_call = linear.src[-1]
|
||||
ast = last_call.src[0]
|
||||
bufs = [s.buffer for s in last_call.src[1:] if s.op is not Ops.BIND]
|
||||
bufs = [s.buffer for s in last_call.src[1:] if not s.is_bound_var]
|
||||
|
||||
src = compiled.asm["ptx"]
|
||||
# specify the shared memory here so we don't need to do it dynamically
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
from __future__ import annotations
|
||||
import functools, math, pathlib
|
||||
from tinygrad import Tensor, dtypes
|
||||
from tinygrad.uop.ops import UOp, Ops, KernelInfo
|
||||
from tinygrad.renderer import Estimates
|
||||
from extra.gemm.cdna_asm_gemm import FP8_DTYPE
|
||||
from extra.llama_kernels import NUM_WG, THREADS_PER_WG, alloc_like, alloc_local, compile_hip, dname_of
|
||||
|
||||
def rmsnorm_mul_fwd(x_in:Tensor, weight:Tensor, eps:float) -> tuple[Tensor, Tensor]:
|
||||
x = x_in.float()
|
||||
rrms = (x.square().mean(-1, keepdim=True) + eps).rsqrt()
|
||||
return ((x * rrms) * weight.float()).cast(x_in.dtype), rrms
|
||||
|
||||
@functools.cache
|
||||
def _rmsnorm_mul_fwd_fxn(x_in_p, w_p, eps, device):
|
||||
return rmsnorm_mul_fwd(Tensor(x_in_p, device=device), Tensor(w_p, device=device), eps)
|
||||
|
||||
def _rmsnorm_mul_bwd(grad:UOp, call:UOp) -> tuple:
|
||||
x = Tensor(call.src[1]).float(); weight = Tensor(call.src[2]).float()
|
||||
rrms = Tensor(call.unbound_outputs[1])
|
||||
x_normed = x * rrms # recompute unweighted normed (x is call.src[1])
|
||||
d_y = Tensor(grad).float()
|
||||
dxn = d_y * weight # d/d(x_normed)
|
||||
d_x = rrms * (dxn - x_normed * (dxn * x_normed).mean(-1, keepdim=True))
|
||||
dw = d_y * x_normed
|
||||
d_weight = dw.sum(axis=tuple(range(dw.ndim - 1))) # reduce batch/seq -> [dim]
|
||||
return (d_x.cast(call.src[1].dtype).uop, d_weight.cast(call.src[2].dtype).uop)
|
||||
|
||||
def rmsnorm_mul(x_in:Tensor, weight:Tensor, eps:float) -> tuple[Tensor, Tensor]:
|
||||
fxn = _rmsnorm_mul_fwd_fxn(x_in.as_param(0).uop, weight.as_param(1).uop, eps, x_in.device)
|
||||
outs = UOp.call_with_outputs((fxn[0].uop, fxn[1].uop), x_in.uop, weight.uop, grad_fxn=_rmsnorm_mul_bwd)
|
||||
return Tensor(outs[0]), Tensor(outs[1])
|
||||
|
||||
@functools.cache
|
||||
def _custom_rmsnorm_mul_quantize_mxfp8_fwd(q:UOp, e8:UOp, rrms:UOp, x:UOp, weight:UOp, *, dname:str, eps:float) -> UOp:
|
||||
*lead, hidden = x.shape
|
||||
rows, padded = math.prod(lead), q.shape[-1]
|
||||
num_wg = min(NUM_WG, rows)
|
||||
threads, workgroups = UOp.special(THREADS_PER_WG, "lidx0"), UOp.special(num_wg, "gidx0")
|
||||
sink = UOp.sink(q.base, e8.base, rrms.base, x.base, weight.base, threads, workgroups,
|
||||
arg=KernelInfo(f"rmsnorm_mul_quantize_mxfp8_{rows}_{hidden}_{padded}",
|
||||
estimates=Estimates(ops=8*rows*hidden, mem=rows*(hidden*2+padded+padded//32+4)+hidden*2)))
|
||||
src = (pathlib.Path(__file__).parent/"rmsnorm_mul_quantize_mxfp8.cpp").read_text()
|
||||
defines = [f"-DN_ELEMS={rows*hidden}", f"-DHIDDEN={hidden}", f"-DPADDED={padded}",
|
||||
f"-DNUM_WG={num_wg}", f"-DTHREADS_PER_WG={THREADS_PER_WG}", f"-DEPS_LITERAL={eps}f"]
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src),
|
||||
UOp(Ops.BINARY, arg=compile_hip(src, defines))))
|
||||
|
||||
@functools.cache
|
||||
def _custom_rmsnorm_mul_quantize_mxfp8_bwd(grad_x:UOp, grad_weight_partial:UOp, grad_q:UOp, x:UOp, weight:UOp, e8:UOp, rrms:UOp,
|
||||
*, dname:str) -> UOp:
|
||||
*lead, hidden = x.shape
|
||||
rows, padded = math.prod(lead), grad_q.shape[-1]
|
||||
num_wg = min(NUM_WG, rows)
|
||||
threads, workgroups = UOp.special(THREADS_PER_WG, "lidx0"), UOp.special(num_wg, "gidx0")
|
||||
sink = UOp.sink(grad_x.base, grad_weight_partial.base, grad_q.base, x.base, weight.base, e8.base, rrms.base,
|
||||
threads, workgroups,
|
||||
arg=KernelInfo(f"rmsnorm_mul_quantize_mxfp8_bwd_{rows}_{hidden}_{padded}",
|
||||
estimates=Estimates(ops=10*rows*hidden, mem=rows*(hidden*6+padded*2+padded//32+4)+num_wg*hidden*4)))
|
||||
src = (pathlib.Path(__file__).parent/"rmsnorm_mul_quantize_mxfp8_bwd.cpp").read_text()
|
||||
defines = [f"-DN_ELEMS={rows*hidden}", f"-DHIDDEN={hidden}", f"-DPADDED={padded}", f"-DNUM_WG={num_wg}", f"-DTHREADS_PER_WG={THREADS_PER_WG}"]
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src),
|
||||
UOp(Ops.BINARY, arg=compile_hip(src, defines))))
|
||||
|
||||
def _rmsnorm_mul_quantize_mxfp8_backward(gradient:UOp, kernel:UOp) -> tuple:
|
||||
_, e8_u, rrms_u, x_u, weight_u = kernel.src[1:]
|
||||
device = x_u.device
|
||||
axis = x_u.axis if isinstance(device, tuple) else None
|
||||
*lead, hidden = x_u.shape
|
||||
num_wg = min(NUM_WG, math.prod(lead))
|
||||
grad_x = alloc_like(x_u.shape, x_u.dtype, device, axis)
|
||||
grad_weight_partial = alloc_local((num_wg, hidden), dtypes.float32, device, axis)
|
||||
grad_q = Tensor(gradient, device=device).cast(dtypes.bfloat16).contiguous()
|
||||
grad_x, grad_weight_partial, *_ = Tensor.custom_kernel(
|
||||
grad_x, grad_weight_partial, grad_q, Tensor(x_u, device=device), Tensor(weight_u, device=device),
|
||||
Tensor(e8_u.after(kernel), device=device), Tensor(rrms_u.after(kernel), device=device),
|
||||
fxn=functools.partial(_custom_rmsnorm_mul_quantize_mxfp8_bwd, dname=dname_of(device)))
|
||||
grad_weight = grad_weight_partial.sum(0).cast(weight_u.dtype)
|
||||
return None, None, None, grad_x.uop, grad_weight.uop
|
||||
|
||||
def rmsnorm_mul_quantize_mxfp8(x:Tensor, weight:Tensor, eps:float, padded:int|None=None) -> tuple[Tensor, Tensor, Tensor]:
|
||||
"""RMSNorm(x)*weight directly to rowwise MXFP8. Returns (q, e8, rrms), without a BF16 normalized round-trip."""
|
||||
assert x.dtype == weight.dtype == dtypes.bfloat16 and x.shape[-1] == weight.shape[0], f"{x.shape=} {weight.shape=}"
|
||||
hidden = x.shape[-1]
|
||||
padded = math.ceil(hidden / 256) * 256 if padded is None else padded
|
||||
assert padded >= hidden and padded % 256 == 0 and hidden % 32 == 0
|
||||
axis = x.uop.axis if isinstance(x.device, tuple) else None
|
||||
q = alloc_like((*x.shape[:-1], padded), FP8_DTYPE, x.device, axis)
|
||||
e8 = alloc_like((*x.shape[:-1], padded // 32), dtypes.uint8, x.device, axis)
|
||||
rrms = alloc_like((*x.shape[:-1], 1), dtypes.float32, x.device, axis)
|
||||
q, e8, rrms, *_ = Tensor.custom_kernel(q, e8, rrms, x, weight,
|
||||
fxn=functools.partial(_custom_rmsnorm_mul_quantize_mxfp8_fwd, dname=dname_of(x.device), eps=eps),
|
||||
grad_fxn=_rmsnorm_mul_quantize_mxfp8_backward)
|
||||
return q, e8, rrms
|
||||
@@ -0,0 +1,95 @@
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <hip/hip_bf16.h>
|
||||
#include <hip/hip_fp8.h>
|
||||
|
||||
#ifndef N_ELEMS
|
||||
#define N_ELEMS 47185920
|
||||
#endif
|
||||
#ifndef HIDDEN
|
||||
#define HIDDEN 2880
|
||||
#endif
|
||||
#ifndef PADDED
|
||||
#define PADDED 3072
|
||||
#endif
|
||||
#ifndef NUM_WG
|
||||
#define NUM_WG 1024
|
||||
#endif
|
||||
#ifndef THREADS_PER_WG
|
||||
#define THREADS_PER_WG 256
|
||||
#endif
|
||||
#ifndef EPS_LITERAL
|
||||
#define EPS_LITERAL 1e-5f
|
||||
#endif
|
||||
|
||||
constexpr int ROWS = N_ELEMS / HIDDEN;
|
||||
constexpr int BLOCK = 32;
|
||||
constexpr int SCALE_BLOCKS = PADDED / BLOCK;
|
||||
constexpr float FP8_MAX = 448.0f;
|
||||
|
||||
static_assert(N_ELEMS % HIDDEN == 0, "N_ELEMS must be divisible by HIDDEN");
|
||||
static_assert(HIDDEN % BLOCK == 0 && PADDED % BLOCK == 0 && PADDED >= HIDDEN,
|
||||
"HIDDEN and PADDED must be block aligned");
|
||||
static_assert(SCALE_BLOCKS <= THREADS_PER_WG, "one thread handles each MXFP8 block");
|
||||
|
||||
extern "C" __global__ __launch_bounds__(THREADS_PER_WG) void rmsnorm_mul_quantize_mxfp8(
|
||||
__hip_fp8_storage_t *__restrict__ q_out,
|
||||
uint8_t *__restrict__ e8_out,
|
||||
float *__restrict__ rrms_out,
|
||||
const __hip_bfloat16 *__restrict__ x,
|
||||
const __hip_bfloat16 *__restrict__ weight) {
|
||||
__shared__ float reduce[THREADS_PER_WG];
|
||||
__shared__ __hip_bfloat16 x_row[HIDDEN];
|
||||
|
||||
const int tid = threadIdx.x;
|
||||
for (int row = blockIdx.x; row < ROWS; row += NUM_WG) {
|
||||
const long long xbase = (long long)row * HIDDEN;
|
||||
float sum_sq = 0.0f;
|
||||
for (int col = tid; col < HIDDEN; col += THREADS_PER_WG) {
|
||||
__hip_bfloat16 xb = x[xbase + col];
|
||||
x_row[col] = xb;
|
||||
float xf = (float)xb;
|
||||
sum_sq = fmaf(xf, xf, sum_sq);
|
||||
}
|
||||
|
||||
reduce[tid] = sum_sq;
|
||||
__syncthreads();
|
||||
for (int s = THREADS_PER_WG / 2; s > 0; s >>= 1) {
|
||||
if (tid < s) reduce[tid] += reduce[tid + s];
|
||||
__syncthreads();
|
||||
}
|
||||
const float rrms = rsqrtf(reduce[0] * (1.0f / (float)HIDDEN) + EPS_LITERAL);
|
||||
if (tid == 0) rrms_out[row] = rrms;
|
||||
|
||||
if (tid < SCALE_BLOCKS) {
|
||||
const int col_base = tid * BLOCK;
|
||||
float vals[BLOCK];
|
||||
float amax = 0.0f;
|
||||
#pragma unroll
|
||||
for (int i = 0; i < BLOCK; i++) {
|
||||
const int col = col_base + i;
|
||||
float v = 0.0f;
|
||||
if (col < HIDDEN) {
|
||||
float xn = (float)x_row[col] * rrms;
|
||||
__hip_bfloat16 yb = (__hip_bfloat16)(xn * (float)weight[col]);
|
||||
v = (float)yb;
|
||||
}
|
||||
vals[i] = v;
|
||||
amax = fmaxf(amax, fabsf(v));
|
||||
}
|
||||
int e8 = (int)floorf(log2f(fmaxf(amax, 1e-38f))) + 127;
|
||||
e8 = max(0, min(254, e8));
|
||||
const float qscale = exp2f((float)(127 - e8));
|
||||
__hip_fp8_storage_t packed[BLOCK];
|
||||
#pragma unroll
|
||||
for (int i = 0; i < BLOCK; i++) {
|
||||
float v = fmaxf(-FP8_MAX, fminf(FP8_MAX, vals[i] * qscale));
|
||||
packed[i] = __hip_cvt_float_to_fp8(v, __HIP_SATFINITE, __HIP_E4M3);
|
||||
}
|
||||
const long long qbase = (long long)row * PADDED + col_base;
|
||||
*reinterpret_cast<uint4 *>(&q_out[qbase]) = *reinterpret_cast<uint4 *>(&packed[0]);
|
||||
*reinterpret_cast<uint4 *>(&q_out[qbase + 16]) = *reinterpret_cast<uint4 *>(&packed[16]);
|
||||
e8_out[(long long)row * SCALE_BLOCKS + tid] = (uint8_t)e8;
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <hip/hip_bf16.h>
|
||||
|
||||
#ifndef N_ELEMS
|
||||
#define N_ELEMS 47185920
|
||||
#endif
|
||||
#ifndef HIDDEN
|
||||
#define HIDDEN 2880
|
||||
#endif
|
||||
#ifndef PADDED
|
||||
#define PADDED 3072
|
||||
#endif
|
||||
#ifndef NUM_WG
|
||||
#define NUM_WG 1024
|
||||
#endif
|
||||
#ifndef THREADS_PER_WG
|
||||
#define THREADS_PER_WG 256
|
||||
#endif
|
||||
|
||||
constexpr int ROWS = N_ELEMS / HIDDEN;
|
||||
constexpr int BLOCK = 32;
|
||||
constexpr int SCALE_BLOCKS = PADDED / BLOCK;
|
||||
constexpr int ELEMS_PER_THREAD = (HIDDEN + THREADS_PER_WG - 1) / THREADS_PER_WG;
|
||||
|
||||
static_assert(N_ELEMS % HIDDEN == 0, "N_ELEMS must be divisible by HIDDEN");
|
||||
static_assert(HIDDEN % BLOCK == 0 && PADDED % BLOCK == 0 && PADDED >= HIDDEN,
|
||||
"HIDDEN and PADDED must be block aligned");
|
||||
|
||||
extern "C" __global__ __launch_bounds__(THREADS_PER_WG) void rmsnorm_mul_quantize_mxfp8_bwd(
|
||||
__hip_bfloat16 *__restrict__ grad_x,
|
||||
float *__restrict__ grad_weight_partial,
|
||||
const __hip_bfloat16 *__restrict__ grad_q,
|
||||
const __hip_bfloat16 *__restrict__ x,
|
||||
const __hip_bfloat16 *__restrict__ weight,
|
||||
const uint8_t *__restrict__ e8,
|
||||
const float *__restrict__ rrms) {
|
||||
__shared__ float reduce[THREADS_PER_WG];
|
||||
const int tid = threadIdx.x;
|
||||
const int wg = blockIdx.x;
|
||||
|
||||
float w[ELEMS_PER_THREAD];
|
||||
float gw[ELEMS_PER_THREAD];
|
||||
#pragma unroll
|
||||
for (int i = 0; i < ELEMS_PER_THREAD; i++) {
|
||||
int col = tid + i * THREADS_PER_WG;
|
||||
w[i] = col < HIDDEN ? (float)weight[col] : 0.0f;
|
||||
gw[i] = 0.0f;
|
||||
}
|
||||
|
||||
for (int row = wg; row < ROWS; row += NUM_WG) {
|
||||
const long long xbase = (long long)row * HIDDEN;
|
||||
const long long qbase = (long long)row * PADDED;
|
||||
const long long ebase = (long long)row * SCALE_BLOCKS;
|
||||
const float r = rrms[row];
|
||||
float xn[ELEMS_PER_THREAD];
|
||||
float gxn[ELEMS_PER_THREAD];
|
||||
float local_dot = 0.0f;
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < ELEMS_PER_THREAD; i++) {
|
||||
const int col = tid + i * THREADS_PER_WG;
|
||||
if (col < HIDDEN) {
|
||||
const float xnf = (float)x[xbase + col] * r;
|
||||
const unsigned se = (unsigned)(254 - (int)e8[ebase + col / BLOCK]) << 23;
|
||||
const float qscale = __builtin_bit_cast(float, se);
|
||||
const float gy = (float)grad_q[qbase + col] * qscale;
|
||||
const float gxnf = gy * w[i];
|
||||
xn[i] = xnf;
|
||||
gxn[i] = gxnf;
|
||||
gw[i] += gy * xnf;
|
||||
local_dot = fmaf(gxnf, xnf, local_dot);
|
||||
} else {
|
||||
xn[i] = gxn[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
reduce[tid] = local_dot;
|
||||
__syncthreads();
|
||||
for (int s = THREADS_PER_WG / 2; s > 0; s >>= 1) {
|
||||
if (tid < s) reduce[tid] += reduce[tid + s];
|
||||
__syncthreads();
|
||||
}
|
||||
const float mean_term = reduce[0] * (1.0f / (float)HIDDEN);
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < ELEMS_PER_THREAD; i++) {
|
||||
const int col = tid + i * THREADS_PER_WG;
|
||||
if (col < HIDDEN) grad_x[xbase + col] = (__hip_bfloat16)(r * (gxn[i] - xn[i] * mean_term));
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
|
||||
const long long gwbase = (long long)wg * HIDDEN;
|
||||
#pragma unroll
|
||||
for (int i = 0; i < ELEMS_PER_THREAD; i++) {
|
||||
const int col = tid + i * THREADS_PER_WG;
|
||||
if (col < HIDDEN) grad_weight_partial[gwbase + col] = gw[i];
|
||||
}
|
||||
}
|
||||
+14
-5
@@ -16,9 +16,12 @@ def _do_reset_device(pci_bus): os.system(f"sudo sh -c 'echo 1 > /sys/bus/pci/dev
|
||||
def _is_module_loaded(name: str) -> bool: return os.path.isdir(f"/sys/module/{name}")
|
||||
|
||||
def cmd_remove_module(args):
|
||||
modules = ["nvidia_drm", "nvidia_modeset", "nvidia_uvm", "nvidia", "ast"] if args.backend == "nv" else ["amdgpu"]
|
||||
modules = ["nvidia_drm", "nvidia_modeset", "nvidia_uvm", "nvidia"] if args.backend == "nv" else ["amdgpu"]
|
||||
to_unload = [m for m in modules if _is_module_loaded(m)]
|
||||
if not to_unload: print("Kernel modules are not loaded")
|
||||
elif getattr(args, "expect", False):
|
||||
print(f"Kernel modules are loaded: {to_unload}")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("Removing kernel modules:", ", ".join(to_unload))
|
||||
try: subprocess.run(["sudo", "modprobe", "-r", *to_unload], check=True)
|
||||
@@ -60,17 +63,19 @@ def cmd_show_pids(args):
|
||||
|
||||
def cmd_kill_pids(args):
|
||||
devs = scan_devs_based_on_lock(prefix:={"amd":"am", "nv":"nv"}[args.backend], args)
|
||||
use_sudo = not getattr(args, "sudoless", False)
|
||||
|
||||
for dev in devs:
|
||||
for i in range(128):
|
||||
if i > 0: time.sleep(0.2)
|
||||
|
||||
try:
|
||||
try: pid = subprocess.check_output(['sudo', 'lsof', temp(f'{prefix}_{dev}.lock')]).decode('utf-8').strip().split('\n')[1].split()[1]
|
||||
try: pid = subprocess.check_output((['sudo'] if use_sudo else []) +
|
||||
['lsof', temp(f'{prefix}_{dev}.lock')]).decode('utf-8').strip().split('\n')[1].split()[1]
|
||||
except subprocess.CalledProcessError: break
|
||||
|
||||
print(f"Killing process {pid} (which uses {dev})")
|
||||
subprocess.run(['sudo', 'kill', '-9', pid], check=True)
|
||||
subprocess.run((['sudo'] if use_sudo else []) + ['kill', '-9', pid], check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Failed to kill process for device {dev}: {e}", file=sys.stderr)
|
||||
|
||||
@@ -79,6 +84,7 @@ def add_common_commands(parent_subparsers):
|
||||
p_insmod.set_defaults(func=cmd_insert_module)
|
||||
|
||||
p_rmmod = parent_subparsers.add_parser("rmmod", help="Remove a kernel module")
|
||||
p_rmmod.add_argument("--expect", action="store_true", help="Just assert that module is already unloaded")
|
||||
p_rmmod.set_defaults(func=cmd_remove_module)
|
||||
|
||||
p_reset = parent_subparsers.add_parser("reset", help="Reset a device")
|
||||
@@ -91,17 +97,20 @@ def add_common_commands(parent_subparsers):
|
||||
|
||||
p_reset = parent_subparsers.add_parser("kill_pids", help="Kill pids of processes using the device")
|
||||
p_reset.add_argument("--pci_bus", default="", help="PCI bus ID of the device")
|
||||
p_reset.add_argument("--sudoless", action="store_true", help="Do not use sudo when detecting or killing pids")
|
||||
p_reset.set_defaults(func=cmd_kill_pids)
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
backend_subparsers = parser.add_subparsers(dest="backend", required=True, metavar="{nv,amd}", help="Hardware backend to target")
|
||||
|
||||
nv_parser = backend_subparsers.add_parser("nv", help="NVIDIA GPUs")
|
||||
nv_parser = backend_subparsers.add_parser("nv", aliases=["NV"], help="NVIDIA GPUs")
|
||||
nv_parser.set_defaults(backend="nv")
|
||||
nv_commands = nv_parser.add_subparsers(dest="command", required=True)
|
||||
add_common_commands(nv_commands)
|
||||
|
||||
amd_parser = backend_subparsers.add_parser("amd", help="AMD GPUs")
|
||||
amd_parser = backend_subparsers.add_parser("amd", aliases=["AMD"], help="AMD GPUs")
|
||||
amd_parser.set_defaults(backend="amd")
|
||||
amd_commands = amd_parser.add_subparsers(dest="command", required=True)
|
||||
add_common_commands(amd_commands)
|
||||
|
||||
|
||||
+647
-307
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,6 @@
|
||||
from __future__ import annotations
|
||||
import functools, pathlib
|
||||
from dataclasses import replace
|
||||
from tinygrad import Tensor, dtypes
|
||||
from tinygrad.uop.ops import shape_to_shape_arg
|
||||
from tinygrad import Tensor
|
||||
from tinygrad.runtime.support.compiler_amd import HIPCCCompiler
|
||||
|
||||
FP8_MAX = 448.0
|
||||
@@ -12,13 +10,13 @@ NUM_WG, THREADS_PER_WG = 1024, 256
|
||||
@functools.cache
|
||||
def _local_abs_max_fxn(x_p, device):
|
||||
x = Tensor(x_p, device=device)
|
||||
inner = Tensor(x.uop.replace(src=(shape_to_shape_arg(x.uop.shard_shape),), arg=replace(x.uop.arg, axis=None))) if x.uop.axis is not None else x
|
||||
inner = Tensor(x.uop.src[0]) if x.uop.axis is not None else x # the per-shard view of the flat param
|
||||
return (inner.abs().max(),)
|
||||
|
||||
def local_abs_max(x:Tensor) -> Tensor:
|
||||
param = x.as_param(0)
|
||||
fxn = _local_abs_max_fxn(param.uop, x.device)
|
||||
return Tensor(fxn[0].uop.call(x.uop).gettuple(0))
|
||||
return Tensor(fxn[0].uop.call_with_output(x.uop))
|
||||
|
||||
def shard_shape(shape:tuple, axis:int, ndev:int) -> list:
|
||||
s = list(shape)
|
||||
|
||||
@@ -50,7 +50,7 @@ def _custom_quantize_fp8_with_amax(fp8_out:UOp, amax_out:UOp, x:UOp, amax_state:
|
||||
else: raise NotImplementedError(f"no atomic max for device {device}")
|
||||
amax_idx = amax_out.reshape((1,)).index(UOp.const(0))
|
||||
max_val = lds[0].load()
|
||||
atomic = UOp(Ops.CUSTOM, dtypes.void, (amax_idx, max_val.bitcast(dtypes.int32), max_val, amax_idx.load()), arg=atomic_arg)
|
||||
atomic = UOp(Ops.CUSTOM, src=(amax_idx, max_val.bitcast(dtypes.int32), max_val, amax_idx.load()), arg=(atomic_arg, dtypes.void))
|
||||
return atomic.end(tid, wg).sink(arg=KernelInfo(f"quantize_fp8_with_amax_{n_elems}", opts_to_apply=()))
|
||||
|
||||
@functools.cache
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import functools, math, pathlib
|
||||
from tinygrad import Tensor, dtypes
|
||||
from tinygrad.uop.ops import UOp, Ops, KernelInfo
|
||||
from tinygrad.renderer import Estimates
|
||||
from extra.llama_kernels import alloc_like, compile_hip
|
||||
|
||||
@functools.cache
|
||||
def _custom_quantize_mxfp4(row_fp4:UOp, row_scale:UOp, col_fp4:UOp, col_scale:UOp, x:UOp, *, shuffle_row:bool, shuffle_col:bool) -> UOp:
|
||||
M, N = math.prod(x.shape[:-1]), x.shape[-1]
|
||||
assert M % 256 == 0 and N % 256 == 0, f"MXFP4 quantization requires multiples of 256, got {x.shape}"
|
||||
name = f"quantize_mxfp4_{int(shuffle_row)}_{int(shuffle_col)}_{M}_{N}"
|
||||
mem = M*N*2 + M*N + M*N//16 # read bf16, write row+col fp4 + e8m0
|
||||
outputs = (row_fp4, row_scale, col_fp4, col_scale)
|
||||
sink = UOp.sink(*(o.base for o in outputs), x.base,
|
||||
*(UOp(Ops.CUSTOM, src=(o.base.index(0),), arg=("", dtypes.void)) for o in outputs),
|
||||
UOp.special(256, "lidx0"), UOp.special(M//128, "gidx0"), UOp.special(N//64, "gidx1"),
|
||||
arg=KernelInfo(name, estimates=Estimates(ops=12*M*N, mem=mem)))
|
||||
src = (pathlib.Path(__file__).parent/"quantize_mxfp4.cpp").read_text()
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=src),
|
||||
UOp(Ops.BINARY, arg=compile_hip(src, [f"-DKERNEL_NAME={name}", f"-DM_DIM={M}", f"-DN_DIM={N}",
|
||||
f"-DSHUFFLE_ROWWISE_FP4_VALUE={int(shuffle_row)}",
|
||||
f"-DSHUFFLE_COLWISE_FP4_VALUE={int(shuffle_col)}"]))))
|
||||
|
||||
def quantize_mxfp4(x:Tensor, *, shuffle_row:bool=False, shuffle_col:bool=False, flatten_row:bool=False) -> tuple[Tensor, Tensor, Tensor, Tensor]:
|
||||
assert x.dtype == dtypes.bfloat16 and x.ndim >= 2, f"expected BF16 matrix, got {x.dtype} {x.shape}"
|
||||
M, N = math.prod(x.shape[:-1]), x.shape[-1]
|
||||
assert M % 256 == 0 and N % 256 == 0, f"MXFP4 quantization requires multiples of 256, got {x.shape}"
|
||||
axis = x.uop.axis if isinstance(x.device, tuple) else None
|
||||
row_axis = 0 if flatten_row and axis is not None else axis
|
||||
col_axis = None if axis is None else (0 if axis == x.ndim-1 else 1)
|
||||
outputs = (alloc_like((M, N//2) if flatten_row else (*x.shape[:-1], N//2), dtypes.uint8, x.device, row_axis),
|
||||
alloc_like((M, N//32) if flatten_row else (*x.shape[:-1], N//32), dtypes.uint8, x.device, row_axis),
|
||||
alloc_like((N, M//2), dtypes.uint8, x.device, col_axis),
|
||||
alloc_like((N, M//32), dtypes.uint8, x.device, col_axis))
|
||||
fxn = functools.partial(_custom_quantize_mxfp4, shuffle_row=shuffle_row, shuffle_col=shuffle_col)
|
||||
return tuple(Tensor.custom_kernel(*outputs, x, fxn=fxn)[:4])
|
||||
@@ -0,0 +1,226 @@
|
||||
// Copyright (c) 2025-2026, Advanced Micro Devices, Inc. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <cstdint>
|
||||
|
||||
#if !defined(KERNEL_NAME) || !defined(M_DIM) || !defined(N_DIM) || !defined(SHUFFLE_ROWWISE_FP4_VALUE) || \
|
||||
!defined(SHUFFLE_COLWISE_FP4_VALUE)
|
||||
#error kernel dimensions and layouts must be defined
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int BLOCK = 32;
|
||||
constexpr int TILE_M = 128;
|
||||
constexpr int TILE_N = 64;
|
||||
constexpr int THREADS = 256;
|
||||
constexpr int THREADS_PER_ROW = 8;
|
||||
constexpr int VALUES_PER_THREAD = 4;
|
||||
constexpr int SMEM_STRIDE = BLOCK + 2;
|
||||
constexpr int M = M_DIM;
|
||||
constexpr int N = N_DIM;
|
||||
constexpr int M_PACKED = M / 2;
|
||||
constexpr int N_PACKED = N / 2;
|
||||
constexpr int M_SCALES = M / BLOCK;
|
||||
constexpr int N_SCALES = N / BLOCK;
|
||||
constexpr bool SHUFFLE_ROWWISE_FP4 = SHUFFLE_ROWWISE_FP4_VALUE;
|
||||
constexpr bool SHUFFLE_COLWISE_FP4 = SHUFFLE_COLWISE_FP4_VALUE;
|
||||
|
||||
static_assert(M % 256 == 0 && N % 256 == 0);
|
||||
|
||||
struct Quantized4 {
|
||||
uint16_t fp4;
|
||||
uint8_t scale;
|
||||
};
|
||||
|
||||
__device__ __forceinline__ float swizzle_xor1(float value) {
|
||||
float result;
|
||||
asm volatile("ds_swizzle_b32 %0, %1 offset:0x041f\n\ts_waitcnt lgkmcnt(0)" : "=v"(result) : "v"(value));
|
||||
return result;
|
||||
}
|
||||
|
||||
__device__ __forceinline__ float swizzle_xor2(float value) {
|
||||
float result;
|
||||
asm volatile("ds_swizzle_b32 %0, %1 offset:0x081f\n\ts_waitcnt lgkmcnt(0)" : "=v"(result) : "v"(value));
|
||||
return result;
|
||||
}
|
||||
|
||||
__device__ __forceinline__ float swizzle_xor4(float value) {
|
||||
float result;
|
||||
asm volatile("ds_swizzle_b32 %0, %1 offset:0x101f\n\ts_waitcnt lgkmcnt(0)" : "=v"(result) : "v"(value));
|
||||
return result;
|
||||
}
|
||||
|
||||
__device__ __forceinline__ float max8(float value) {
|
||||
value = fmaxf(value, swizzle_xor4(value));
|
||||
value = fmaxf(value, swizzle_xor2(value));
|
||||
return fmaxf(value, swizzle_xor1(value));
|
||||
}
|
||||
|
||||
__device__ __forceinline__ float4 load_bf16x4(const uint16_t* values) {
|
||||
const uint32_t lo = *reinterpret_cast<const uint32_t*>(values);
|
||||
const uint32_t hi = *reinterpret_cast<const uint32_t*>(values + 2);
|
||||
return make_float4(__uint_as_float(lo << 16), __uint_as_float(lo & 0xffff0000u),
|
||||
__uint_as_float(hi << 16), __uint_as_float(hi & 0xffff0000u));
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void hadamard16(float4& value, int lane) {
|
||||
const float a0 = value.x + value.y, a1 = value.x - value.y;
|
||||
const float a2 = value.z + value.w, a3 = value.z - value.w;
|
||||
value = make_float4(a0 + a2, a1 + a3, a0 - a2, a1 - a3);
|
||||
|
||||
const float4 xor1 = make_float4(swizzle_xor1(value.x), swizzle_xor1(value.y), swizzle_xor1(value.z), swizzle_xor1(value.w));
|
||||
value = lane & 1 ? make_float4(xor1.x - value.x, xor1.y - value.y, xor1.z - value.z, xor1.w - value.w)
|
||||
: make_float4(xor1.x + value.x, xor1.y + value.y, xor1.z + value.z, xor1.w + value.w);
|
||||
|
||||
const float4 xor2 = make_float4(swizzle_xor2(value.x), swizzle_xor2(value.y), swizzle_xor2(value.z), swizzle_xor2(value.w));
|
||||
value = lane & 2 ? make_float4(xor2.x - value.x, xor2.y - value.y, xor2.z - value.z, xor2.w - value.w)
|
||||
: make_float4(xor2.x + value.x, xor2.y + value.y, xor2.z + value.z, xor2.w + value.w);
|
||||
value.x *= 0.25f;
|
||||
value.y *= 0.25f;
|
||||
value.z *= 0.25f;
|
||||
value.w *= 0.25f;
|
||||
}
|
||||
|
||||
__device__ __forceinline__ uint8_t e8m0_scale(float amax, float& scale) {
|
||||
if (amax == 0.0f) {
|
||||
scale = 1.0f;
|
||||
return 127;
|
||||
}
|
||||
|
||||
const uint32_t rounded = (__float_as_uint(amax) + 0x200000u) & 0xff800000u;
|
||||
int exponent = static_cast<int>((rounded >> 23) & 0xff) - 129;
|
||||
exponent = exponent < -127 ? -127 : exponent > 127 ? 127 : exponent;
|
||||
scale = exponent == -127 ? __uint_as_float(0x00400000u) : __uint_as_float(static_cast<uint32_t>(exponent + 127) << 23);
|
||||
return static_cast<uint8_t>(exponent + 127);
|
||||
}
|
||||
|
||||
__device__ __forceinline__ uint16_t pack_fp4(float4 value, float scale) {
|
||||
uint32_t lo = 0, hi = 0;
|
||||
asm volatile("v_cvt_scalef32_pk_fp4_f32 %0, %1, %2, %3" : "+v"(lo) : "v"(value.x), "v"(value.y), "v"(scale));
|
||||
asm volatile("v_cvt_scalef32_pk_fp4_f32 %0, %1, %2, %3" : "+v"(hi) : "v"(value.z), "v"(value.w), "v"(scale));
|
||||
return static_cast<uint16_t>(lo | (hi << 8));
|
||||
}
|
||||
|
||||
__device__ __forceinline__ Quantized4 quantize(float4 value, int lane) {
|
||||
hadamard16(value, lane);
|
||||
const float local_max = fmaxf(fmaxf(fabsf(value.x), fabsf(value.y)), fmaxf(fabsf(value.z), fabsf(value.w)));
|
||||
float scale;
|
||||
const uint8_t e8m0 = e8m0_scale(max8(local_max), scale);
|
||||
return {pack_fp4(value, scale), e8m0};
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void store_scale(uint8_t* output, int row, int col, int cols, uint8_t value) {
|
||||
const int tile = ((row >> 5) * (cols >> 3) + (col >> 3)) << 8;
|
||||
const int offset = ((col & 3) << 6) + ((row & 15) << 2) + (((col >> 2) & 1) << 1) + ((row >> 4) & 1);
|
||||
output[tile + offset] = value;
|
||||
}
|
||||
|
||||
template<bool Shuffled>
|
||||
__device__ __forceinline__ void store_fp4(uint8_t* output, int row, int col, int packed_cols, uint16_t value) {
|
||||
int index = row * packed_cols + col;
|
||||
if constexpr (Shuffled) {
|
||||
const int tile = (row >> 4) * (packed_cols << 4) + (col >> 5) * 512;
|
||||
const int offset = ((col >> 4) & 1) * 256 + (row & 15) * 16 + (col & 15);
|
||||
index = tile + offset;
|
||||
}
|
||||
*reinterpret_cast<uint16_t*>(output + index) = value;
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void load_tile(uint16_t* tile, const uint16_t* input, int tile_m, int tile_n) {
|
||||
const int row = threadIdx.x / THREADS_PER_ROW;
|
||||
const int col = threadIdx.x % THREADS_PER_ROW * VALUES_PER_THREAD;
|
||||
const uint64_t packed = *reinterpret_cast<const uint64_t*>(input + (tile_m + row) * N + tile_n + col);
|
||||
*reinterpret_cast<uint32_t*>(tile + row * SMEM_STRIDE + col) = static_cast<uint32_t>(packed);
|
||||
*reinterpret_cast<uint32_t*>(tile + row * SMEM_STRIDE + col + 2) = static_cast<uint32_t>(packed >> 32);
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void quantize_row(uint16_t* tile, uint8_t* fp4_output, uint8_t* scale_output,
|
||||
int tile_m, int tile_n, int local_row, int lane) {
|
||||
const int row = tile_m + local_row;
|
||||
const int col = lane * VALUES_PER_THREAD;
|
||||
const Quantized4 result = quantize(load_bf16x4(tile + local_row * SMEM_STRIDE + col), lane);
|
||||
store_fp4<SHUFFLE_ROWWISE_FP4>(fp4_output, row, (tile_n + col) / 2, N_PACKED, result.fp4);
|
||||
if (lane == 0) store_scale(scale_output, row, tile_n / BLOCK, N_SCALES, result.scale);
|
||||
}
|
||||
|
||||
__device__ __forceinline__ Quantized4 quantize_col(uint16_t* tile, int col, int lane) {
|
||||
const int row = lane * VALUES_PER_THREAD;
|
||||
return quantize(make_float4(
|
||||
__uint_as_float(static_cast<uint32_t>(tile[(row + 0) * SMEM_STRIDE + col]) << 16),
|
||||
__uint_as_float(static_cast<uint32_t>(tile[(row + 1) * SMEM_STRIDE + col]) << 16),
|
||||
__uint_as_float(static_cast<uint32_t>(tile[(row + 2) * SMEM_STRIDE + col]) << 16),
|
||||
__uint_as_float(static_cast<uint32_t>(tile[(row + 3) * SMEM_STRIDE + col]) << 16)), lane);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" __global__ __launch_bounds__(THREADS, 8)
|
||||
void KERNEL_NAME(uint8_t* __restrict__ rowwise_fp4, uint8_t* __restrict__ rowwise_scale,
|
||||
uint8_t* __restrict__ colwise_fp4, uint8_t* __restrict__ colwise_scale,
|
||||
const uint16_t* __restrict__ input) {
|
||||
__shared__ uint16_t tile[BLOCK * SMEM_STRIDE];
|
||||
const int tid = threadIdx.x;
|
||||
const int line = tid / THREADS_PER_ROW;
|
||||
const int lane = tid % THREADS_PER_ROW;
|
||||
const int block_m = blockIdx.x * TILE_M;
|
||||
const int block_n = blockIdx.y * TILE_N;
|
||||
|
||||
if constexpr (!SHUFFLE_COLWISE_FP4) {
|
||||
uint16_t col_fp4[TILE_N / BLOCK][TILE_M / BLOCK];
|
||||
uint8_t col_scale[TILE_N / BLOCK][TILE_M / BLOCK];
|
||||
|
||||
for (int chunk_m = 0; chunk_m < TILE_M / BLOCK; chunk_m++) {
|
||||
for (int chunk_n = 0; chunk_n < TILE_N / BLOCK; chunk_n++) {
|
||||
const int tile_m = block_m + chunk_m * BLOCK;
|
||||
const int tile_n = block_n + chunk_n * BLOCK;
|
||||
load_tile(tile, input, tile_m, tile_n);
|
||||
__syncthreads();
|
||||
|
||||
quantize_row(tile, rowwise_fp4, rowwise_scale, tile_m, tile_n, line, lane);
|
||||
const Quantized4 result = quantize_col(tile, line, lane);
|
||||
col_fp4[chunk_n][chunk_m] = result.fp4;
|
||||
col_scale[chunk_n][chunk_m] = result.scale;
|
||||
__syncthreads();
|
||||
}
|
||||
}
|
||||
|
||||
for (int chunk_n = 0; chunk_n < TILE_N / BLOCK; chunk_n++) {
|
||||
for (int chunk_m = 0; chunk_m < TILE_M / BLOCK; chunk_m++)
|
||||
tile[line * BLOCK + chunk_m * THREADS_PER_ROW + lane] = col_fp4[chunk_n][chunk_m];
|
||||
__syncthreads();
|
||||
|
||||
for (int round = 0; round < BLOCK / THREADS_PER_ROW; round++) {
|
||||
const int col = round * THREADS_PER_ROW + tid / BLOCK;
|
||||
const int row_pair = tid % BLOCK;
|
||||
*reinterpret_cast<uint16_t*>(colwise_fp4 + (block_n + chunk_n * BLOCK + col) * M_PACKED + block_m / 2 + row_pair * 2) =
|
||||
tile[col * BLOCK + row_pair];
|
||||
}
|
||||
|
||||
if (lane == 0) {
|
||||
const int col = block_n + chunk_n * BLOCK + line;
|
||||
for (int chunk_m = 0; chunk_m < TILE_M / BLOCK; chunk_m++)
|
||||
store_scale(colwise_scale, col, block_m / BLOCK + chunk_m, M_SCALES, col_scale[chunk_n][chunk_m]);
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
} else {
|
||||
for (int chunk_m = 0; chunk_m < TILE_M / BLOCK; chunk_m++) {
|
||||
for (int chunk_n = 0; chunk_n < TILE_N / BLOCK; chunk_n++) {
|
||||
const int tile_m = block_m + chunk_m * BLOCK;
|
||||
const int tile_n = block_n + chunk_n * BLOCK;
|
||||
load_tile(tile, input, tile_m, tile_n);
|
||||
__syncthreads();
|
||||
|
||||
quantize_row(tile, rowwise_fp4, rowwise_scale, tile_m, tile_n, line, lane);
|
||||
const int row = lane * VALUES_PER_THREAD;
|
||||
const int col = tile_n + line;
|
||||
const Quantized4 result = quantize_col(tile, line, lane);
|
||||
store_fp4<true>(colwise_fp4, col, (tile_m + row) / 2, M_PACKED, result.fp4);
|
||||
if (lane == 0) store_scale(colwise_scale, col, tile_m / BLOCK, M_SCALES, result.scale);
|
||||
__syncthreads();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,13 @@ def _rmsnorm_fwd_fxn(x_in_p, eps, device):
|
||||
return rmsnorm_fwd(Tensor(x_in_p, device=device), eps)
|
||||
|
||||
def _rmsnorm_bwd(grad:UOp, call:UOp) -> tuple:
|
||||
x_normed = Tensor(call.gettuple(0)).float()
|
||||
outs = call.unbound_outputs
|
||||
x_normed = Tensor(outs[0]).float()
|
||||
do_float = Tensor(grad).float()
|
||||
d_x = Tensor(call.gettuple(1)) * (do_float - x_normed * (do_float * x_normed).mean(-1, keepdim=True))
|
||||
d_x = Tensor(outs[1]) * (do_float - x_normed * (do_float * x_normed).mean(-1, keepdim=True))
|
||||
return (d_x.cast(call.src[1].dtype).uop,)
|
||||
|
||||
def rmsnorm(x_in:Tensor, eps:float) -> tuple[Tensor, Tensor]:
|
||||
fxn = _rmsnorm_fwd_fxn(x_in.as_param(0).uop, eps, x_in.device)
|
||||
call = UOp.maketuple(fxn[0].uop, fxn[1].uop).call(x_in.uop, grad_fxn=_rmsnorm_bwd)
|
||||
return Tensor(call.gettuple(0)), Tensor(call.gettuple(1))
|
||||
outs = UOp.call_with_outputs((fxn[0].uop, fxn[1].uop), x_in.uop, grad_fxn=_rmsnorm_bwd)
|
||||
return Tensor(outs[0]), Tensor(outs[1])
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import functools, math
|
||||
from tinygrad import Tensor, dtypes
|
||||
from tinygrad.uop.ops import UOp, KernelInfo
|
||||
from tinygrad.renderer import Estimates
|
||||
from extra.llama_kernels import alloc_like
|
||||
|
||||
LOG2E = 1.4426950408889634
|
||||
|
||||
@functools.cache
|
||||
def _custom_swiglu(out:UOp, x_w13:UOp) -> UOp:
|
||||
rows, hidden = math.prod(x_w13.shape[:-1]), x_w13.shape[-1]//2
|
||||
n_elems = rows * hidden
|
||||
out, x_w13 = out.reshape(n_elems), x_w13.reshape(rows, 2*hidden)
|
||||
i = UOp.range(n_elems, 0)
|
||||
row, col = i // hidden, i % hidden
|
||||
act, gate = x_w13[row, col].cast(dtypes.float), x_w13[row, hidden+col].cast(dtypes.float)
|
||||
sigmoid = (1.0 + (-LOG2E * act).exp2()).reciprocal()
|
||||
store = out[i].store((act * sigmoid * gate).cast(out.dtype))
|
||||
return store.end(i).sink(arg=KernelInfo(f"swiglu_fwd_{n_elems}", estimates=Estimates(ops=5*n_elems, mem=6*n_elems)))
|
||||
|
||||
@functools.cache
|
||||
def _custom_swiglu_bwd(grad_out:UOp, x_w13:UOp, grad_act:UOp) -> UOp:
|
||||
rows, hidden = math.prod(x_w13.shape[:-1]), x_w13.shape[-1]//2
|
||||
n_elems = rows * hidden
|
||||
grad_out, x_w13, grad_act = grad_out.reshape(rows, 2*hidden), x_w13.reshape(rows, 2*hidden), grad_act.reshape(n_elems)
|
||||
i = UOp.range(n_elems, 0)
|
||||
row, col = i // hidden, i % hidden
|
||||
act, gate = x_w13[row, col].cast(dtypes.float), x_w13[row, hidden+col].cast(dtypes.float)
|
||||
grad = grad_act[i].cast(dtypes.float)
|
||||
sigmoid = (1.0 + (-LOG2E * act).exp2()).reciprocal()
|
||||
silu = act * sigmoid
|
||||
dact = grad_out[row, col].store((grad * (sigmoid + silu * (1.0 - sigmoid)) * gate).cast(grad_out.dtype))
|
||||
dgate = grad_out.after(dact)[row, hidden+col].store((grad * silu).cast(grad_out.dtype))
|
||||
return dgate.end(i).sink(arg=KernelInfo(f"swiglu_bwd_{n_elems}", estimates=Estimates(ops=10*n_elems, mem=10*n_elems)))
|
||||
|
||||
def _swiglu_bwd(gradient:UOp, kernel:UOp):
|
||||
_, x_w13 = kernel.src[1:]
|
||||
axis = x_w13.axis if isinstance(x_w13.device, tuple) else None
|
||||
grad_out = alloc_like(x_w13.shape, dtypes.bfloat16, x_w13.device, axis)
|
||||
grad_out, *_ = Tensor.custom_kernel(grad_out, Tensor(x_w13, device=x_w13.device), Tensor(gradient, device=x_w13.device),
|
||||
fxn=_custom_swiglu_bwd)
|
||||
return (None, grad_out.uop)
|
||||
|
||||
def swiglu(x_w13:Tensor) -> Tensor:
|
||||
assert x_w13.dtype == dtypes.bfloat16 and x_w13.ndim >= 2 and x_w13.shape[-1] % 32 == 0
|
||||
*prefix, two_k = x_w13.shape
|
||||
axis = x_w13.uop.axis if isinstance(x_w13.device, tuple) else None
|
||||
out = alloc_like((*prefix, two_k//2), dtypes.bfloat16, x_w13.device, axis)
|
||||
return Tensor.custom_kernel(out, x_w13, fxn=_custom_swiglu, grad_fxn=_swiglu_bwd)[0]
|
||||
@@ -3,7 +3,7 @@ import os
|
||||
# TODO: there is a timing bug without this
|
||||
os.environ["AMD_AQL"] = "1"
|
||||
|
||||
from tinygrad import Tensor, Device, GlobalCounters, Context
|
||||
from tinygrad import Tensor, Device, GlobalCounters, Context, dtypes
|
||||
from tinygrad.helpers import getenv, DEV
|
||||
from tinygrad.uop.ops import UOp, Ops, KernelInfo
|
||||
from tinygrad.renderer import Estimates
|
||||
@@ -37,7 +37,7 @@ def launchBenchmark(instruction, vgprIndices, dense=True, accum=False, **kwargs)
|
||||
gidx = UOp.special(NUM_WORKGROUPS, "gidx0")
|
||||
FLOPs = FLOPS_PER_MATMUL * NUM_WAVES * NUM_WORKGROUPS * INTERNAL_LOOP * INSTRUCTIONS_PER_LOOP
|
||||
sink = UOp.sink(A.base, threads, gidx, arg=KernelInfo(inst.op.name.lower(), estimates=Estimates(ops=FLOPs, mem=0)))
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple([UOp(Ops.INS, arg=x) for x in insts]))))
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple([UOp(Ops.INS, arg=(x, dtypes.void)) for x in insts]))))
|
||||
dummy = Tensor.zeros(1).contiguous().realize()
|
||||
out = Tensor.custom_kernel(dummy, fxn=fxn)[0]
|
||||
linear = out.schedule_linear()
|
||||
|
||||
@@ -5,9 +5,9 @@ from tinygrad.helpers import getenv, DEBUG
|
||||
|
||||
# https://github.com/facebookresearch/llama/blob/1076b9c51c77ad06e9d7ba8a4c6df775741732bd/llama/model.py#L47
|
||||
def precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0) -> Tensor:
|
||||
freqs = 1.0 / (theta ** (Tensor.arange(0, dim, 2)[:(dim // 2)] / dim))
|
||||
freqs = Tensor.arange(end).unsqueeze(dim=1) * freqs.unsqueeze(dim=0)
|
||||
return Tensor.stack(freqs.cos(), freqs.sin(), dim=-1).reshape(1, end, 1, dim//2, 2)
|
||||
freqs = 1.0 / (theta ** (Tensor.arange(0, dim, 2, dtype=dtypes.float32)[:(dim // 2)] / dim))
|
||||
freqs = Tensor.arange(end, dtype=dtypes.float32).unsqueeze(dim=1) * freqs.unsqueeze(dim=0)
|
||||
return Tensor.stack(freqs.cos(), freqs.sin(), dim=-1).cast(dtypes.default_float).reshape(1, end, 1, dim//2, 2)
|
||||
|
||||
# matches meta, non hugging face weights
|
||||
# (a+i*b) * (c+i*d) = (ac-bd) + i*(ad+bc)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from tinygrad import Tensor
|
||||
import os
|
||||
from tinygrad.tensor import _to_np_dtype
|
||||
from tinygrad.nn.onnx import OnnxRunner, OnnxValue
|
||||
import numpy as np
|
||||
import onnxruntime as ort
|
||||
ort_options = ort.SessionOptions()
|
||||
ort_options.log_severity_level = 3
|
||||
ort_options.intra_op_num_threads = os.cpu_count() or 1
|
||||
|
||||
def get_example_inputs(graph_inputs:dict[str, OnnxValue], config={}):
|
||||
"""
|
||||
|
||||
@@ -89,7 +89,8 @@ class TestBeamSearch(unittest.TestCase):
|
||||
s.apply_opt(Opt(OptOps.TC, 0, (-1, 0, 1)))
|
||||
up = prod([x for x, t in zip(s.full_shape, s.axis_types) if t in (AxisType.UPCAST, AxisType.UNROLL)])
|
||||
actions = get_kernel_actions(s, include_0=False, max_up=int(up))
|
||||
upcasted = [s for s in actions.values() if any(opt.op in (OptOps.UPCAST, OptOps.UNROLL) for opt in s.applied_opts)]
|
||||
upcasted = [s for s in actions.values() if any(o.op is OptOps.SPLIT and o.arg[1] in (AxisType.UPCAST, AxisType.UNROLL)
|
||||
for o in s.applied_opts)]
|
||||
assert len(upcasted) > 0, f"expected upcast/unroll actions after TC with max_up={up}, but got none"
|
||||
|
||||
def test_max_up(self):
|
||||
@@ -98,8 +99,8 @@ class TestBeamSearch(unittest.TestCase):
|
||||
s = Scheduler(ast, Device[Device.DEFAULT].renderer)
|
||||
for max_up in (2, 4):
|
||||
actions = get_kernel_actions(s, include_0=False, max_up=max_up)
|
||||
for up_opts in [s.applied_opts for s in actions.values() if any(opt.op in (OptOps.UPCAST, OptOps.UNROLL) for opt in s.applied_opts)]:
|
||||
assert len([opt for opt in up_opts if opt.arg > max_up]) == 0 and len([op for op in up_opts if op.arg <= max_up]) > 0
|
||||
up_opts = [o for s in actions.values() for o in s.applied_opts if o.op is OptOps.SPLIT and o.arg[1] in (AxisType.UPCAST, AxisType.UNROLL)]
|
||||
assert len([opt for opt in up_opts if opt.arg[0] > max_up]) == 0 and len([op for op in up_opts if op.arg[0] <= max_up]) > 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import os, sys, time
|
||||
from tinygrad.runtime.support.system import RemotePCIDevice
|
||||
from extra.remote.hcq1_remote import RemotePCIDevice
|
||||
|
||||
LAT_N_RUNS = 500
|
||||
THROUGHPUT_N_RUNS = 8
|
||||
@@ -18,7 +18,7 @@ if __name__ == "__main__":
|
||||
print(f"connected to {os.environ['REMOTE']}, device: {name}\n")
|
||||
|
||||
# ping (minimal server round-trip, no device I/O)
|
||||
from tinygrad.runtime.support.system import RemoteCmd
|
||||
from extra.remote.hcq1_remote import RemoteCmd
|
||||
sock = pci.sock
|
||||
for _ in range(10): RemotePCIDevice._rpc(sock, 0, RemoteCmd.PING)
|
||||
st = time.perf_counter()
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
from __future__ import annotations
|
||||
import os, mmap, array, functools, contextlib, itertools, struct, socket, subprocess, time, enum, atexit
|
||||
from tinygrad.helpers import getenv, temp, ceildiv, unwrap, fetch, system, _ensure_downloads_dir, DEBUG, flatten
|
||||
from tinygrad.runtime.support.hcq import FileIOInterface, MMIOInterface
|
||||
from tinygrad.runtime.support.system import PCIDevice, System
|
||||
|
||||
class RemoteCmd(enum.IntEnum):
|
||||
PROBE,MAP_BAR,MAP_SYSMEM_FD,CFG_READ,CFG_WRITE,RESET,MMIO_READ,MMIO_WRITE,MAP_SYSMEM,SYSMEM_READ,SYSMEM_WRITE,RESIZE_BAR,PING = range(13)
|
||||
|
||||
class RemoteMMIOInterface(MMIOInterface):
|
||||
def __init__(self, dev:RemotePCIDevice, residx:int, nbytes:int, fmt='B', off=0, rd_cmd=RemoteCmd.MMIO_READ, wr_cmd=RemoteCmd.MMIO_WRITE):
|
||||
self.dev, self.residx, self.nbytes, self.fmt, self.off, self.el_sz = dev, residx, nbytes, fmt, off, struct.calcsize(fmt)
|
||||
self.rd_cmd, self.wr_cmd = rd_cmd, wr_cmd
|
||||
|
||||
def __getitem__(self, index):
|
||||
sl = index if isinstance(index, slice) else slice(index, index + 1)
|
||||
start, stop = (sl.start or 0) * self.el_sz, (sl.stop or len(self)) * self.el_sz
|
||||
data = self.dev._bulk_read(self.rd_cmd, self.residx, self.off + start, stop - start)
|
||||
result = data if self.fmt == 'B' else list(struct.unpack(f'<{(stop - start) // self.el_sz}{self.fmt}', data))
|
||||
return result if isinstance(index, slice) else result[0]
|
||||
|
||||
def __setitem__(self, index, val):
|
||||
start = (index.start or 0) * self.el_sz if isinstance(index, slice) else index * self.el_sz
|
||||
data = (val if self.fmt == 'B' else struct.pack(f'<{len(val)}{self.fmt}', *val)) if isinstance(index, slice) else struct.pack(f'<{self.fmt}', val)
|
||||
self.dev._bulk_write(self.wr_cmd, self.residx, self.off + start, data)
|
||||
|
||||
def view(self, offset:int=0, size:int|None=None, fmt=None):
|
||||
return RemoteMMIOInterface(self.dev, self.residx, size or (self.nbytes - offset), fmt or self.fmt, self.off + offset, self.rd_cmd, self.wr_cmd)
|
||||
|
||||
class RemotePCIDevice(PCIDevice):
|
||||
_bulk_sent:int = 0
|
||||
_bulk_recv:int = 0
|
||||
_rpc_count:int = 0
|
||||
_start_time:float = 0.0
|
||||
|
||||
@staticmethod
|
||||
@functools.cache
|
||||
def remote_sock(host:str, port:int) -> socket.socket:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
sock.settimeout(getenv("REMOTE_TIMEOUT", 3))
|
||||
sock.connect((host, port))
|
||||
sock.settimeout(None)
|
||||
if DEBUG >= 1 and RemotePCIDevice._start_time == 0.0:
|
||||
RemotePCIDevice._start_time = time.perf_counter()
|
||||
def _print_stats():
|
||||
dt = time.perf_counter() - RemotePCIDevice._start_time
|
||||
sent_mb, recv_mb = RemotePCIDevice._bulk_sent / 1e6, RemotePCIDevice._bulk_recv / 1e6
|
||||
print(f"remote: sent {sent_mb:,.2f} MB ({sent_mb/dt:,.2f} MB/s), recv {recv_mb:,.2f} MB ({recv_mb/dt:,.2f} MB/s), "
|
||||
f"{RemotePCIDevice._rpc_count:,} roundtrips in {dt:.2f}s")
|
||||
atexit.register(_print_stats)
|
||||
return sock
|
||||
|
||||
@staticmethod
|
||||
@functools.cache
|
||||
def remote_list(vendor:int, devices:tuple[tuple[int, tuple[int, ...]], ...], base_class:int|None) -> list[tuple[socket.socket, str]]:
|
||||
payload = array.array('I', itertools.chain.from_iterable((m, d) for m, ds in devices for d in ds)).tobytes()
|
||||
def q(r:str) -> list[tuple[socket.socket, str]]:
|
||||
sock = RemotePCIDevice.remote_sock((host:=r.strip().split(":")[0]), (port:=int(r.strip().split(":")[1]) if ":" in r else 6667))
|
||||
data_len, _, _, _ = RemotePCIDevice._rpc(sock, 0, RemoteCmd.PROBE, base_class or 0, len(payload), vendor, payload=payload)
|
||||
return [(sock, f"remote:{host}:{port}:{d}") for d in RemotePCIDevice._recvall(sock, data_len).decode().split('\n')]
|
||||
return flatten([q(r) for r in getenv("REMOTE", "").split(",") if r.strip()])
|
||||
|
||||
@staticmethod
|
||||
def _recvall(sock:socket.socket, n:int) -> bytes:
|
||||
data = b''
|
||||
while len(data) < n and (chunk:=sock.recv(n - len(data))): data += chunk
|
||||
if len(data) < n: raise RuntimeError("Connection closed")
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def _rpc(sock:socket.socket, dev_id:int, cmd:int, *args:int, bar:int=0, readout_size:int=0, payload:bytes=b'', has_fd=False):
|
||||
sock.sendall(struct.pack('<BIIQQQ', cmd, dev_id, bar, *(*args, 0, 0, 0)[:3]) + payload)
|
||||
if has_fd:
|
||||
msg, anc, _, _ = sock.recvmsg(17, socket.CMSG_LEN(4))
|
||||
fd = struct.unpack('<i', anc[0][2][:4])[0]
|
||||
else: msg, fd = RemotePCIDevice._recvall(sock, 17), None
|
||||
if (resp:=struct.unpack('<BQQ', msg))[0] != 0:
|
||||
raise RuntimeError(f"RPC failed: {RemotePCIDevice._recvall(sock, resp[1]).decode('utf-8') if resp[1] > 0 else 'unknown error'}")
|
||||
RemotePCIDevice._rpc_count += 1
|
||||
return (resp[1], resp[2]) + ((RemotePCIDevice._recvall(sock, readout_size) if readout_size > 0 else None),) + (fd,)
|
||||
|
||||
def __init__(self, devpref:str, pcibus:str, sock:socket.socket):
|
||||
self.sock, self.pcibus, self.dev_id = sock, pcibus, int(pcibus.split(':')[-1]) if ':' in pcibus else 0
|
||||
self.peer_group = sock.getpeername()[0]
|
||||
for buft in [socket.SO_SNDBUF, socket.SO_RCVBUF]: self.sock.setsockopt(socket.SOL_SOCKET, buft, 64 << 20)
|
||||
|
||||
self.lock_fd = System.flock_acquire(f"{devpref.lower()}_{pcibus.lower()}.lock")
|
||||
|
||||
def _bulk_read(self, cmd:int, idx:int, offset:int, size:int) -> bytes:
|
||||
RemotePCIDevice._bulk_recv += size
|
||||
return unwrap(self._rpc(self.sock, self.dev_id, cmd, offset, size, bar=idx, readout_size=size)[2])
|
||||
def _bulk_write(self, cmd:int, idx:int, offset:int, data:bytes):
|
||||
RemotePCIDevice._bulk_sent += len(data)
|
||||
self.sock.sendall(struct.pack('<BIIQQQ', cmd, self.dev_id, idx, offset, len(data), 0) + data)
|
||||
|
||||
def alloc_sysmem(self, size:int, vaddr:int=0, contiguous:bool=False) -> tuple[MMIOInterface, list[int]]:
|
||||
paddrs_len, handle, _, _ = self._rpc(self.sock, self.dev_id, RemoteCmd.MAP_SYSMEM, size, int(contiguous))
|
||||
paddrs = list(struct.unpack(f'<{paddrs_len // 8}Q', self._recvall(self.sock, paddrs_len)))
|
||||
return RemoteMMIOInterface(self, handle, size, fmt='B', rd_cmd=RemoteCmd.SYSMEM_READ, wr_cmd=RemoteCmd.SYSMEM_WRITE), paddrs
|
||||
|
||||
def reset(self): self._rpc(self.sock, self.dev_id, RemoteCmd.RESET)
|
||||
def read_config(self, offset:int, size:int): return self._rpc(self.sock, self.dev_id, RemoteCmd.CFG_READ, offset, size)[0]
|
||||
def write_config(self, offset:int, value:int, size:int): self._rpc(self.sock, self.dev_id, RemoteCmd.CFG_WRITE, offset, size, value)
|
||||
|
||||
@functools.cache
|
||||
def bar_info(self, bar_idx:int) -> tuple[int, int]: return self._rpc(self.sock, self.dev_id, RemoteCmd.MAP_BAR, bar=bar_idx)[:2]
|
||||
def map_bar(self, bar:int, off:int=0, addr:int=0, size:int|None=None, fmt='B') -> MMIOInterface:
|
||||
return RemoteMMIOInterface(self, bar, size or self.bar_info(bar)[1], fmt).view(off, size, fmt)
|
||||
def resize_bar(self, bar_idx:int): self._rpc(self.sock, self.dev_id, RemoteCmd.RESIZE_BAR, bar=bar_idx)
|
||||
|
||||
class APLRemotePCIDevice(RemotePCIDevice):
|
||||
APP_PATH = "/Applications/TinyGPU.app/Contents/MacOS/TinyGPU"
|
||||
|
||||
@classmethod
|
||||
def ensure_app(cls):
|
||||
commit = "c0d024f9ff0e1dc8fdf217f255da7101d91e8323"
|
||||
app_name = f"TinyGPU_{commit}.zip"
|
||||
if (_ensure_downloads_dir() / app_name).is_file() and os.path.exists(cls.APP_PATH): return
|
||||
print("Downloading TinyGPU.app...")
|
||||
with contextlib.suppress(RuntimeError): system("pkill -f TinyGPU")
|
||||
system(f"ditto -xk {fetch(f'https://github.com/tinygrad/tinygpu_releases/raw/{commit}/TinyGPU.zip', name=app_name)} /Applications")
|
||||
print(system(f"{cls.APP_PATH} install"))
|
||||
|
||||
def __init__(self, devpref:str, pcibus:str):
|
||||
self.ensure_app()
|
||||
sock_path, sock = getenv("APL_REMOTE_SOCK", temp("tinygpu.sock")), socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
for i in range(100):
|
||||
with contextlib.suppress(ConnectionRefusedError, FileNotFoundError):
|
||||
sock.connect(sock_path)
|
||||
break
|
||||
if i == 0: subprocess.Popen([self.APP_PATH, "server", sock_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
time.sleep(0.05)
|
||||
else: raise RuntimeError(f"Failed to connect to TinyGPU server at {sock_path}.")
|
||||
super().__init__(devpref, "usb4", sock=sock)
|
||||
|
||||
def alloc_sysmem(self, size:int, vaddr:int=0, contiguous:bool=False) -> tuple[MMIOInterface, list[int]]:
|
||||
mapped_size, _, _, fd = self._rpc(self.sock, self.dev_id, RemoteCmd.MAP_SYSMEM_FD, size, int(contiguous), has_fd=True)
|
||||
memview = MMIOInterface(FileIOInterface(fd=fd).mmap(0, mapped_size, mmap.PROT_READ | mmap.PROT_WRITE, mmap.MAP_SHARED, 0), mapped_size, fmt='B')
|
||||
|
||||
# paddrs are returned as (paddr, size) pairs until a (paddr=0, size=0) terminator in the beginning of the mapping.
|
||||
paddrs_raw = list(itertools.takewhile(lambda p: p[1] != 0, zip(memview.view(fmt='Q')[0::2], memview.view(fmt='Q')[1::2])))
|
||||
return memview, [p + i for p, sz in paddrs_raw for i in range(0, sz, 0x1000)][:ceildiv(size, 0x1000)]
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
import socket, struct, sys
|
||||
from tinygrad.runtime.support.system import PCIDevice, RemoteCmd, System
|
||||
from tinygrad.runtime.support.system import PCIDevice, System
|
||||
from extra.remote.hcq1_remote import RemoteCmd
|
||||
from tinygrad.helpers import DEBUG, OSX
|
||||
|
||||
def resp(resp0=0, resp1=0, status=0): return struct.pack('<BQQ', status, resp0, resp1)
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
# Runbook: Llama 3 8B Training on DigitalOcean MI350X
|
||||
|
||||
## Machine Specs
|
||||
- 8x MI350X GPUs (gfx950, device ID 75b0), 288GB VRAM each
|
||||
- 2TB RAM, 192 CPUs, 2TB disk
|
||||
- ROCm 7.14 at `/opt/rocm` (NOT `/opt/rocm-7.1.1` like the submission scripts assume)
|
||||
- Python 3.12
|
||||
|
||||
## Phase 1: System Setup
|
||||
|
||||
### 1.1 Install packages
|
||||
```bash
|
||||
apt-get update
|
||||
apt-get install -y python3-pip python3-venv git tmux rclone clang
|
||||
```
|
||||
|
||||
### 1.2 Install Python deps
|
||||
```bash
|
||||
python3 -m pip install --break-system-packages --ignore-installed typing-extensions numpy tqdm wandb tiktoken sentencepiece
|
||||
```
|
||||
Note: `--ignore-installed typing-extensions` is needed because the base image ships typing-extensions 4.10.0 without a RECORD file, so pip cannot uninstall it.
|
||||
|
||||
### 1.3 Install ROCm dev headers
|
||||
The base image has ROCm runtime but NOT the HIP dev headers. Need:
|
||||
```bash
|
||||
apt-get install -y amdrocm-core-dev
|
||||
```
|
||||
This installs `hip/hip_runtime.h` at `/opt/rocm/core-7.14/include/hip/hip_runtime.h`.
|
||||
The symlink `/opt/rocm/include` → `/opt/rocm/core-7.14/include` makes it available at `/opt/rocm/include/hip/hip_runtime.h`.
|
||||
|
||||
### 1.4 Configure ROCm comgr
|
||||
ROCm 7.14 ships comgr 3.3 at `/opt/rocm/lib/libamd_comgr.so`. tinygrad's DLL loader needs explicit env vars to find it (it searches for `libcomgr.so*` by default, not `libamd_comgr.so*`). Set these in the run command:
|
||||
```bash
|
||||
export COMGR_PATH=/opt/rocm/lib/libamd_comgr.so
|
||||
export COMGR_3_PATH=/opt/rocm/lib/libamd_comgr.so
|
||||
```
|
||||
Also add ROCm libs to ldconfig so comgr's shared library dependencies resolve:
|
||||
```bash
|
||||
cat > /etc/ld.so.conf.d/rocm.conf << 'EOF'
|
||||
/opt/rocm/lib
|
||||
/opt/rocm/lib/llvm/lib
|
||||
/opt/rocm/lib/rocm_sysdeps/lib
|
||||
EOF
|
||||
ldconfig
|
||||
```
|
||||
|
||||
### 1.5 Install geohot tmux config
|
||||
```bash
|
||||
curl -sL https://raw.githubusercontent.com/geohot/configuration/master/.tmux.conf -o ~/.tmux.conf
|
||||
```
|
||||
|
||||
### 1.6 Verify GPU PCI access
|
||||
The AM userspace driver accesses the GPUs directly over PCI. Do not load `amdgpu`. `/dev/kfd` is not required.
|
||||
```bash
|
||||
rmmod amdgpu
|
||||
lspci -nnk -d 1002:
|
||||
```
|
||||
The MI350X devices should not show a `Kernel driver in use: amdgpu`.
|
||||
|
||||
## Phase 2: Clone tinygrad
|
||||
```bash
|
||||
cd /root
|
||||
git clone https://github.com/tinygrad/tinygrad.git
|
||||
cd tinygrad
|
||||
python3 -m pip install --break-system-packages -e .
|
||||
```
|
||||
|
||||
## Phase 3: Download C4 Dataset
|
||||
|
||||
The C4 data is on the MLCommons Cloudflare R2 bucket in Megatron-LM indexed format.
|
||||
|
||||
```bash
|
||||
rclone config create mlc-training s3 provider=Cloudflare \
|
||||
access_key_id=76ea42eadb867e854061a1806220ee1e \
|
||||
secret_access_key=a53625c4d45e3ca8ac0df8a353ea3a41ffc3292aa25259addd8b7dc5a6ce2936 \
|
||||
endpoint=c2686074cb2caf5cbaf6d134bdba8b47.r2.cloudflarestorage.com
|
||||
|
||||
mkdir -p /raid/datasets/c4-8b
|
||||
(rclone copy mlc-training:mlcommons-training-wg-public/llama3_1/datasets/c4/llama3_1_8b/ /raid/datasets/c4-8b/ -P && \
|
||||
PYTHONPATH=. python3 examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama31_8b/implementations/tinybox_8xMI350X/buid_dataset_cache.py) \
|
||||
> /root/dataset_cache.log 2>&1 &
|
||||
```
|
||||
Leave this running and proceed to the beam step while the dataset downloads and its cache builds.
|
||||
|
||||
### 3.1 Smoke test (beam search, 2 layers, fake data)
|
||||
Always run beam first to validate the pipeline:
|
||||
```bash
|
||||
tmux new-session -d -s beam 'cd /root/tinygrad && COMGR_PATH=/opt/rocm/lib/libamd_comgr.so COMGR_3_PATH=/opt/rocm/lib/libamd_comgr.so CC=/opt/rocm/core-7.14/lib/llvm/bin/clang DEV=PCI+AMD:HIP ROCM_PATH=/opt/rocm bash examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama31_8b/implementations/tinybox_8xMI350X/dev_beam.sh 2>&1 | tee /root/beam.log'
|
||||
```
|
||||
|
||||
The beam test runs 10 training steps with 2 layers. Expected results:
|
||||
- ~0.29s per step after warmup
|
||||
- ~700K GFLOPS, ~7% MFU (low because only 2 layers)
|
||||
- ~380 GB VRAM used
|
||||
- Loss stable at ~12.55 with random init
|
||||
|
||||
Files downloaded (~85GB total, ~6 minutes):
|
||||
- `c4-train.en_6_text_document.bin` (79 GB)
|
||||
- `c4-train.en_6_text_document.idx` (870 MB)
|
||||
- `c4-validation-91205-samples.en_text_document.bin` (159 MB)
|
||||
- `c4-validation-91205-samples.en_text_document.idx` (1.8 MB)
|
||||
- `LICENSE.txt`, `NOTICE.txt`
|
||||
|
||||
**Wait for rclone to fully complete before starting training.** Starting training while the dataset is still downloading will read a truncated .bin file, causing `ValueError: all input arrays must have the same shape` in the dataloader. The stale `.index_cache` and `.blend_cache` files must also be deleted if this happens:
|
||||
```bash
|
||||
rm -f /raid/datasets/c4-8b/*.index_cache /raid/datasets/c4-8b/*.blend_cache
|
||||
```
|
||||
|
||||
## Phase 4: wandb Login
|
||||
```bash
|
||||
wandb login
|
||||
```
|
||||
Enter API key from https://wandb.ai/authorize
|
||||
|
||||
Alternatively, pass the key directly:
|
||||
```bash
|
||||
wandb login <API_KEY>
|
||||
```
|
||||
|
||||
## Phase 5: Run Training
|
||||
|
||||
Run training in tmux so it survives SSH disconnects:
|
||||
```bash
|
||||
tmux new-session -d -s train 'cd /root/tinygrad && COMGR_PATH=/opt/rocm/lib/libamd_comgr.so COMGR_3_PATH=/opt/rocm/lib/libamd_comgr.so CC=/opt/rocm/core-7.14/lib/llvm/bin/clang DEV=PCI+AMD:HIP ROCM_PATH=/opt/rocm WANDB=1 bash examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama31_8b/implementations/tinybox_8xMI350X/dev_run.sh 2>&1 | tee /root/train.log'
|
||||
```
|
||||
Attach with `tmux attach -t train`.
|
||||
|
||||
### 5.1 Full training run
|
||||
```bash
|
||||
tmux new-session -d -s train 'cd /root/tinygrad && COMGR_PATH=/opt/rocm/lib/libamd_comgr.so COMGR_3_PATH=/opt/rocm/lib/libamd_comgr.so CC=/opt/rocm/core-7.14/lib/llvm/bin/clang DEV=PCI+AMD:HIP ROCM_PATH=/opt/rocm WANDB=1 bash examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama31_8b/implementations/tinybox_8xMI350X/dev_run.sh 2>&1 | tee /root/train.log'
|
||||
```
|
||||
|
||||
## Environment Variable Reference
|
||||
|
||||
| Variable | Value | Why |
|
||||
|---|---|---|
|
||||
| `COMGR_PATH` | `/opt/rocm/lib/libamd_comgr.so` | tinygrad's DLL loader needs explicit path to find comgr 3.3 |
|
||||
| `COMGR_3_PATH` | `/opt/rocm/lib/libamd_comgr.so` | comgr 3.x uses a separate `comgr_3` module with its own path var |
|
||||
| `CC` | `/opt/rocm/core-7.14/lib/llvm/bin/clang` | System clang doesn't know gfx950; must use ROCm's bundled clang |
|
||||
| `DEV` | `PCI+AMD:HIP` | Force HIPRenderer (comgr-based) over HIPCCRenderer (hipcc subprocess) |
|
||||
| `ROCM_PATH` | `/opt/rocm` | Script defaults to `/opt/rocm-7.1.1` which doesn't exist |
|
||||
| `WANDB` | `1` | Enable wandb logging (off by default) |
|
||||
|
||||
## Architecture
|
||||
|
||||
| Component | Source file |
|
||||
|---|---|
|
||||
| Model | `examples/mlperf/models/flat_llama.py` — FlatTransformer, FP8 MXFP4 weights, fused QKV, flash attention |
|
||||
| Trainer | `examples/mlperf/model_train.py` → `train_llama3()` |
|
||||
| Optimizer | `examples/mlperf/optim.py` — GradAccClipAdamW, master weights, FP8 re-quant |
|
||||
| LR schedule | `examples/mlperf/lr_schedulers.py` — CosineAnnealingLRWithWarmup |
|
||||
| Dataloader | `examples/mlperf/dataloader.py` — Megatron-LM indexed bin format |
|
||||
| ASM GEMM | `extra/gemm/cdna_asm_gemm.py` — gfx950 MFMA assembly, MXFP4 |
|
||||
| Flash attention | `extra/thunder/amd/fa.py` |
|
||||
| Fused kernels | `extra/llama_kernels/` — rmsnorm, silu, quantize, fused_ce |
|
||||
| GPU driver | `tinygrad/runtime/ops_amd.py` — HCQ, using the AM userspace PCI interface |
|
||||
| Renderer | `tinygrad/renderer/cstyle.py` — HIPRenderer for gfx950 |
|
||||
| comgr compiler | `tinygrad/runtime/support/compiler_amd.py` — HIPCompiler using comgr 3.3 |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `'hip/hip_runtime.h' file not found`
|
||||
Install `amdrocm-core-dev`:
|
||||
```bash
|
||||
apt-get install -y amdrocm-core-dev
|
||||
```
|
||||
|
||||
### `'gfx950' is not a recognized processor` + LLVM crash
|
||||
System clang doesn't know gfx950. Set `CC=/opt/rocm/core-7.14/lib/llvm/bin/clang`.
|
||||
|
||||
### `comgr not available: try setting COMGR_PATH?`
|
||||
Add ROCm libs to ldconfig and set `COMGR_PATH` and `COMGR_3_PATH`:
|
||||
```bash
|
||||
# /etc/ld.so.conf.d/rocm.conf should contain /opt/rocm/lib paths
|
||||
ldconfig
|
||||
```
|
||||
|
||||
### `comgr not available: try setting COMGR_3_PATH?`
|
||||
comgr 3.x uses a separate module. Set `COMGR_3_PATH=/opt/rocm/lib/libamd_comgr.so` too.
|
||||
|
||||
### `No such file or directory: 'clang'`
|
||||
Install clang: `apt-get install -y clang` (for CPU compilation).
|
||||
For gfx950 HIP compilation, comgr (not clang) is used — ensure the ROCm 7.14 comgr 3.3 is properly loaded via `COMGR_PATH` and `COMGR_3_PATH`.
|
||||
|
||||
## Appendix: KVM Virtualization Observations
|
||||
|
||||
### Virtualization detection
|
||||
```
|
||||
$ systemd-detect-virt
|
||||
kvm
|
||||
$ lspci -nn | grep AMD
|
||||
83:00.0 ... Device [1002:75b0]
|
||||
```
|
||||
CPU flags include `hypervisor`. `dmesg` shows `Hypervisor detected: KVM`.
|
||||
|
||||
### No fan control
|
||||
No `fan*` or `pwm*` hwmon entries exist. Only `temp*`, `power*`, `freq*` are exposed. GPU temps read 56-63°C, power ~265W per GPU.
|
||||
@@ -1,4 +1,4 @@
|
||||
import os, subprocess, sys, shlex
|
||||
import os, subprocess, sys, shlex, pickle
|
||||
from pathlib import Path
|
||||
from tinygrad.helpers import temp, getenv
|
||||
|
||||
@@ -23,5 +23,8 @@ if __name__ == "__main__":
|
||||
# AM_RESET=1 gets a clear trace, does not work on mi300 machines
|
||||
subprocess.run([sys.executable, *shlex.split(test)], cwd=EXAMPLES_DIR.parent.parent.parent,
|
||||
env={**os.environ, "DEV":"AMD", "AM_RESET":"1" if not arch.startswith("gfx9") else "0", "VIZ":"-2", "PYTHONPATH":"."})
|
||||
with open(PROFILE_PATH, "rb") as f: events = pickle.load(f)
|
||||
with open(PROFILE_PATH, "wb") as f:
|
||||
pickle.dump([e for e in events if type(e).__name__ in {"ProfilePMCEvent", "ProfileSQTTEvent", "ProfileProgramEvent"}], f)
|
||||
PROFILE_PATH.rename(dest:=EXAMPLES_DIR/arch/f"profile_{name}_run_{i}.pkl")
|
||||
print(f"saved SQTT trace to {dest}")
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+26
-99
@@ -1,13 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
import ctypes, pathlib, argparse, pickle, dataclasses, threading, itertools
|
||||
from decimal import Decimal
|
||||
from typing import Generator
|
||||
from tinygrad.helpers import temp, unwrap, DEBUG
|
||||
from tinygrad.runtime.ops_amd import ProfileSQTTEvent
|
||||
from tinygrad.runtime.autogen import rocprof
|
||||
from tinygrad.renderer.amd.dsl import Inst
|
||||
from tinygrad.helpers import ProfileEvent, ProfileRangeEvent, ProfilePointEvent
|
||||
from tinygrad.device import ProfileProgramEvent
|
||||
from tinygrad.device import ProfileDeviceEvent, ProfileProgramEvent
|
||||
from test.amd.disasm import disasm
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
@@ -39,17 +37,18 @@ class WaveExec(WaveSlot):
|
||||
insts_array = (struct*(len(self.insts)//sz)).from_buffer(self.insts)
|
||||
for inst in insts_array:
|
||||
inst_typ = rocprof.enum_rocprofiler_thread_trace_decoder_inst_category_t.get(inst.category)
|
||||
yield InstExec(inst_typ, inst.pc.address, inst.stall, inst.duration, inst.time)
|
||||
yield InstExec(inst_typ.replace("ROCPROFILER_THREAD_TRACE_DECODER_", "") if inst_typ else "UNKNOWN",
|
||||
inst.pc.address, inst.stall, inst.duration, inst.time)
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class OccEvent(WaveSlot):
|
||||
time:int
|
||||
start:int
|
||||
|
||||
RunKey = tuple[str, int]
|
||||
RunKey = tuple[int, int]
|
||||
|
||||
class _ROCParseCtx:
|
||||
def __init__(self, sqtt_evs:list[ProfileSQTTEvent], disasms:dict[str, dict[int, Inst]]):
|
||||
def __init__(self, sqtt_evs:list[ProfileSQTTEvent], disasms:dict[int, dict[int, Inst]]):
|
||||
self.sqtt_evs, self.disasms = iter(sqtt_evs), {k:{k2:(disasm(v2), v2.size()) for k2,v2 in v.items()} for k,v in disasms.items()}
|
||||
self.inst_execs:dict[RunKey, list[WaveExec]] = {}
|
||||
self.occ_events:dict[RunKey, list[OccEvent]] = {}
|
||||
@@ -76,7 +75,7 @@ class _ROCParseCtx:
|
||||
self.inst_execs.setdefault(unwrap(self.active_run), []).append(WaveExec(ev.wave_id, ev.cu, ev.simd, unwrap(self.active_se), ev.begin_time,
|
||||
ev.end_time, insts_blob))
|
||||
|
||||
def decode(sqtt_evs:list[ProfileSQTTEvent], disasms:dict[str, dict[int, Inst]]) -> _ROCParseCtx:
|
||||
def decode(sqtt_evs:list[ProfileSQTTEvent], disasms:dict[int, dict[int, Inst]]) -> _ROCParseCtx:
|
||||
ROCParseCtx = _ROCParseCtx(sqtt_evs, disasms)
|
||||
|
||||
@rocprof.rocprof_trace_decoder_se_data_callback_t
|
||||
@@ -129,83 +128,9 @@ def decode(sqtt_evs:list[ProfileSQTTEvent], disasms:dict[str, dict[int, Inst]])
|
||||
raise exc
|
||||
return ROCParseCtx
|
||||
|
||||
def unpack_occ(viz_data, i:int, j:int, key:tuple[str, int], data:list, p:ProfileProgramEvent, target:str) -> dict:
|
||||
from tinygrad.viz.serve import amd_decode, create_step, row_tuple
|
||||
steps = viz_data.ctxs[i]["steps"]
|
||||
if len(steps[j+1:]) > 0: return {"steps":[{k:v for k,v in s.items() if k != "data"} for s in steps[j+1:]]}
|
||||
base = unwrap(p.base)
|
||||
disasm:dict[int, Inst] = {addr+base:inst for addr,inst in amd_decode(unwrap(p.lib), target).items()}
|
||||
rctx = decode(data, {p.tag:disasm})
|
||||
cu_events:dict[str, list[ProfileEvent]] = {}
|
||||
# ** inst traces
|
||||
wave_insts:dict[str, dict[str, dict]] = {}
|
||||
inst_units:dict[str, itertools.count] = {}
|
||||
for w in rctx.inst_execs.get(key, []):
|
||||
if (u:=w.wave_loc) not in inst_units: inst_units[u] = itertools.count(0)
|
||||
n = next(inst_units[u])
|
||||
if (events:=cu_events.get(w.cu_loc)) is None: cu_events[w.cu_loc] = events = []
|
||||
events.append(ProfileRangeEvent(f"SIMD:{w.simd}", loc:=f"INST WAVE:{w.wave_id} N:{n}", Decimal(w.begin_time), Decimal(w.end_time)))
|
||||
wave_insts.setdefault(w.cu_loc, {})[f"{u} N:{n}"] = {"wave":w, "disasm":disasm, "prg":p, "run_number":n, "loc":loc}
|
||||
# ** occ traces (only WAVESTART/WAVEEND)
|
||||
units:dict[str, itertools.count] = {}
|
||||
wave_start:dict[str, int] = {}
|
||||
for occ in rctx.occ_events.get(key, []):
|
||||
if (u:=occ.wave_loc) not in units: units[u] = itertools.count(0)
|
||||
if u in inst_units: continue
|
||||
if occ.start: wave_start[u] = occ.time
|
||||
else:
|
||||
if (events:=cu_events.get(occ.cu_loc)) is None: cu_events[occ.cu_loc] = events = []
|
||||
events.append(ProfileRangeEvent(f"SIMD:{occ.simd}", f"OCC WAVE:{occ.wave_id} N:{next(units[u])}", Decimal(wave_start.pop(u)),Decimal(occ.time)))
|
||||
# ** split graph by CU
|
||||
for cu in sorted(cu_events, key=row_tuple):
|
||||
steps.append(create_step(f"{cu} {len(cu_events[cu])}", ("/cu-sqtt", i, len(steps)), depth=1,
|
||||
data=[ProfilePointEvent(unit, "start", unit, ts=Decimal(0)) for unit in units]+cu_events[cu]))
|
||||
for k in sorted(wave_insts.get(cu, []), key=row_tuple):
|
||||
wd = wave_insts[cu][k]
|
||||
steps.append(create_step(k.replace(cu, ""), ("/amd-sqtt-insts", i, len(steps)), loc=wd["loc"], depth=2,
|
||||
data={"fxn":unpack_insts, "args":(wd,)}))
|
||||
return {"steps":[{k:v for k,v in s.items() if k != "data"} for s in steps[j+1:]]}
|
||||
|
||||
def unpack_insts(viz_data, i:int, j:int, data:dict) -> dict:
|
||||
columns = ["PC", "Instruction", "Hits", "Cycles", "Stall", "Type"]
|
||||
inst_columns = ["N", "Clk", "Idle", "Dur", "Stall"]
|
||||
# Idle: The total time gap between the completion of previous instruction and the beginning of the current instruction.
|
||||
# The idle time can be caused by:
|
||||
# * Arbiter loss
|
||||
# * Source or destination register dependency
|
||||
# * Instruction cache miss
|
||||
# Stall: The total number of cycles the hardware pipe couldn't issue an instruction.
|
||||
# Duration: Total latency in cycles, defined as "Stall time + Issue time" for gfx9 or "Stall time + Execute time" for gfx10+.
|
||||
prev_instr = (w:=data["wave"]).begin_time
|
||||
pc_to_inst = data["disasm"]
|
||||
start_pc = None
|
||||
rows:dict[int, dict] = {}
|
||||
for pc, inst in pc_to_inst.items():
|
||||
if start_pc is None: start_pc = pc
|
||||
rows[pc] = {"pc":pc-start_pc, "inst":str(inst), "hit_count":0, "dur":0, "stall":0, "type":"", "hits":{"cols":inst_columns, "rows":[]}}
|
||||
for e in w.unpack_insts():
|
||||
if not (inst:=rows[e.pc]).get("type"): inst["type"] = str(e.typ).split("_")[-1]
|
||||
inst["hit_count"] += 1
|
||||
inst["dur"] += e.dur
|
||||
inst["stall"] += e.stall
|
||||
inst["hits"]["rows"].append((inst["hit_count"]-1, e.time, max(0, e.time-prev_instr), e.dur, e.stall))
|
||||
prev_instr = max(prev_instr, e.time + e.dur)
|
||||
summary = [{"label":"Total Cycles", "value":w.end_time-w.begin_time}, {"label":"SE", "value":w.se}, {"label":"CU", "value":w.cu},
|
||||
{"label":"SIMD", "value":w.simd}, {"label":"Wave ID", "value":w.wave_id}, {"label":"Run number", "value":data["run_number"]}]
|
||||
return {"rows":[tuple(v.values()) for v in rows.values()], "cols":columns, "metadata":[summary], "ref":viz_data.ref_map.get(data["prg"].name)}
|
||||
|
||||
def print_data(data:dict) -> None:
|
||||
from tabulate import tabulate
|
||||
# plaintext
|
||||
if "src" in data: print(data["src"])
|
||||
# table format
|
||||
elif "cols" in data:
|
||||
print(tabulate([r[:len(data["cols"])] for r in data["rows"]], headers=data["cols"], tablefmt="github"))
|
||||
|
||||
def main() -> None:
|
||||
import tinygrad.viz.serve as viz
|
||||
from tinygrad.uop.ops import RewriteTrace
|
||||
data = viz.VizData()
|
||||
from tabulate import tabulate
|
||||
from tinygrad.viz.serve import amd_decode
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--profile', type=pathlib.Path, metavar="PATH", help='Path to profile (optional file, default: latest profile)',
|
||||
@@ -216,26 +141,28 @@ def main() -> None:
|
||||
|
||||
with args.profile.open("rb") as f: profile = pickle.load(f)
|
||||
|
||||
viz.get_profile(profile, data=data)
|
||||
|
||||
# List all kernels
|
||||
if args.kernel is None:
|
||||
for c in data.ctxs:
|
||||
print(c["name"])
|
||||
for s in c["steps"]: print(" "+s["name"])
|
||||
for p in profile:
|
||||
if isinstance(p, ProfileProgramEvent) and p.device.startswith("AMD"): print(p.name)
|
||||
return None
|
||||
|
||||
# Find kernel trace
|
||||
trace = next((c for c in data.ctxs if c["name"] == f"SQTT {args.kernel}"), None)
|
||||
if not trace: raise RuntimeError(f"no matching trace for {args.kernel}")
|
||||
n = 0
|
||||
for s in trace["steps"]:
|
||||
if "PKTS" in s["name"]: continue
|
||||
print(s["name"])
|
||||
ret = viz.get_render(data, s["query"])
|
||||
print_data(ret)
|
||||
n += 1
|
||||
if n > args.n: break
|
||||
prg = next((p for p in profile if isinstance(p, ProfileProgramEvent) and p.name == args.kernel), None)
|
||||
dev = next((p for p in profile if isinstance(p, ProfileDeviceEvent) and p.device == prg.device), None)
|
||||
assert prg is not None and dev is not None, "must have program binary and device props"
|
||||
target = f"gfx{dev.props['gfx_target_version']//1000}"
|
||||
sqtt = [p for p in profile if isinstance(p, ProfileSQTTEvent) and p.kern == prg.tag]
|
||||
|
||||
pc_to_inst = {addr+prg.base:inst for addr,inst in amd_decode(prg.lib, target).items()}
|
||||
rctx = decode(sqtt, {prg.tag:pc_to_inst})
|
||||
waves = sorted(itertools.chain.from_iterable(rctx.inst_execs.values()), key=lambda w:(w.se, w.cu, w.simd, w.wave_id, w.begin_time))
|
||||
if not waves: raise RuntimeError(f"no instruction traces for {args.kernel}")
|
||||
run_numbers:dict[str, itertools.count] = {}
|
||||
for w in itertools.islice(waves, args.n):
|
||||
if w.wave_loc not in run_numbers: run_numbers[w.wave_loc] = itertools.count()
|
||||
print(f"{w.wave_loc} N:{next(run_numbers[w.wave_loc])} Total Cycles:{w.end_time-w.begin_time}")
|
||||
rows = [(e.time, f"0x{e.pc:x}", pc_to_inst[e.pc], e.typ, e.dur, e.stall) for e in w.unpack_insts()]
|
||||
print(tabulate(rows, headers=("Timestamp", "PC", "Instruction", "Type", "Duration", "Stall"), tablefmt="github"))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
+57
-37
@@ -2,7 +2,7 @@ import math, pathlib, functools, struct
|
||||
|
||||
from tinygrad import Device, Tensor
|
||||
from tinygrad.dtype import DTypeLike, dtypes
|
||||
from tinygrad.helpers import DEBUG
|
||||
from tinygrad.helpers import DEBUG, getenv
|
||||
from tinygrad.renderer import Estimates
|
||||
from tinygrad.runtime.support.compiler_amd import HIPCCCompiler
|
||||
from tinygrad.runtime.support.elf import elf_loader
|
||||
@@ -19,16 +19,33 @@ def _sharded_empty(shape:Tensor, ref:Tensor, axis:int|None, dtype:DTypeLike|None
|
||||
@functools.cache
|
||||
def custom_fused_qkv_rope_forward(q:UOp, k:UOp, v:UOp, xqkv:UOp, freqs_cis:UOp,
|
||||
device:str, arch:str, B:int, N:int, H:int, H_KV:int, D:int):
|
||||
code = (pathlib.Path(__file__).parent / "fused_qkv_rope.cpp").read_text()
|
||||
threads = 256
|
||||
thread_idx = UOp.special(threads, "lidx0")
|
||||
block_idx_x, block_idx_y = UOp.special(B, "gidx0"), UOp.special(N, "gidx1")
|
||||
sink = UOp.sink(q.base, k.base, v.base, xqkv.base, freqs_cis.base, thread_idx, block_idx_x, block_idx_y,
|
||||
arg=KernelInfo(name="fused_qkv_rope_forward"))
|
||||
compile_args = ["-std=c++20", "-ffast-math", f"-DATTN_B={B}", f"-DATTN_N={N}", f"-DATTN_H={H}",
|
||||
f"-DATTN_H_KV={H_KV}", f"-DATTN_D={D}", f"-DTHREADS_PER_BLOCK={threads}"]
|
||||
lib = HIPCCCompiler(arch, compile_args).compile_cached(code)
|
||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=code), UOp(Ops.BINARY, arg=lib)))
|
||||
group_size = H // H_KV
|
||||
q, k, v = q.reshape(B, N, H, D), k.reshape(B, N, H_KV, D), v.reshape(B, N, H_KV, D)
|
||||
xqkv = xqkv.reshape(B, N, H_KV, group_size + 2, D)
|
||||
b, n = UOp.range(B, 0), UOp.range(N, 1)
|
||||
pair = UOp.range(D // 2, 2)
|
||||
even = pair * 2
|
||||
c = freqs_cis[0, n, 0, pair, 0].cast(dtypes.float)
|
||||
s = freqs_cis[0, n, 0, pair, 1].cast(dtypes.float)
|
||||
ordered:UOp|None = None
|
||||
for kvh in range(H_KV):
|
||||
q_out, k_out, v_out = (x.after(ordered) if ordered is not None else x for x in (q, k, v))
|
||||
x_in = xqkv.after(ordered) if ordered is not None else xqkv
|
||||
stores:list[UOp] = []
|
||||
for rep in range(group_size):
|
||||
a = x_in[b, n, kvh, rep, even].cast(dtypes.float)
|
||||
bb = x_in[b, n, kvh, rep, even + 1].cast(dtypes.float)
|
||||
h = kvh * group_size + rep
|
||||
stores += [q_out[b, n, h, even].store((a * c - bb * s).cast(q.dtype)), q_out[b, n, h, even + 1].store((a * s + bb * c).cast(q.dtype))]
|
||||
a = x_in[b, n, kvh, group_size, even].cast(dtypes.float)
|
||||
bb = x_in[b, n, kvh, group_size, even + 1].cast(dtypes.float)
|
||||
stores += [k_out[b, n, kvh, even].store((a * c - bb * s).cast(k.dtype)),
|
||||
k_out[b, n, kvh, even + 1].store((a * s + bb * c).cast(k.dtype)),
|
||||
v_out[b, n, kvh, even].store(x_in[b, n, kvh, group_size + 1, even]),
|
||||
v_out[b, n, kvh, even + 1].store(x_in[b, n, kvh, group_size + 1, even + 1])]
|
||||
ordered = UOp.group(*stores)
|
||||
assert ordered is not None
|
||||
return ordered.end(pair, n, b).sink(arg=KernelInfo(name="fused_qkv_rope_forward"))
|
||||
|
||||
@functools.cache
|
||||
def custom_fused_qkv_rope_backward(dxqkv:UOp, dq:UOp, dk:UOp, dv:UOp, freqs_cis:UOp,
|
||||
@@ -109,8 +126,7 @@ def fused_qkv_rope(xqkv:Tensor, freqs_cis:Tensor, n_heads:int, n_kv_heads:int, h
|
||||
def _sharded_empty_like(ref:Tensor, axis:int|None=None) -> Tensor:
|
||||
return _sharded_empty(ref.shape, ref, axis)
|
||||
|
||||
@functools.cache
|
||||
def _fa_grad_fxn(B, H, N, D, H_local, H_KV_local, H_KV, B_local, shard_axis, shard_axis_t, single_device, arch, has_sink):
|
||||
def _fa_grad_fxn(B, H, N, D, H_local, H_KV_local, H_KV, B_local, shard_axis, shard_axis_t, single_device, arch, has_sink, window=0):
|
||||
def grad(dou:UOp, ker:UOp) -> tuple:
|
||||
do = Tensor(dou, device=dou.device)
|
||||
attn = Tensor(ker.src[1].after(ker), device=ker.src[1].device)
|
||||
@@ -129,7 +145,7 @@ def _fa_grad_fxn(B, H, N, D, H_local, H_KV_local, H_KV, B_local, shard_axis, sha
|
||||
delta_vec = _sharded_empty((B, H, 1, N), xq, dtype=dtypes.float32, axis=shard_axis_t)
|
||||
delta_vec, dq = Tensor.custom_kernel(delta_vec, dq, attn, do, fxn=functools.partial(custom_fa_backward_pre, device=single_device, arch=arch, B=B_local, N=N, H=H_local, H_KV=H_KV_local, D=D))[:2]
|
||||
|
||||
dq, dk_partial, dv_partial = Tensor.custom_kernel(dq, dk_partial, dv_partial, do, xq, xk, xv, l_vec, delta_vec, fxn=functools.partial(custom_fa_backward, device=single_device, arch=arch, B=B_local, N=N, H=H_local, H_KV=H_KV_local, D=D))[:3]
|
||||
dq, dk_partial, dv_partial = Tensor.custom_kernel(dq, dk_partial, dv_partial, do, xq, xk, xv, l_vec, delta_vec, fxn=functools.partial(custom_fa_backward, device=single_device, arch=arch, B=B_local, N=N, H=H_local, H_KV=H_KV_local, D=D, window=window))[:3]
|
||||
|
||||
if D == 64:
|
||||
dq = dq.reshape(B, H, N//16, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2).permute(0, 1, 2, 8, 9, 10, 11, 3, 4, 6, 7, 5, 12).reshape(B, H, N, D).transpose(1, 2)
|
||||
@@ -149,7 +165,7 @@ def _fa_grad_fxn(B, H, N, D, H_local, H_KV_local, H_KV, B_local, shard_axis, sha
|
||||
return grad
|
||||
|
||||
# TODO: remove write_flat once scheduler can remove reshapes between custom_kernel. TestCustomKernel.test_simple_reshape
|
||||
def flash_attention(xq, xk, xv, attn_mask:Tensor|None=None, is_causal:bool=False, write_flat:bool=False, sinks:Tensor|None=None):
|
||||
def flash_attention(xq, xk, xv, attn_mask:Tensor|None=None, is_causal:bool=False, write_flat:bool=False, sinks:Tensor|None=None, window:int=0):
|
||||
assert attn_mask is None, "attn_mask not supported"
|
||||
assert is_causal, "only causal attention supported"
|
||||
|
||||
@@ -176,18 +192,18 @@ def flash_attention(xq, xk, xv, attn_mask:Tensor|None=None, is_causal:bool=False
|
||||
attn = _sharded_empty((B, N, H * D), xq, axis=shard_axis) if write_flat else _sharded_empty_like(xq, axis=shard_axis)
|
||||
l_vec = _sharded_empty((B, H, 1, N), xq, dtype=dtypes.float32, axis=shard_axis_t)
|
||||
|
||||
grad = _fa_grad_fxn(B, H, N, D, H_local, H_KV_local, H_KV, B_local, shard_axis, shard_axis_t, single_device, arch, has_sink)
|
||||
grad = _fa_grad_fxn(B, H, N, D, H_local, H_KV_local, H_KV, B_local, shard_axis, shard_axis_t, single_device, arch, has_sink, window=window)
|
||||
|
||||
fwd_inputs = (attn, l_vec, xq, xk, xv) + ((sinks,) if has_sink else ())
|
||||
attn, l_vec = Tensor.custom_kernel(*fwd_inputs, fxn=functools.partial(custom_fa_forward, device=single_device, arch=arch, B=B_local, N=N, H=H_local, H_KV=H_KV_local, D=D, has_sink=has_sink), grad_fxn=grad)[:2]
|
||||
attn, l_vec = Tensor.custom_kernel(*fwd_inputs, fxn=functools.partial(custom_fa_forward, device=single_device, arch=arch, B=B_local, N=N, H=H_local, H_KV=H_KV_local, D=D, has_sink=has_sink, window=window), grad_fxn=grad)[:2]
|
||||
|
||||
return attn, attn, l_vec
|
||||
|
||||
@functools.cache
|
||||
def custom_fa_forward(o:UOp, l_vec:UOp, q:UOp, k:UOp, v:UOp, sinks:UOp|None=None, *, device:str, arch:str, B:int, N:int, H:int, H_KV:int, D:int, has_sink:bool=True):
|
||||
def custom_fa_forward(o:UOp, l_vec:UOp, q:UOp, k:UOp, v:UOp, sinks:UOp|None=None, *, device:str, arch:str, B:int, N:int, H:int, H_KV:int, D:int, has_sink:bool=True, window:int=0):
|
||||
code = (pathlib.Path(__file__).parent / "fa_fwd_causal.cpp").read_text()
|
||||
compile_args = [f"-I{(pathlib.Path(__file__).parent / 'include').as_posix()}", "-std=c++20", "-DKITTENS_CDNA4", "-DHIP_ENABLE_WARP_SYNC_BUILTINS", "-ffast-math",
|
||||
f"-DATTN_B={B}", f"-DATTN_N={N}", f"-DATTN_H={H}", f"-DATTN_H_KV={H_KV}", f"-DATTN_D={D}", f"-DATTN_SINK={int(has_sink)}"]
|
||||
f"-DATTN_B={B}", f"-DATTN_N={N}", f"-DATTN_H={H}", f"-DATTN_H_KV={H_KV}", f"-DATTN_D={D}", f"-DATTN_SINK={int(has_sink)}", f"-DWINDOW={window}"]
|
||||
|
||||
Q_BLOCK_SIZE = 32
|
||||
NUM_WARPS = 8
|
||||
@@ -206,10 +222,11 @@ def custom_fa_forward(o:UOp, l_vec:UOp, q:UOp, k:UOp, v:UOp, sinks:UOp|None=None
|
||||
arg=KernelInfo(name="custom_fa_forward", estimates=estimates))
|
||||
|
||||
lib = HIPCCCompiler(arch, compile_args).compile_cached(code)
|
||||
lib = bytearray(lib)
|
||||
rodata_off = next(sh.header.sh_offset for sh in elf_loader(bytes(lib))[1] if sh.name == ".rodata")
|
||||
struct.pack_into('<I', lib, rodata_off, 160000)
|
||||
lib = bytes(lib)
|
||||
if not getenv("NO_HIPCC"):
|
||||
lib = bytearray(lib)
|
||||
rodata_off = next(sh.header.sh_offset for sh in elf_loader(bytes(lib))[1] if sh.name == ".rodata")
|
||||
struct.pack_into('<I', lib, rodata_off, 160000)
|
||||
lib = bytes(lib)
|
||||
|
||||
return UOp(Ops.PROGRAM,
|
||||
src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=code), UOp(Ops.BINARY, arg=lib)))
|
||||
@@ -236,19 +253,20 @@ def custom_fa_backward_pre(delta_vec:UOp, dq:UOp, o:UOp, do:UOp, device:str, arc
|
||||
arg=KernelInfo(name="custom_fa_backward_pre", estimates=estimates))
|
||||
|
||||
lib = HIPCCCompiler(arch, compile_args).compile_cached(code)
|
||||
lib = bytearray(lib)
|
||||
rodata_off = next(sh.header.sh_offset for sh in elf_loader(bytes(lib))[1] if sh.name == ".rodata")
|
||||
struct.pack_into('<I', lib, rodata_off, 160000)
|
||||
lib = bytes(lib)
|
||||
if not getenv("NO_HIPCC"):
|
||||
lib = bytearray(lib)
|
||||
rodata_off = next(sh.header.sh_offset for sh in elf_loader(bytes(lib))[1] if sh.name == ".rodata")
|
||||
struct.pack_into('<I', lib, rodata_off, 160000)
|
||||
lib = bytes(lib)
|
||||
|
||||
return UOp(Ops.PROGRAM,
|
||||
src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=code), UOp(Ops.BINARY, arg=lib)))
|
||||
|
||||
@functools.cache
|
||||
def custom_fa_backward(dq:UOp, dk:UOp, dv:UOp, do:UOp, q:UOp, k:UOp, v:UOp, l_vec:UOp, delta_vec:UOp, device:str, arch:str, B:int, N:int, H:int, H_KV:int, D:int):
|
||||
def custom_fa_backward(dq:UOp, dk:UOp, dv:UOp, do:UOp, q:UOp, k:UOp, v:UOp, l_vec:UOp, delta_vec:UOp, device:str, arch:str, B:int, N:int, H:int, H_KV:int, D:int, window:int=0):
|
||||
code = (pathlib.Path(__file__).parent / "fa_bwd_causal.cpp").read_text()
|
||||
compile_args = [f"-I{(pathlib.Path(__file__).parent / 'include').as_posix()}", "-std=c++20", "-DKITTENS_CDNA4", "-DHIP_ENABLE_WARP_SYNC_BUILTINS", "-ffast-math",
|
||||
f"-DATTN_B={B}", f"-DATTN_N={N}", f"-DATTN_H={H}", f"-DATTN_H_KV={H_KV}", f"-DATTN_D={D}"]
|
||||
f"-DATTN_B={B}", f"-DATTN_N={N}", f"-DATTN_H={H}", f"-DATTN_H_KV={H_KV}", f"-DATTN_D={D}", f"-DWINDOW={window}"]
|
||||
|
||||
BLOCK_SIZE_KV = 256
|
||||
GROUP_SIZE = H // H_KV
|
||||
@@ -268,10 +286,11 @@ def custom_fa_backward(dq:UOp, dk:UOp, dv:UOp, do:UOp, q:UOp, k:UOp, v:UOp, l_ve
|
||||
arg=KernelInfo(name="custom_fa_backward", estimates=estimates))
|
||||
|
||||
lib = HIPCCCompiler(arch, compile_args).compile_cached(code)
|
||||
lib = bytearray(lib)
|
||||
rodata_off = next(sh.header.sh_offset for sh in elf_loader(bytes(lib))[1] if sh.name == ".rodata")
|
||||
struct.pack_into('<I', lib, rodata_off, 160000)
|
||||
lib = bytes(lib)
|
||||
if not getenv("NO_HIPCC"):
|
||||
lib = bytearray(lib)
|
||||
rodata_off = next(sh.header.sh_offset for sh in elf_loader(bytes(lib))[1] if sh.name == ".rodata")
|
||||
struct.pack_into('<I', lib, rodata_off, 160000)
|
||||
lib = bytes(lib)
|
||||
|
||||
return UOp(Ops.PROGRAM,
|
||||
src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=code), UOp(Ops.BINARY, arg=lib)))
|
||||
@@ -298,10 +317,11 @@ def custom_fa_backward_post(dq_out:UOp, dq_in:UOp, device:str, arch:str, B:int,
|
||||
arg=KernelInfo(name="custom_fa_backward_post", estimates=estimates))
|
||||
|
||||
lib = HIPCCCompiler(arch, compile_args).compile_cached(code)
|
||||
lib = bytearray(lib)
|
||||
rodata_off = next(sh.header.sh_offset for sh in elf_loader(bytes(lib))[1] if sh.name == ".rodata")
|
||||
struct.pack_into('<I', lib, rodata_off, 160000)
|
||||
lib = bytes(lib)
|
||||
if not getenv("NO_HIPCC"):
|
||||
lib = bytearray(lib)
|
||||
rodata_off = next(sh.header.sh_offset for sh in elf_loader(bytes(lib))[1] if sh.name == ".rodata")
|
||||
struct.pack_into('<I', lib, rodata_off, 160000)
|
||||
lib = bytes(lib)
|
||||
|
||||
return UOp(Ops.PROGRAM,
|
||||
src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=code), UOp(Ops.BINARY, arg=lib)))
|
||||
|
||||
@@ -43,6 +43,10 @@ constexpr int SLICE_QO = 32;
|
||||
constexpr int DOT_SLICE_QO = 16;
|
||||
constexpr int WARP_SIZE_KV = 64; // warp size for KV
|
||||
constexpr bool causal = true;
|
||||
// WINDOW>0: sliding-window backward (query i sees keys in [i-WINDOW+1, i])
|
||||
#ifndef WINDOW
|
||||
#define WINDOW 0
|
||||
#endif
|
||||
|
||||
#define NUM_WARPS 4
|
||||
#define NUM_THREADS (kittens::WARP_THREADS * NUM_WARPS)
|
||||
@@ -88,7 +92,12 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
const int k_start_min = j_min * WARP_SIZE_KV;
|
||||
// first Q step that can overlap this K_span:
|
||||
const int first_step = max(0, k_start_min / STEP_QO);
|
||||
#if WINDOW
|
||||
// cap the Q loop, padded by 2 masked steps: the epilogue's deferred dq path miscomputes in-window tail queries
|
||||
const int num_steps_per_head = min(total_steps_per_head - first_step, (BLOCK_SIZE_KV + WINDOW) / STEP_QO + 2);
|
||||
#else
|
||||
const int num_steps_per_head = total_steps_per_head - first_step;
|
||||
#endif
|
||||
const int num_steps = num_steps_per_head * HEADS_PER_WG;
|
||||
const int k_pos = j * WARP_SIZE_KV;
|
||||
|
||||
@@ -380,6 +389,13 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
mov<0, 1, neg_inf_v>(P_ij);
|
||||
mov<0, 2, neg_inf_v>(P_ij);
|
||||
mov<0, 3, neg_inf_v>(P_ij);
|
||||
#if WINDOW
|
||||
// window lower boundary, mirror of the causal edge
|
||||
} else if (q_pos - k_pos == WINDOW) {
|
||||
make_window<0, 0, neg_inf_v>(P_ij, P_ij);
|
||||
} else if (q_pos - k_pos > WINDOW) {
|
||||
mov<neg_inf_v>(P_ij);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
mul<0, 2>(P_ij, P_ij, P_SCALE_FACTOR);
|
||||
@@ -533,6 +549,8 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
if constexpr (D == 128) load<0, 2>(Q_i, subtile_inplace<DOT_SLICE_QO, D>(Q_i_smem[tic][0], {0, 0}), Q_i_addr);
|
||||
if constexpr (D == 128) load<0, 3>(Q_i, subtile_inplace<DOT_SLICE_QO, D>(Q_i_smem[tic][0], {0, 0}), Q_i_addr);
|
||||
mma_AtB<0, 0, 7>(dQ_i_T, K_j_col, dP_ij_bf16_col_T, dQ_i_T);
|
||||
// D=64: wait out MFMA->VALU accumulator hazard on dQ_i_T
|
||||
if constexpr (D == 64) asm volatile("s_nop 15");
|
||||
if constexpr (D == 128) mma_AtB<1, 0, 0>(dQ_i_T, K_j_col, dP_ij_bf16_col_T);
|
||||
// Load K_j from shared memory to registers
|
||||
// load(K_j, subtile_inplace<WARP_SIZE_KV, D>(K_j_smem, {warpid, 0}));
|
||||
@@ -638,6 +656,13 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
make_causal<0, 1, neg_inf_v>(P_ij, P_ij);
|
||||
mov<0, 2, neg_inf_v>(P_ij);
|
||||
mov<0, 3, neg_inf_v>(P_ij);
|
||||
#if WINDOW
|
||||
} else if (q_pos - k_pos == WINDOW) {
|
||||
mov<0, 0, neg_inf_v>(P_ij);
|
||||
make_window<0, 1, neg_inf_v>(P_ij, P_ij);
|
||||
} else if (q_pos - k_pos > WINDOW) {
|
||||
mov<neg_inf_v>(P_ij);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
mul<0, 2>(P_ij, P_ij, P_SCALE_FACTOR);
|
||||
@@ -791,6 +816,8 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
if constexpr (D == 128) load<0, 2>(Q_i, subtile_inplace<DOT_SLICE_QO, D>(Q_i_smem[tic][0], {0, 0}), Q_i_addr);
|
||||
if constexpr (D == 128) load<0, 3>(Q_i, subtile_inplace<DOT_SLICE_QO, D>(Q_i_smem[tic][0], {0, 0}), Q_i_addr);
|
||||
mma_AtB<0, 0, 7>(dQ_i_T, K_j_col, dP_ij_bf16_col_T, dQ_i_T);
|
||||
// D=64: wait out MFMA->VALU accumulator hazard on dQ_i_T
|
||||
if constexpr (D == 64) asm volatile("s_nop 15");
|
||||
if constexpr (D == 128) mma_AtB<1, 0, 0>(dQ_i_T, K_j_col, dP_ij_bf16_col_T);
|
||||
// Load K_j from shared memory to registers
|
||||
// load(K_j, subtile_inplace<WARP_SIZE_KV, D>(K_j_smem, {warpid, 0}));
|
||||
@@ -895,6 +922,14 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
// Apply the causal mask to [0, 2] and set [0, 3:4] to -inf
|
||||
make_causal<0, 2, neg_inf_v>(P_ij, P_ij);
|
||||
mov<0, 3, neg_inf_v>(P_ij);
|
||||
#if WINDOW
|
||||
} else if (q_pos - k_pos == WINDOW) {
|
||||
mov<0, 0, neg_inf_v>(P_ij);
|
||||
mov<0, 1, neg_inf_v>(P_ij);
|
||||
make_window<0, 2, neg_inf_v>(P_ij, P_ij);
|
||||
} else if (q_pos - k_pos > WINDOW) {
|
||||
mov<neg_inf_v>(P_ij);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
mul<0, 2>(P_ij, P_ij, P_SCALE_FACTOR);
|
||||
@@ -1048,6 +1083,8 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
if constexpr (D == 128) load<0, 2>(Q_i, subtile_inplace<DOT_SLICE_QO, D>(Q_i_smem[tic][0], {0, 0}), Q_i_addr);
|
||||
if constexpr (D == 128) load<0, 3>(Q_i, subtile_inplace<DOT_SLICE_QO, D>(Q_i_smem[tic][0], {0, 0}), Q_i_addr);
|
||||
mma_AtB<0, 0, 7>(dQ_i_T, K_j_col, dP_ij_bf16_col_T, dQ_i_T);
|
||||
// D=64: wait out MFMA->VALU accumulator hazard on dQ_i_T
|
||||
if constexpr (D == 64) asm volatile("s_nop 15");
|
||||
if constexpr (D == 128) mma_AtB<1, 0, 0>(dQ_i_T, K_j_col, dP_ij_bf16_col_T);
|
||||
// Load K_j from shared memory to registers
|
||||
// load(K_j, subtile_inplace<WARP_SIZE_KV, D>(K_j_smem, {warpid, 0}));
|
||||
@@ -1151,6 +1188,15 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
} else if (q_pos == k_pos) {
|
||||
// Apply the causal mask to [0, 3]
|
||||
make_causal<0, 3, neg_inf_v>(P_ij, P_ij);
|
||||
#if WINDOW
|
||||
} else if (q_pos - k_pos == WINDOW) {
|
||||
mov<0, 0, neg_inf_v>(P_ij);
|
||||
mov<0, 1, neg_inf_v>(P_ij);
|
||||
mov<0, 2, neg_inf_v>(P_ij);
|
||||
make_window<0, 3, neg_inf_v>(P_ij, P_ij);
|
||||
} else if (q_pos - k_pos > WINDOW) {
|
||||
mov<neg_inf_v>(P_ij);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
mul<0, 2>(P_ij, P_ij, P_SCALE_FACTOR);
|
||||
@@ -1303,6 +1349,8 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
if constexpr (D == 128) load<0, 2>(Q_i, subtile_inplace<DOT_SLICE_QO, D>(Q_i_smem[toc][0], {0, 0}), Q_i_addr);
|
||||
if constexpr (D == 128) load<0, 3>(Q_i, subtile_inplace<DOT_SLICE_QO, D>(Q_i_smem[toc][0], {0, 0}), Q_i_addr);
|
||||
mma_AtB<0, 0, 7>(dQ_i_T, K_j_col, dP_ij_bf16_col_T, dQ_i_T);
|
||||
// D=64: wait out MFMA->VALU accumulator hazard on dQ_i_T
|
||||
if constexpr (D == 64) asm volatile("s_nop 15");
|
||||
if constexpr (D == 128) mma_AtB<1, 0, 0>(dQ_i_T, K_j_col, dP_ij_bf16_col_T);
|
||||
// Load K_j from shared memory to registers
|
||||
// load(K_j, subtile_inplace<WARP_SIZE_KV, D>(K_j_smem, {warpid, 0}));
|
||||
@@ -1428,6 +1476,13 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
mov<0, 1, neg_inf_v>(P_ij);
|
||||
mov<0, 2, neg_inf_v>(P_ij);
|
||||
mov<0, 3, neg_inf_v>(P_ij);
|
||||
#if WINDOW
|
||||
// window lower boundary, mirror of the causal edge
|
||||
} else if (q_pos - k_pos == WINDOW) {
|
||||
make_window<0, 0, neg_inf_v>(P_ij, P_ij);
|
||||
} else if (q_pos - k_pos > WINDOW) {
|
||||
mov<neg_inf_v>(P_ij);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
mul<0, 2>(P_ij, P_ij, P_SCALE_FACTOR);
|
||||
@@ -1582,6 +1637,8 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
if constexpr (D == 128) load<0, 2>(Q_i, subtile_inplace<DOT_SLICE_QO, D>(Q_i_smem[tic][0], {0, 0}), Q_i_addr);
|
||||
if constexpr (D == 128) load<0, 3>(Q_i, subtile_inplace<DOT_SLICE_QO, D>(Q_i_smem[tic][0], {0, 0}), Q_i_addr);
|
||||
mma_AtB<0, 0, 7>(dQ_i_T, K_j_col, dP_ij_bf16_col_T, dQ_i_T);
|
||||
// D=64: wait out MFMA->VALU accumulator hazard on dQ_i_T
|
||||
if constexpr (D == 64) asm volatile("s_nop 15");
|
||||
if constexpr (D == 128) mma_AtB<1, 0, 0>(dQ_i_T, K_j_col, dP_ij_bf16_col_T);
|
||||
// Load K_j from shared memory to registers
|
||||
// load(K_j, subtile_inplace<WARP_SIZE_KV, D>(K_j_smem, {warpid, 0}));
|
||||
@@ -1689,6 +1746,13 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
make_causal<0, 1, neg_inf_v>(P_ij, P_ij);
|
||||
mov<0, 2, neg_inf_v>(P_ij);
|
||||
mov<0, 3, neg_inf_v>(P_ij);
|
||||
#if WINDOW
|
||||
} else if (q_pos - k_pos == WINDOW) {
|
||||
mov<0, 0, neg_inf_v>(P_ij);
|
||||
make_window<0, 1, neg_inf_v>(P_ij, P_ij);
|
||||
} else if (q_pos - k_pos > WINDOW) {
|
||||
mov<neg_inf_v>(P_ij);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
mul<0, 2>(P_ij, P_ij, P_SCALE_FACTOR);
|
||||
@@ -1842,6 +1906,8 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
if constexpr (D == 128) load<0, 2>(Q_i, subtile_inplace<DOT_SLICE_QO, D>(Q_i_smem[tic][0], {0, 0}), Q_i_addr);
|
||||
if constexpr (D == 128) load<0, 3>(Q_i, subtile_inplace<DOT_SLICE_QO, D>(Q_i_smem[tic][0], {0, 0}), Q_i_addr);
|
||||
mma_AtB<0, 0, 7>(dQ_i_T, K_j_col, dP_ij_bf16_col_T, dQ_i_T);
|
||||
// D=64: wait out MFMA->VALU accumulator hazard on dQ_i_T
|
||||
if constexpr (D == 64) asm volatile("s_nop 15");
|
||||
if constexpr (D == 128) mma_AtB<1, 0, 0>(dQ_i_T, K_j_col, dP_ij_bf16_col_T);
|
||||
// Load K_j from shared memory to registers
|
||||
// load(K_j, subtile_inplace<WARP_SIZE_KV, D>(K_j_smem, {warpid, 0}));
|
||||
@@ -1946,6 +2012,14 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
// Apply the causal mask to [0, 2] and set [0, 3:4] to -inf
|
||||
make_causal<0, 2, neg_inf_v>(P_ij, P_ij);
|
||||
mov<0, 3, neg_inf_v>(P_ij);
|
||||
#if WINDOW
|
||||
} else if (q_pos - k_pos == WINDOW) {
|
||||
mov<0, 0, neg_inf_v>(P_ij);
|
||||
mov<0, 1, neg_inf_v>(P_ij);
|
||||
make_window<0, 2, neg_inf_v>(P_ij, P_ij);
|
||||
} else if (q_pos - k_pos > WINDOW) {
|
||||
mov<neg_inf_v>(P_ij);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
mul<0, 2>(P_ij, P_ij, P_SCALE_FACTOR);
|
||||
@@ -2099,6 +2173,8 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
if constexpr (D == 128) load<0, 2>(Q_i, subtile_inplace<DOT_SLICE_QO, D>(Q_i_smem[tic][0], {0, 0}), Q_i_addr);
|
||||
if constexpr (D == 128) load<0, 3>(Q_i, subtile_inplace<DOT_SLICE_QO, D>(Q_i_smem[tic][0], {0, 0}), Q_i_addr);
|
||||
mma_AtB<0, 0, 7>(dQ_i_T, K_j_col, dP_ij_bf16_col_T, dQ_i_T);
|
||||
// D=64: wait out MFMA->VALU accumulator hazard on dQ_i_T
|
||||
if constexpr (D == 64) asm volatile("s_nop 15");
|
||||
if constexpr (D == 128) mma_AtB<1, 0, 0>(dQ_i_T, K_j_col, dP_ij_bf16_col_T);
|
||||
// Load K_j from shared memory to registers
|
||||
// load(K_j, subtile_inplace<WARP_SIZE_KV, D>(K_j_smem, {warpid, 0}));
|
||||
@@ -2202,6 +2278,15 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
} else if (q_pos == k_pos) {
|
||||
// Apply the causal mask to [0, 3]
|
||||
make_causal<0, 3, neg_inf_v>(P_ij, P_ij);
|
||||
#if WINDOW
|
||||
} else if (q_pos - k_pos == WINDOW) {
|
||||
mov<0, 0, neg_inf_v>(P_ij);
|
||||
mov<0, 1, neg_inf_v>(P_ij);
|
||||
mov<0, 2, neg_inf_v>(P_ij);
|
||||
make_window<0, 3, neg_inf_v>(P_ij, P_ij);
|
||||
} else if (q_pos - k_pos > WINDOW) {
|
||||
mov<neg_inf_v>(P_ij);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
mul<0, 2>(P_ij, P_ij, P_SCALE_FACTOR);
|
||||
@@ -2354,6 +2439,8 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
if constexpr (D == 128) load<0, 2>(Q_i, subtile_inplace<DOT_SLICE_QO, D>(Q_i_smem[toc][0], {0, 0}), Q_i_addr);
|
||||
if constexpr (D == 128) load<0, 3>(Q_i, subtile_inplace<DOT_SLICE_QO, D>(Q_i_smem[toc][0], {0, 0}), Q_i_addr);
|
||||
mma_AtB<0, 0, 7>(dQ_i_T, K_j_col, dP_ij_bf16_col_T, dQ_i_T);
|
||||
// D=64: wait out MFMA->VALU accumulator hazard on dQ_i_T
|
||||
if constexpr (D == 64) asm volatile("s_nop 15");
|
||||
if constexpr (D == 128) mma_AtB<1, 0, 0>(dQ_i_T, K_j_col, dP_ij_bf16_col_T);
|
||||
// Load K_j from shared memory to registers
|
||||
// load(K_j, subtile_inplace<WARP_SIZE_KV, D>(K_j_smem, {warpid, 0}));
|
||||
@@ -2471,6 +2558,12 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
mov<0, 1, neg_inf_v>(P_ij);
|
||||
mov<0, 2, neg_inf_v>(P_ij);
|
||||
mov<0, 3, neg_inf_v>(P_ij);
|
||||
#if WINDOW
|
||||
} else if (q_pos - k_pos == WINDOW) {
|
||||
make_window<0, 0, neg_inf_v>(P_ij, P_ij);
|
||||
} else if (q_pos - k_pos > WINDOW) {
|
||||
mov<neg_inf_v>(P_ij);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
mul<0, 2>(P_ij, P_ij, P_SCALE_FACTOR);
|
||||
@@ -2732,6 +2825,13 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
make_causal<0, 1, neg_inf_v>(P_ij, P_ij);
|
||||
mov<0, 2, neg_inf_v>(P_ij);
|
||||
mov<0, 3, neg_inf_v>(P_ij);
|
||||
#if WINDOW
|
||||
} else if (q_pos - k_pos == WINDOW) {
|
||||
mov<0, 0, neg_inf_v>(P_ij);
|
||||
make_window<0, 1, neg_inf_v>(P_ij, P_ij);
|
||||
} else if (q_pos - k_pos > WINDOW) {
|
||||
mov<neg_inf_v>(P_ij);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
mul<0, 2>(P_ij, P_ij, P_SCALE_FACTOR);
|
||||
@@ -2988,6 +3088,14 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
// Apply the causal mask to [0, 2] and set [0, 3:4] to -inf
|
||||
make_causal<0, 2, neg_inf_v>(P_ij, P_ij);
|
||||
mov<0, 3, neg_inf_v>(P_ij);
|
||||
#if WINDOW
|
||||
} else if (q_pos - k_pos == WINDOW) {
|
||||
mov<0, 0, neg_inf_v>(P_ij);
|
||||
mov<0, 1, neg_inf_v>(P_ij);
|
||||
make_window<0, 2, neg_inf_v>(P_ij, P_ij);
|
||||
} else if (q_pos - k_pos > WINDOW) {
|
||||
mov<neg_inf_v>(P_ij);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
mul<0, 2>(P_ij, P_ij, P_SCALE_FACTOR);
|
||||
@@ -3244,6 +3352,15 @@ __global__ void attend_bwd_combined_ker(bf16 *dQ_ptr, bf16 *dK_ptr, bf16 *dV_ptr
|
||||
} else if (q_pos == k_pos) {
|
||||
// Apply the causal mask to [0, 3]
|
||||
make_causal<0, 3, neg_inf_v>(P_ij, P_ij);
|
||||
#if WINDOW
|
||||
} else if (q_pos - k_pos == WINDOW) {
|
||||
mov<0, 0, neg_inf_v>(P_ij);
|
||||
mov<0, 1, neg_inf_v>(P_ij);
|
||||
mov<0, 2, neg_inf_v>(P_ij);
|
||||
make_window<0, 3, neg_inf_v>(P_ij, P_ij);
|
||||
} else if (q_pos - k_pos > WINDOW) {
|
||||
mov<neg_inf_v>(P_ij);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
mul<0, 2>(P_ij, P_ij, P_SCALE_FACTOR);
|
||||
|
||||
@@ -34,6 +34,10 @@ constexpr int ATTN_D = 128; // dimension
|
||||
constexpr int Q_BLOCK_SIZE = 32; // q block size
|
||||
constexpr int KV_BLOCK_SIZE = 64; // kv block size
|
||||
constexpr bool causal = true;
|
||||
// WINDOW>0: sliding-window attention, query i attends keys in [i-WINDOW+1, i]
|
||||
#ifndef WINDOW
|
||||
#define WINDOW 0
|
||||
#endif
|
||||
|
||||
#define NUM_WARPS 8
|
||||
#define NUM_THREADS (kittens::WARP_THREADS * NUM_WARPS)
|
||||
@@ -82,11 +86,26 @@ template<typename T=float, typename L=col_l, typename S=rt_16x32_4_s> using attn
|
||||
|
||||
/**********************************************************/
|
||||
template<int THR_X, int THR_Y>
|
||||
__device__ inline void mask_vec2_imm(uint32_t rel_vgpr, uint32_t neg_inf_vgpr,
|
||||
__device__ inline void mask_vec2_imm(uint32_t rel_vgpr, uint32_t rel_hi_vgpr, uint32_t neg_inf_vgpr,
|
||||
uint32_t& x_ref, uint32_t& y_ref) {
|
||||
|
||||
uint64_t x_mask, y_mask;
|
||||
// uint32_t ox, oy;
|
||||
#if WINDOW
|
||||
// causal+window in one asm block to not disturb register allocation
|
||||
asm volatile(
|
||||
"v_cmp_lt_i32_e64 %0, %4, %5\n\t"
|
||||
"v_cmp_lt_i32_e64 %1, %4, %7\n\t"
|
||||
"v_cndmask_b32_e64 %2, %2, %6, %0\n\t"
|
||||
"v_cndmask_b32_e64 %3, %3, %6, %1\n\t"
|
||||
"v_cmp_ge_i32_e64 %0, %8, %5\n\t"
|
||||
"v_cmp_ge_i32_e64 %1, %8, %7\n\t"
|
||||
"v_cndmask_b32_e64 %2, %2, %6, %0\n\t"
|
||||
"v_cndmask_b32_e64 %3, %3, %6, %1\n\t"
|
||||
: "=s"(x_mask), "=s"(y_mask), "+v"(x_ref), "+v"(y_ref)
|
||||
: "v"(rel_vgpr), "n"(THR_X), "v"(neg_inf_vgpr), "n"(THR_Y), "v"(rel_hi_vgpr)
|
||||
: "vcc"
|
||||
);
|
||||
#else
|
||||
asm volatile(
|
||||
// x: rel < THR_X ?
|
||||
"v_cmp_lt_i32_e64 %0, %6, %7\n\t"
|
||||
@@ -99,7 +118,7 @@ __device__ inline void mask_vec2_imm(uint32_t rel_vgpr, uint32_t neg_inf_vgpr,
|
||||
"n"(THR_X), "v"(neg_inf_vgpr), "n"(THR_Y)
|
||||
: "vcc"
|
||||
);
|
||||
// x_ref = ox; y_ref = oy;
|
||||
#endif
|
||||
}
|
||||
|
||||
template<ducks::rt::col_layout RT>
|
||||
@@ -122,6 +141,8 @@ __device__ inline void mask_kv_tile(RT &dst, int q_abs, int k_abs, uint32_t neg_
|
||||
// (smaller rel ⇒ more "future" keys that must be -inf)
|
||||
const int rel0 = q_pos - (k_base + row_base);
|
||||
const uint32_t rel = static_cast<uint32_t>(rel0);
|
||||
// rel-WINDOW keeps THR within the inline-constant range
|
||||
const uint32_t rel_hi = static_cast<uint32_t>(rel0 - WINDOW);
|
||||
|
||||
#pragma unroll
|
||||
for (int j = 0; j < dst.width; ++j) {
|
||||
@@ -145,14 +166,14 @@ __device__ inline void mask_kv_tile(RT &dst, int q_abs, int k_abs, uint32_t neg_
|
||||
// - reuse a single neg_inf register
|
||||
// - keep VCC live across the pair
|
||||
// - avoid reloading -inf or recomputing rel
|
||||
mask_vec2_imm< 0, 1 >(rel, neg_inf_v, d0x, d0y);
|
||||
mask_vec2_imm< 2, 3 >(rel, neg_inf_v, d1x, d1y);
|
||||
mask_vec2_imm< 8, 9 >(rel, neg_inf_v, d2x, d2y);
|
||||
mask_vec2_imm<10,11 >(rel, neg_inf_v, d3x, d3y);
|
||||
mask_vec2_imm<16,17 >(rel, neg_inf_v, d4x, d4y);
|
||||
mask_vec2_imm<18,19 >(rel, neg_inf_v, d5x, d5y);
|
||||
mask_vec2_imm<24,25 >(rel, neg_inf_v, d6x, d6y);
|
||||
mask_vec2_imm<26,27 >(rel, neg_inf_v, d7x, d7y);
|
||||
mask_vec2_imm< 0, 1 >(rel, rel_hi, neg_inf_v, d0x, d0y);
|
||||
mask_vec2_imm< 2, 3 >(rel, rel_hi, neg_inf_v, d1x, d1y);
|
||||
mask_vec2_imm< 8, 9 >(rel, rel_hi, neg_inf_v, d2x, d2y);
|
||||
mask_vec2_imm<10,11 >(rel, rel_hi, neg_inf_v, d3x, d3y);
|
||||
mask_vec2_imm<16,17 >(rel, rel_hi, neg_inf_v, d4x, d4y);
|
||||
mask_vec2_imm<18,19 >(rel, rel_hi, neg_inf_v, d5x, d5y);
|
||||
mask_vec2_imm<24,25 >(rel, rel_hi, neg_inf_v, d6x, d6y);
|
||||
mask_vec2_imm<26,27 >(rel, rel_hi, neg_inf_v, d7x, d7y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,6 +222,16 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
|
||||
else max_num_tiles = num_tiles;
|
||||
const int q_start_pos = tile_idx * Q_BLOCK_SIZE;
|
||||
|
||||
#if WINDOW
|
||||
// start at the first in-window tile; clamp keeps >=4 tiles for the pipeline unroll
|
||||
const int block_min_q = block_tile_idx * NUM_WARPS * Q_BLOCK_SIZE;
|
||||
int min_tile = (block_min_q - WINDOW + 1) / KV_BLOCK_SIZE;
|
||||
if (min_tile < 0) min_tile = 0;
|
||||
if (min_tile > max_num_tiles - 4) min_tile = max(0, max_num_tiles - 4);
|
||||
#else
|
||||
constexpr int min_tile = 0;
|
||||
#endif
|
||||
|
||||
constexpr float TEMPERATURE_SCALE = (D == 128) ? 0.08838834764f*1.44269504089f : 0.125f*1.44269504089f;
|
||||
uint32_t neg_inf_v = 0xff800000;
|
||||
|
||||
@@ -231,21 +262,23 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
|
||||
G::prefill_swizzled_offsets<1, false>(k_smem[0], g.Kg, swizzled_offsets_K);
|
||||
G::prefill_swizzled_offsets<1, false>(v_smem[0], g.Vg, swizzled_offsets_V);
|
||||
|
||||
G::load<1, false>(k_smem[0], g.Kg, {batch_idx, 0, head_idx_kv, 0}, swizzled_offsets_K);
|
||||
G::load<1, false>(k_smem[0], g.Kg, {batch_idx, min_tile, head_idx_kv, 0}, swizzled_offsets_K);
|
||||
__builtin_amdgcn_s_waitcnt(0);
|
||||
__builtin_amdgcn_sched_barrier(0);
|
||||
__builtin_amdgcn_s_barrier();
|
||||
|
||||
qo_tile<D, float> q_reg_fl;
|
||||
load<1, qo_tile<D, float>, _gl_QKVO>(q_reg_fl, g.Qg, {batch_idx, tile_idx, head_idx, 0});
|
||||
#if !WINDOW
|
||||
mul(q_reg_fl, q_reg_fl, TEMPERATURE_SCALE); // Use sqrtf for clarity
|
||||
#endif
|
||||
copy(q_reg, q_reg_fl);
|
||||
transpose(q_reg_transposed, q_reg);
|
||||
|
||||
// All warps then collaboratively load in the first slice of V (V0) and the second slice of K (K1) into shared memory
|
||||
G::load<1, false>(k_smem[1], g.Kg, {batch_idx, 1, head_idx_kv, 0}, swizzled_offsets_K);
|
||||
G::load<1, false>(k_smem[1], g.Kg, {batch_idx, min_tile + 1, head_idx_kv, 0}, swizzled_offsets_K);
|
||||
// All warps then load in the first slice of K (K0)
|
||||
G::load<1, false>(v_smem[0], g.Vg, {batch_idx, 0, head_idx_kv, 0}, swizzled_offsets_V);
|
||||
G::load<1, false>(v_smem[0], g.Vg, {batch_idx, min_tile, head_idx_kv, 0}, swizzled_offsets_V);
|
||||
load(k_reg, k_smem[0]);
|
||||
__builtin_amdgcn_sched_barrier(0);
|
||||
asm volatile("s_waitcnt lgkmcnt(0)");
|
||||
@@ -257,15 +290,25 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
|
||||
zero(att_block[0]);
|
||||
transpose(k_reg_transposed, k_reg);
|
||||
mma_AtB(att_block[0], k_reg_transposed, q_reg_transposed, att_block[0]);
|
||||
#if WINDOW
|
||||
mul(att_block[0], att_block[0], TEMPERATURE_SCALE);
|
||||
#endif
|
||||
__builtin_amdgcn_sched_barrier(0);
|
||||
if constexpr (causal) {
|
||||
const int kv_end_pos = (1) * KV_BLOCK_SIZE;
|
||||
if (__builtin_expect(q_start_pos < kv_end_pos, 0)) { // Only mask if needed
|
||||
mask_kv_tile(att_block[0], tile_idx, 0, neg_inf_v, lane);
|
||||
const int kv_end_pos = (min_tile + 1) * KV_BLOCK_SIZE;
|
||||
if (__builtin_expect(WINDOW || q_start_pos < kv_end_pos, WINDOW ? 1 : 0)) {
|
||||
mask_kv_tile(att_block[0], tile_idx, min_tile, neg_inf_v, lane);
|
||||
}
|
||||
}
|
||||
// Each warp performs a partial softmax of QK0 (i.e. some of the online softmax up until but not including the second exponential scaling of the attention block likely)
|
||||
#if WINDOW
|
||||
// floor the max: min_tile can be fully masked, which would NaN via exp2(-inf - -inf)
|
||||
zero(max_vec_prev);
|
||||
add(max_vec_prev, max_vec_prev, -1e4f);
|
||||
col_max(max_vec, att_block[0], max_vec_prev);
|
||||
#else
|
||||
col_max(max_vec, att_block[0]);
|
||||
#endif
|
||||
|
||||
copy(max_vec_prev, max_vec);
|
||||
exp2(scale_vec, scale_vec);
|
||||
@@ -284,21 +327,28 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
|
||||
// All warps then load in the second slice of K (K1)
|
||||
load(k_reg, k_smem[1]);
|
||||
// All warps then collaboratively load in the third slice of K (K2) into shared memory
|
||||
G::load<1, false>(k_smem[0], g.Kg, {batch_idx, 2, head_idx_kv, 0}, swizzled_offsets_K);
|
||||
G::load<1, false>(k_smem[0], g.Kg, {batch_idx, min_tile + 2, head_idx_kv, 0}, swizzled_offsets_K);
|
||||
// All warps then collaboratively load in the second slice of V (V1) into shared memory
|
||||
G::load<1, false>(v_smem[1], g.Vg, {batch_idx, 1, head_idx_kv, 0}, swizzled_offsets_V);
|
||||
G::load<1, false>(v_smem[1], g.Vg, {batch_idx, min_tile + 1, head_idx_kv, 0}, swizzled_offsets_V);
|
||||
asm volatile("s_waitcnt lgkmcnt(0)");
|
||||
asm volatile("s_waitcnt vmcnt(" FA_VM4 ")");
|
||||
__builtin_amdgcn_sched_barrier(0);
|
||||
__builtin_amdgcn_s_barrier();
|
||||
|
||||
// hot loop
|
||||
for (int j = 3; j < max_num_tiles - 1; j += 2) {
|
||||
for (int j = min_tile + 3; j < max_num_tiles - 1; j += 2) {
|
||||
// Cluster 0:
|
||||
// QK1
|
||||
zero(att_block[1]);
|
||||
transpose(k_reg_transposed, k_reg);
|
||||
mma_AtB(att_block[1], k_reg_transposed, q_reg_transposed, att_block[1]);
|
||||
#if WINDOW
|
||||
mul(att_block[1], att_block[1], TEMPERATURE_SCALE);
|
||||
#endif
|
||||
#if WINDOW
|
||||
// window masks interior tiles that causal skips
|
||||
mask_kv_tile(att_block[1], tile_idx, j - 2, neg_inf_v, lane);
|
||||
#endif
|
||||
// Finish softmax for QK0
|
||||
exp2(att_block[0].tiles[1][0], att_block[0].tiles[1][0]);
|
||||
mul(norm_vec, norm_vec, scale_vec);
|
||||
@@ -359,6 +409,9 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
|
||||
zero(att_block[0]);
|
||||
transpose(k_reg_transposed, k_reg);
|
||||
mma_AtB(att_block[0], k_reg_transposed, q_reg_transposed, att_block[0]);
|
||||
#if WINDOW
|
||||
mul(att_block[0], att_block[0], TEMPERATURE_SCALE);
|
||||
#endif
|
||||
// Finish softmax for QK1
|
||||
exp2(att_block[1].tiles[1][0], att_block[1].tiles[1][0]);
|
||||
mul(norm_vec, norm_vec, scale_vec);
|
||||
@@ -379,7 +432,7 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
|
||||
load(v_reg, v_smem[1]);
|
||||
if constexpr (causal) {
|
||||
const int kv_end_pos = (j) * KV_BLOCK_SIZE;
|
||||
if (q_start_pos < kv_end_pos) { // Only mask if needed
|
||||
if (WINDOW || q_start_pos < kv_end_pos) {
|
||||
mask_kv_tile(att_block[0], tile_idx, j - 1, neg_inf_v, lane);
|
||||
}
|
||||
}
|
||||
@@ -427,6 +480,9 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
|
||||
zero(att_block[1]);
|
||||
transpose(k_reg_transposed, k_reg);
|
||||
mma_AtB(att_block[1], k_reg_transposed, q_reg_transposed, att_block[1]);
|
||||
#if WINDOW
|
||||
mul(att_block[1], att_block[1], TEMPERATURE_SCALE);
|
||||
#endif
|
||||
// Finish softmax for QK2
|
||||
exp2(att_block[0].tiles[1][0], att_block[0].tiles[1][0]);
|
||||
mul(norm_vec, norm_vec, scale_vec);
|
||||
@@ -447,7 +503,7 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
|
||||
load(v_reg, v_smem[0]);
|
||||
if constexpr (causal) {
|
||||
const int kv_end_pos = (max_num_tiles - 2) * KV_BLOCK_SIZE;
|
||||
if (__builtin_expect(q_start_pos < kv_end_pos, 0)) { // Only mask if needed
|
||||
if (__builtin_expect(WINDOW || q_start_pos < kv_end_pos, WINDOW ? 1 : 0)) {
|
||||
mask_kv_tile(att_block[1], tile_idx, max_num_tiles - 3, neg_inf_v, lane);
|
||||
}
|
||||
}
|
||||
@@ -493,6 +549,9 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
|
||||
zero(att_block[0]);
|
||||
transpose(k_reg_transposed, k_reg);
|
||||
mma_AtB(att_block[0], k_reg_transposed, q_reg_transposed, att_block[0]);
|
||||
#if WINDOW
|
||||
mul(att_block[0], att_block[0], TEMPERATURE_SCALE);
|
||||
#endif
|
||||
// Finish softmax for QK3
|
||||
exp2(att_block[1].tiles[1][0], att_block[1].tiles[1][0]);
|
||||
mul(norm_vec, norm_vec, scale_vec);
|
||||
@@ -510,7 +569,7 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
|
||||
load(v_reg, v_smem[1]);
|
||||
if constexpr (causal) {
|
||||
const int kv_end_pos = (max_num_tiles - 1) * KV_BLOCK_SIZE;
|
||||
if (__builtin_expect(q_start_pos < kv_end_pos, 1)) { // Only mask if needed
|
||||
if (__builtin_expect(WINDOW || q_start_pos < kv_end_pos, 1)) {
|
||||
mask_kv_tile(att_block[0], tile_idx, max_num_tiles - 2, neg_inf_v, lane);
|
||||
}
|
||||
}
|
||||
@@ -555,6 +614,9 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
|
||||
zero(att_block[1]);
|
||||
transpose(k_reg_transposed, k_reg);
|
||||
mma_AtB(att_block[1], k_reg_transposed, q_reg_transposed, att_block[1]);
|
||||
#if WINDOW
|
||||
mul(att_block[1], att_block[1], TEMPERATURE_SCALE);
|
||||
#endif
|
||||
// Finish softmax for QK4
|
||||
exp2(att_block[0].tiles[1][0], att_block[0].tiles[1][0]);
|
||||
mul(norm_vec, norm_vec, scale_vec);
|
||||
@@ -572,7 +634,7 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
|
||||
load(v_reg, v_smem[0]);
|
||||
if constexpr (causal) {
|
||||
const int kv_end_pos = (max_num_tiles) * KV_BLOCK_SIZE;
|
||||
if (__builtin_expect(q_start_pos < kv_end_pos, 1)) { // Only mask if needed
|
||||
if (__builtin_expect(WINDOW || q_start_pos < kv_end_pos, 1)) {
|
||||
mask_kv_tile(att_block[1], tile_idx, max_num_tiles - 1, neg_inf_v, lane);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <hip/hip_bf16.h>
|
||||
|
||||
#ifndef ATTN_B
|
||||
#define ATTN_B 2
|
||||
#endif
|
||||
#ifndef ATTN_N
|
||||
#define ATTN_N 8192
|
||||
#endif
|
||||
#ifndef ATTN_H
|
||||
#define ATTN_H 32
|
||||
#endif
|
||||
#ifndef ATTN_H_KV
|
||||
#define ATTN_H_KV 8
|
||||
#endif
|
||||
#ifndef ATTN_D
|
||||
#define ATTN_D 128
|
||||
#endif
|
||||
#ifndef THREADS_PER_BLOCK
|
||||
#define THREADS_PER_BLOCK 256
|
||||
#endif
|
||||
|
||||
constexpr int GROUP_SIZE = ATTN_H / ATTN_H_KV;
|
||||
constexpr int HALF_D = ATTN_D / 2;
|
||||
constexpr int PACKED_D = (GROUP_SIZE + 2) * ATTN_D;
|
||||
|
||||
extern "C" __global__ __launch_bounds__(THREADS_PER_BLOCK) void
|
||||
fused_qkv_rope_forward(
|
||||
__hip_bfloat16* __restrict__ q,
|
||||
__hip_bfloat16* __restrict__ k,
|
||||
__hip_bfloat16* __restrict__ v,
|
||||
const __hip_bfloat16* __restrict__ xqkv,
|
||||
const __hip_bfloat16* __restrict__ freqs_cis) {
|
||||
const int b = blockIdx.x;
|
||||
const int n = blockIdx.y;
|
||||
const int bn = b * ATTN_N + n;
|
||||
const int packed_bn = bn * ATTN_H_KV * PACKED_D;
|
||||
const int q_bn = bn * ATTN_H * ATTN_D;
|
||||
const int kv_bn = bn * ATTN_H_KV * ATTN_D;
|
||||
|
||||
if (threadIdx.x < HALF_D) {
|
||||
const int pair = threadIdx.x;
|
||||
const int even = pair << 1;
|
||||
const float c = static_cast<float>(freqs_cis[((n * HALF_D + pair) * 2) + 0]);
|
||||
const float s = static_cast<float>(freqs_cis[((n * HALF_D + pair) * 2) + 1]);
|
||||
|
||||
for (int kvh = 0; kvh < ATTN_H_KV; kvh++) {
|
||||
const int base = packed_bn + kvh * PACKED_D;
|
||||
|
||||
for (int rep = 0; rep < GROUP_SIZE; rep++) {
|
||||
const int qbase = base + rep * ATTN_D;
|
||||
const int h = kvh * GROUP_SIZE + rep;
|
||||
const float a = static_cast<float>(xqkv[qbase + even]);
|
||||
const float bb = static_cast<float>(xqkv[qbase + even + 1]);
|
||||
const int out = q_bn + h * ATTN_D + even;
|
||||
q[out] = static_cast<__hip_bfloat16>(a * c - bb * s);
|
||||
q[out + 1] = static_cast<__hip_bfloat16>(a * s + bb * c);
|
||||
}
|
||||
|
||||
const float a = static_cast<float>(xqkv[base + GROUP_SIZE * ATTN_D + even]);
|
||||
const float bb = static_cast<float>(xqkv[base + GROUP_SIZE * ATTN_D + even + 1]);
|
||||
const int out = kv_bn + kvh * ATTN_D + even;
|
||||
k[out] = static_cast<__hip_bfloat16>(a * c - bb * s);
|
||||
k[out + 1] = static_cast<__hip_bfloat16>(a * s + bb * c);
|
||||
v[out] = xqkv[base + (GROUP_SIZE + 1) * ATTN_D + even];
|
||||
v[out + 1] = xqkv[base + (GROUP_SIZE + 1) * ATTN_D + even + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user