Compare commits

...
145 Commits
Author SHA1 Message Date
George HotzandGitHub 7252a38b05 Merge branch 'master' into x86_numel 2026-07-11 16:54:49 -07:00
geohot 0af1afc43c remove dtype.count from x86 and others 2026-07-11 16:54:17 -07:00
George HotzandGitHub e69ce4be7f switch x86 to use numel instead of dtype.count (#16985)
* switch x86 to use numel instead of dtype.count

* works?
2026-07-11 15:26:57 -07:00
chenyuandGitHub 047a467bf9 delete UOp._stack and UOp.vectorize [PR] (#16988) 2026-07-11 15:26:55 -04:00
chenyuandGitHub 9d47014fd8 first class STACK [PR] (#16986) 2026-07-11 13:55:32 -04:00
geohot bc68e54447 works? 2026-07-11 09:45:30 -07:00
geohot ff9dd57a05 switch x86 to use numel instead of dtype.count 2026-07-11 08:11:58 -07:00
George HotzandGitHub afeb5c708f x86 simplification (#16983)
* simplify x86

* more extras

* simpler

* work

* fixes

* should pasS

* cmt-n

* delete more

* and more
2026-07-11 08:11:05 -07:00
nimlgenandGitHub 928af24b74 hcq2: mini speed ups (#16984) 2026-07-11 15:48:07 +03:00
sirhcmandGitHub f1ccb85a27 ci: use llvm-20 in amd tests (#16982) 2026-07-11 02:26:10 -04:00
sirhcmandGitHub a7b74ee593 remove slice from rangeify [PR] (#16981) 2026-07-11 02:01:38 -04:00
George HotzandGitHub 43ad225d36 nv_610 support (glm) (#16979)
* nv_610 support

* unbump onnx

* fix autogen workflow
2026-07-10 20:37:30 -07:00
qazalandGitHub 75a4bfddc9 fp8 gemm tests including fused scales (#16980)
* fp8 gemm tests matching fused scales

* work

* diff
2026-07-11 12:33:21 +09:00
chenyuandGitHub 4234a9d727 empty _get_clause is True [PR] (#16975)
remove hack in GroupOp.Broadcastable in dtype_from_uop
2026-07-10 18:03:39 -04:00
George HotzandGitHub 40de90ab19 lil changes to cifar (#16972)
* lil changes to cifar

* lil changes

* pool
2026-07-10 14:19:46 -07:00
nimlgenandGitHub 52e060fd7a hcq2: link cache (#16965)
* x

* x

* mini

* cleaner
2026-07-10 23:47:16 +03:00
chenyuandGitHub df50e0814c explicit error for unbound Variable in program (#16971)
also allow Tensor(UOp, dtype)
2026-07-10 16:23:59 -04:00
chenyuandGitHub 2fda6b3888 fix shape broadcast for symbolic (#16970)
* fix shape broadcast for symbolic

0 or smax was wrong for the unresolved case

* test with null
2026-07-10 15:13:01 -04:00
George HotzandGitHub 77823056d4 remove dead DSP code and pre_matcher (#16968) 2026-07-10 11:27:50 -07:00
chenyuandGitHub 3964eee64f failing tests for Tensor(Variable) (#16967) 2026-07-10 14:17:03 -04:00
George HotzandGitHub 1e55cef493 remove vec from llvm (#16966)
* remove vec from llvm

* scalars are junk

* remove vec from dsp
2026-07-10 10:47:31 -07:00
geohot 2b7c298aaf hotfix: remove SPEC=2 timeout to match other jobs 2026-07-10 09:35:48 -07:00
qazalandGitHub bc3bf1988a asm gemm test cleanups try 2 (#16964)
* fix hipcc requirement

* move hipcc

* fp8 in llama

* update some of those skips

* update dtype

* change
2026-07-10 16:00:24 +09:00
George HotzandGitHub 34452efa22 remove dtypes.vec from cstyle (#16961)
* remove dtypes.vec from cstyle

* bugfix from gpt5.6

* cleanups
2026-07-09 23:11:11 -07:00
sirhcmandGitHub 88c6f02abe remove late_buffer_view, try 2 (#16963) 2026-07-10 00:46:26 -04:00
wozeparrotandGitHub 5cc31e23e6 gptoss: scripts (#16962) 2026-07-09 20:50:20 -07:00
sirhcmandGitHub 414995d1f5 Revert "remove late buffer view" (#16960) 2026-07-09 21:57:42 -04:00
chenyuandGitHub 8d546f55e9 dtypes.weakfloat (#16956) 2026-07-09 19:32:21 -04:00
George HotzandGitHub d80c971ea2 param + cast dtype in arg (#16955)
* param + cast dtype in arg

* require dtype

* fix replace in dtype decomps

* fix x86

* dtype none is fine there

* Revert "dtype none is fine there"

This reverts commit 5315cc3e59.
2026-07-09 16:32:01 -07:00
chenyuandGitHub 78223d690a update SPECIAL spec (#16952)
also updated tests with bad UOp
2026-07-09 19:05:34 -04:00
George HotzandGitHub 95681f17ee remove extra dtypes from tests (#16954)
* remove extra dtypes from tests

* a few more
2026-07-09 14:59:56 -07:00
George HotzandGitHub 1df49a7bf5 dtype doesn't need to be explicit in most places now (glm) [PR] (#16951)
* dtype doesn't need to be explicit in most places now (glm) [PR]

* fix amd pcode

* image idx long/int meh

* real image fix
2026-07-09 14:41:34 -07:00
sirhcmandGitHub 851e5727d2 remove late buffer view (#16941) 2026-07-09 17:32:35 -04:00
chenyuandGitHub 93338df753 real dtypes.index (#16950) 2026-07-09 16:28:39 -04:00
George HotzandGitHub 8d4c9d1058 make the upat compiler match the spec (glm) [PR] (#16948)
* make the upat compiler match the spec

* cleanups

* CUSTOMI/CUSTOM

* fix that test

* pyliteral

* fix variable shadowing
2026-07-09 12:30:39 -07:00
chenyuandGitHub ba2c68b1ed start dtypes.index [PR] (#16949) 2026-07-09 15:12:23 -04:00
sirhcmandGitHub da435c719b tinyfs: use assign (#16947) 2026-07-09 14:52:42 -04:00
chenyuandGitHub 8b96b95af5 tighter pm_no_weakints [PR] (#16946)
towards spliting weakint and index
2026-07-09 14:31:16 -04:00
George HotzandGitHub 904b51a783 dtype fixups (grok) [PR] (#16940)
* dtype fixups

* a few more

* amd fixes

* revert that

* Remove comment in test_graph_rewrite_div_folding_bug

Removed comment about STACK dtype matching its element dtype in the test for graph rewrite division folding bug.
2026-07-09 11:03:22 -07:00
nimlgenandGitHub c9f6b2e42b hcq2: prereq for linker (#16945) 2026-07-09 17:21:21 +03:00
qazalandGitHub 6dbc35b8e0 fix am_smi for non interactive shell (#16942) 2026-07-09 13:32:59 +09:00
wozeparrotandGitHub 2971343a60 fa: support D=64 and attn sink (#16935) 2026-07-08 20:03:47 -07:00
George HotzandGitHub 00d01d978d add dtype production rule checked with SPEC=2 (#16939)
* add dtype production rule checked with SPEC=2

* no None support yet

* detach passes through

* work

* fix

* line len
2026-07-08 18:04:34 -07:00
George HotzandGitHub 7f8bbe5407 hlb_cifar free speed (#16938)
* free speed for hlb cifar

* mirror
2026-07-08 15:49:48 -07:00
chenyuandGitHub e2fc928d85 use get_idx and get_valid in coalese [PR] (#16937) 2026-07-08 17:20:54 -04:00
chenyuandGitHub 8085bd57ec remove old stale metadata codes [pr] (#16936) 2026-07-08 17:11:15 -04:00
nimlgenandGitHub 52b84adc2a hcq2: tiny changes (#16934) 2026-07-08 23:17:28 +03:00
George HotzandGitHub fdffc6c0c8 remove dtypes base (#16931)
* remove dtypes base

* find/replace bug

* vcount is junk too
2026-07-08 11:48:11 -07:00
George HotzandGitHub 63cb1369cb remove ImageDType (glm) (#16918)
* remove ImageDType (glm)

* gpt work

* better

* is_image_shape

* cleanups

* remove crap from index

* revert crap python change

* image index with float, this needs to be fixed

* simp
2026-07-08 11:27:52 -07:00
George HotzandGitHub 6358f939e7 param arg is always the shape (#16914)
* param arg is always the shape

* earlier

* pm_no_weakints

* real fix
2026-07-08 10:51:56 -07:00
George HotzandGitHub 6e05cbdbcb remove the dtype.scalar calls everywhere [pr] (#16930)
* remove the dtype.scalar calls everywhere

* remove base everywhere

* Revert "remove base everywhere"

This reverts commit 0dd0a5a243.
2026-07-08 10:10:03 -07:00
George HotzandGitHub e4d0d634d4 remove a bunch of dtype.count [PR] (#16929)
* remove a bunch of dtype.count

* remove nonsense rules

* rm dead code
2026-07-08 09:34:46 -07:00
George HotzandGitHub a951650865 lil cleanups from reduce and expand [PR] (#16928) 2026-07-08 08:54:19 -07:00
chenyuandGitHub e0c70b6b82 mixin/op.py [PR] (#16926)
* mixin/op.py [PR]

* that
2026-07-07 21:45:52 -04:00
wozeparrotandGitHub ab131c2086 gptoss: zero-1 optim (#16916) 2026-07-07 18:34:47 -07:00
4f7d4e95d7 fix <= 2 case (#16923)
Co-authored-by: George Hotz <[email protected]>
2026-07-07 18:17:58 -07:00
chenyuandGitHub c26468c5d0 keccak and hash to mixin [PR] (#16925) 2026-07-07 21:12:46 -04:00
chenyuandGitHub c43a3fdebb bitcast to mixin [PR] (#16924) 2026-07-07 20:03:29 -04:00
DivijandGitHub 9f388d42b7 padded maxpool with -inf as done in pytorch (#16858) 2026-07-07 16:10:44 -07:00
George HotzandGitHub 4a51047146 remove unused pm_syntactic_sugar (#16920)
* remove unused pm_syntactic_sugar

* merge in group for reduce

* merge in add loads

* that ctx is unused

* late loads

* Revert "late loads"

This reverts commit ee70cffcee.
2026-07-07 15:40:52 -07:00
George HotzandGitHub 2aebb6f6c4 move mop cleanup [pr] (#16915)
* move mop cleanup [pr]

* those are mop cleanups

* more mop_cleanups

* revert

* movement.py
2026-07-07 14:30:32 -07:00
qazalandGitHub 3f248070b2 add movement op rendering to pyrender (#16910) 2026-07-07 13:48:48 -07:00
George HotzandGitHub 2fc7e5341b final removal of PtrDType (glm) (#16913)
* final removal of PtrDType (glm)

* junk
2026-07-07 13:37:33 -07:00
chenyuandGitHub 27de6c6db0 use more UOp.valid method [PR] (#16912)
prerequisite to fix WHERE
2026-07-07 16:27:37 -04:00
chenyuandGitHub fb4a781fca fix a few imports [PR] (#16909) 2026-07-07 14:21:13 -04:00
George HotzandGitHub d8fbbff260 EXPAND adds dims to the front (glm) (#16908)
* EXPAND adds dims to the front (glm)

* more read images

* simpler + spec

* spec should use n instead of s'

* bump comma to 12
2026-07-07 11:03:06 -07:00
nimlgenandGitHub 91cefdb52a hcq2: rewrite patch split (#16907)
* x

* new patch splitter

* style

* x

* x
2026-07-07 20:43:52 +03:00
George HotzandGitHub 0035bb6fa8 only front reduce (#16901)
* only front reduce

* lint + spec

* that should fail

* meh
2026-07-07 09:55:07 -07:00
George HotzandGitHub 6f1a983493 Ops.GROUP has no shape, like sink (#16906)
* Ops.GROUP has no shape, like sink

* readme
2026-07-07 08:20:38 -07:00
Ben WaldronandGitHub 357b7544d6 Allow non-const alt values for wgsl gated load (#16885)
* Allow non-const alt values for wgsl gated load

* Add regression test

* Make z3 happy

* Change to int to match nearby test + re-run CI

* Remove ptr
2026-07-07 08:18:02 -07:00
qazalandGitHub f0a06fae4c viz: readable REWRITE_ERROR (#16903)
* viz: readable REWRITE_ERROR

* traceback.format_exc handles this
2026-07-07 17:25:42 +09:00
qazalandGitHub 9454e2ddbf viz: inf loop early cutoff (#16902)
* pause on error

* reorder err

* simpler
2026-07-07 17:00:45 +09:00
wozeparrotandGitHub a805ce03b1 gptoss: model train (#16884) 2026-07-06 22:07:50 -07:00
George HotzandGitHub 6e44176cfe fix flash attention example (#16900)
* fix spec for amd_copy_matmul

* remove shaped wmma

* fix flash attention example?

* dead code
2026-07-06 20:51:32 -07:00
George HotzandGitHub d94ad4444e fix spec for amd_copy_matmul + remove SHAPED_WMMA (#16897)
* fix spec for amd_copy_matmul

* remove shaped wmma
2026-07-06 19:00:10 -07:00
George HotzandGitHub 40d112d4d6 remove ptr=True from index (#16898) 2026-07-06 18:34:18 -07:00
George HotzandGitHub 6b8b2f5aeb remove PtrDtype from tests (#16896)
* remove PtrDtype from tests

* fixes

* not needed

* no -1
2026-07-06 18:22:22 -07:00
chenyuandGitHub eb3cb16cc1 clean up pm_remove_invalid [PR] (#16895) 2026-07-06 20:10:52 -04:00
George HotzandGitHub 4faed79216 remove placeholder from is_ptr (#16893)
* remove placeholder from is_ptr

* simpler

* needed

* remove rewriter

* fix tests
2026-07-06 16:55:56 -07:00
George HotzandGitHub ccd3428aad remove ptrdtype from kernel graph (#16892)
* remove ptrdtype

* tag instead of set

* no ptrdtype

* debuf like the ranges
2026-07-06 14:59:07 -07:00
George HotzandGitHub 9512dc30f4 remove vec from const (#16889)
* remove vec from const

* reject in spec

* remove all vector dtypes

* not needed

* remove that

* clean up invalid
2026-07-06 12:23:07 -07:00
nimlgenandGitHub 47f9677f67 hcq2: shuffle passes (#16886) 2026-07-06 21:56:55 +03:00
geohot 465c689d1f reduce removes axes now 2026-07-06 11:52:11 -07:00
RaineandGitHub e86c56f00f upstream decouple isa reg property from uop (#16804)
* decouple isa reg property from uop

* fix linter errors, should add strong typing once clear INS tag spec

* changed name

* remove import
2026-07-06 11:28:07 -07:00
sirhcmandGitHub deb5232413 skip test_index_fused_out_of_bounds if CL (#16888) 2026-07-06 13:53:58 -04:00
wozeparrotandGitHub a330bfffc9 gptoss: remove contiguous (#16882) 2026-07-05 17:48:32 -07:00
wozeparrotandGitHub 35522af0df gptoss: use quantize_mxfp8 (#16881) 2026-07-05 17:48:24 -07:00
nimlgenandGitHub 62cde59cb5 hcq2: tiny changes (#16880) 2026-07-06 00:24:55 +03:00
chenyuandGitHub 4462ec3013 remove sintify and sgep [PR] (#16879) 2026-07-05 13:28:23 -04:00
nimlgenandGitHub 17e4f9b6f9 hcq2: simpler (#16878) 2026-07-05 18:26:19 +03:00
chenyuandGitHub a398b678cd skip test_index_fused_out_of_bounds (#16877)
reads OOB now
2026-07-05 09:30:37 -04:00
chenyuandGitHub dd6aa77fd1 delete Ops.UNROLL and Ops.CONTRACT (#16872)
82 ops
2026-07-05 08:28:40 -04:00
qazalandGitHub 96b94bbf1d skip sqtt tests that unpickle UOps (#16875) 2026-07-05 18:52:31 +09:00
chenyuandGitHub 0cff3d74ca do not use Ops.UNROLL and Ops.CONTRACT [PR] (#16870) 2026-07-04 19:37:13 -04:00
George HotzandGitHub c92a4c0442 move devectorizer remains into coalese (#16869)
* move devectorizer remains into coalese

* fix flaky
2026-07-04 15:36:45 -07:00
George HotzandGitHub 43c1bbceb1 remove pm_render (#16868)
* remove pm_render

* move merge_reduce_ends

* ext devectorize
2026-07-04 15:18:43 -07:00
George HotzandGitHub a145fdce4b remove the .gep method (#16867) 2026-07-04 14:07:09 -07:00
wozeparrotandGitHub 971800b46a support none block divisible embed size (#16865) 2026-07-04 13:46:47 -07:00
George HotzandGitHub 3fd6f3d28c no more GEP (#16866) 2026-07-04 13:42:28 -07:00
George HotzandGitHub d76bcbaa02 deletions after new codegen (#16864)
* deletions after new codegen

* delete the expander
2026-07-04 13:12:11 -07:00
George HotzandGitHub c7e7687bd3 new codegen, try 3 (#16781)
* new codegen, try 3 [pr]

* reduce

* diffs from cg2

* minor fixes

* mergable

* new local buffers

* fixes

* correct fix

* add barrier, and image is after that

* fix get

* wmma work

* devec wmma

* fixes for wmma

* slightly more flexible

* fix tensor cores

* fix linter

* tests

* remove invalid?

* remove extra

* wmma merge

* flip reshape/permute on wmma

* broadcast_binary None

* update symbolic for vecless

* x86 fix

* fix custom llama kernel

* is_ptr hack

* fix pre-commit

* fix wmma

* simpler

* fixes

* better hreduce

* move reduce axes

* all wmma tests pass

* allow stacked wmma

* permute cleanups

* late permute

* correct permute flip

* fix group for reduce

* fix wmma permuted

* all pass

* mop cleanup

* new test

* push permute/reshape

* revert that

* half
2026-07-04 12:55:44 -07:00
nimlgenandGitHub fdf434062b hcq2: simpler (#16862)
* x

* x

* x
2026-07-04 22:07:37 +03:00
chenyuandGitHub 09922d2326 rework propagate_invalid [PR] (#16861) 2026-07-04 12:37:22 -04:00
qazalandGitHub 07f7383d29 llama: remove unused bf16 assembly gemm (#16859)
* only hk bf16 gemm

* rm asm gemm

* more cleanup

* half isn't supported in asm gemm anymore

* more test edits

* unused

* remove TestMagicGu

* uop gemm is still tested

* minimal diff
2026-07-04 18:41:54 +09:00
sirhcmandGitHub 353d8f1e13 use rusticl in ci (#16852) 2026-07-03 23:58:58 -04:00
chenyuandGitHub 709251e22d more generic fold_add_divmod_recombine [pr] (#16854) 2026-07-03 22:00:59 -04:00
George HotzandGitHub 62c951b2ce lil changes from codegen 6 (#16855) 2026-07-03 17:08:18 -07:00
George HotzandGitHub 41d6731bfd reduce removes ones (#16847)
* reduce removes ones

* test changes

* lil clean
2026-07-03 13:10:08 -07:00
nimlgenandGitHub 13d99388ab system: raise if unbind is unsuccessful (#16846) 2026-07-03 22:01:30 +03:00
chenyuandGitHub 5fb3cfb9bc remove early THREEFRY const folding [PR] (#16850)
it folds automatically once decomposed
2026-07-03 15:00:35 -04:00
George HotzandGitHub ed3dec4674 remove noop reshapes (#16849) 2026-07-03 11:39:57 -07:00
chenyuandGitHub ed7981f8f2 move divandmod next to symbolic [PR] (#16848) 2026-07-03 14:21:59 -04:00
chenyuandGitHub ee5e8cea68 no dtype.vec in select_dtype [PR] (#16845) 2026-07-03 13:19:12 -04:00
chenyuandGitHub 3dc44f3692 image vectorize -> _stack [PR] (#16844) 2026-07-03 12:51:35 -04:00
chenyuandGitHub 2f0690dcc2 minor gpudims cleanup [PR] (#16843) 2026-07-03 11:23:23 -04:00
chenyuandGitHub 6345c2883d simpler get_grouped_dims (#16842)
works for more than 3D, also deleted get_contraction
2026-07-03 10:29:11 -04:00
nimlgenandGitHub d73bca617b hcq2: fix deps tracking (#16841) 2026-07-03 15:51:25 +03:00
wozeparrotandGitHub 42be177ea8 gpt-oss-20b: model (#16837)
* feat: gpt-oss model file

* clean: don't need that function

* feat: working model
2026-07-03 01:29:56 -07:00
qazalandGitHub fd18dbad7b viz: do not check src[0] for INDEX/STAGE rendering (#16839)
* viz: do not check src[0] for INDEX/STAGE rendering

* index too large
2026-07-03 15:11:08 +09:00
geohot 0240998b06 hotfix: that test_ellipsis_with_int test was important 2026-07-02 19:19:38 -07:00
George HotzandGitHub e6fbede157 changes from new codegen (#16836) 2026-07-02 17:01:37 -07:00
sirhcmandGitHub e74c7042c3 ci: use tinymesa from pypi (#16835) 2026-07-02 19:25:10 -04:00
chenyuandGitHub bc115d44fd don't check isinstance ElementwiseMixin [PR] (#16834)
won't work once Tensor no longer inherits mixin
2026-07-02 18:28:52 -04:00
nimlgenandGitHub c2c784e028 hcq2: rewrite (#16775)
* hcq2: rewrite

* rw

* mid

* jit

* tmp

* x

* x

* revert

* x

* x
2026-07-03 00:00:08 +03:00
chenyuandGitHub 29b59962c9 ops and callify cleanups [PR] (#16833) 2026-07-02 16:51:44 -04:00
chenyuandGitHub e9dd2990b6 delete UOp.multibase [PR] (#16832) 2026-07-02 16:00:01 -04:00
chenyuandGitHub 682b098542 fix multi write after read in create_schedule (#16831) 2026-07-02 15:12:48 -04:00
chenyuandGitHub 48c2081378 clean up create_schedule [PR] (#16830) 2026-07-02 14:49:01 -04:00
chenyuandGitHub 3e73a2542b _apply_map_to_tensors always use walk rewrite (#16827)
with assign it's wrong to recursively apply, fixed a random bug that counter does not update after round 2
2026-07-02 10:43:25 -04:00
chenyuandGitHub b2be3c6c57 disk_copy_is_buffer maps each disk COPY once [PR] (#16826) 2026-07-02 09:58:34 -04:00
chenyuandGitHub b61285efa4 remove apply_after [PR] (#16825)
tighten buffer_map spec and becomes_map is much smaller now, all intermediate afters are not needed
2026-07-02 09:19:35 -04:00
qazalandGitHub 149fd91e22 viz: no tqdm noise in serve.py (#16824) 2026-07-02 15:37:26 +09:00
sirhcmandGitHub e0d7696ccd split benchmarks (#16812) 2026-07-01 22:28:15 -04:00
chenyuandGitHub 2e62dd308d move fs_load and fs_store to nn (#16821)
similar to safe_load and safe_save
2026-07-01 22:01:15 -04:00
chenyuandGitHub c8aed121cf fix WAR in create_schedule (#16823)
* fix WAR in create_schedule

combined dep building, fixed a case that realize(x, y) != realize(y, x)

* fix
2026-07-01 22:00:51 -04:00
chenyuandGitHub efd256b2a3 fix precompiled STORE+AFTER (#16822) 2026-07-01 17:07:37 -04:00
chenyuandGitHub 4b3de041e2 delete Ops.DEVICE (#16820)
85 ops
2026-07-01 12:20:05 -04:00
chenyuandGitHub fd7fd9cdca move GETADDR device to its arg (#16808) 2026-07-01 09:14:07 +03:00
George HotzandGitHub 3c07f31790 remove shape special case for Ops.CAST (#16817) 2026-06-30 22:37:07 -07:00
sirhcmandGitHub 4a4de1a966 mesa: use stdio autogen (#16815) 2026-06-30 23:33:55 -04:00
chenyuandGitHub 1261d719a8 tighter GETTUPLE spec (#16816) 2026-06-30 23:33:10 -04:00
chenyuandGitHub 59c7874724 fix isclose with scalar other (#16813) 2026-06-30 21:56:33 -04:00
chenyuandGitHub 372774fc41 empty to CreationMixin [PR] (#16811) 2026-06-30 20:42:42 -04:00
nimlgenandGitHub 3732d6b43c jit prereqs for new hcq2 (#16807)
* jit prereqs for new hcq2

* ty
2026-06-30 23:00:10 +03:00
sirhcmandGitHub 8472d374ab ci: parallelize fuzz tests (#16805) 2026-06-30 14:14:32 -04:00
178 changed files with 33909 additions and 9480 deletions
+14 -25
View File
@@ -10,7 +10,7 @@ inputs:
required: false
default: '' # if you don't set a key, it doesn't cache
deps:
description: 'Extra dependency groups (comma separated)'
description: 'Extra dependency groups (space separated)'
required: false
default: ''
pydeps:
@@ -41,10 +41,6 @@ inputs:
description: "Install LLVM?"
required: false
default: 'false'
mesa:
description: "Install mesa (true, false, cpu)"
required: false
default: 'false'
tinydreno:
description: "Install tinydreno"
required: false
@@ -114,7 +110,8 @@ runs:
shell: bash
run: |
uv venv .venv
uv pip install --python .venv -e ".[${{ inputs.deps }}]" ${{ inputs.pydeps }} --torch-backend cpu --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/Triton-Nightly/pypi/simple/
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/
- name: Install dependencies in venv (without extra)
if: inputs.deps == ''
shell: bash
@@ -146,11 +143,6 @@ runs:
echo 'Acquire::http::Pipeline-Depth "5";' | sudo tee -a /etc/apt/apt.conf.d/99parallel
echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' | sudo tee -a /etc/apt/apt.conf.d/99keep-debs
- name: Add OpenCL Repo
if: inputs.opencl == 'true' && runner.os == 'Linux'
shell: bash
run: echo "deb [ allow-insecure=yes ] https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list
- name: Add AMD Repo (Linux)
if: inputs.amd == 'true' && runner.os == 'Linux'
shell: bash
@@ -176,10 +168,7 @@ runs:
pkgs=""
# **** OpenCL ****
if [[ "${{ inputs.opencl }}" == "true" ]]; then
pkgs+=" opencl-headers \
intel-oneapi-runtime-openmp=2023.2.1-16 intel-oneapi-runtime-compilers-common=2023.2.1-16 intel-oneapi-runtime-compilers=2023.2.1-16 \
intel-oneapi-runtime-dpcpp-sycl-opencl-cpu=2023.2.1-16 intel-oneapi-runtime-tbb-common=2021.10.0-49541 \
intel-oneapi-runtime-tbb=2021.10.0-49541 intel-oneapi-runtime-opencl=2023.2.1-16"
pkgs+=" ocl-icd-opencl-dev"
fi
# **** AMD ****
if [[ "${{ inputs.amd }}" == "true" ]]; then
@@ -286,18 +275,18 @@ runs:
shell: bash
run: brew install llvm@20
# **** mesa ****
- name: Install mesa (linux)
if: inputs.mesa != 'false' && runner.os == 'Linux'
shell: bash
run: sudo curl -fL https://github.com/sirhcm/tinymesa/releases/download/v1/libtinymesa${{ inputs.mesa == 'cpu' && '_cpu' || '' }}-mesa-25.2.7-linux-amd64.so -o /usr/lib/libtinymesa${{ inputs.mesa == 'cpu' && '_cpu' || '' }}.so
- name: Install mesa (macOS)
if: inputs.mesa != 'false' && runner.os == 'macOS'
shell: bash
run: brew install sirhcm/tinymesa/tinymesa${{ inputs.mesa == 'cpu' && '_cpu' || '' }}
# *** 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'
shell: bash
run: |
sudo curl -fL https://github.com/sirhcm/tinymesa/releases/download/rusticl-v1/libRusticlOpenCL.so.1.0.0 -o /usr/lib/libRusticlOpenCL.so
sudo mkdir -p /etc/OpenCL/vendors
echo "/usr/lib/libRusticlOpenCL.so" | sudo tee /etc/OpenCL/vendors/rusticl.icd
echo "RUSTICL_ENABLE=llvmpipe" >> "$GITHUB_ENV"
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
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
python3 -c "from tinygrad.runtime.autogen import opencl"
python3 -c "from tinygrad.runtime.autogen import cuda, nvrtc, nvjitlink, nv_570, nv_580, nv"
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.am import *"
python3 -c "from tinygrad.runtime.autogen.nv_regs import *"
+207 -100
View File
@@ -81,8 +81,59 @@ jobs:
# source /tmp/tinygrad_pytest_ci/bin/activate
# pytest -nauto --durations=20
sharedbenchmarks:
name: Benchmark (DEV=${{ matrix.dev }})
llmbenchmark:
name: LLM (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
defaults:
run:
shell: bash -e -o pipefail {0}
env:
DEV: ${{ matrix.dev }}
if: github.repository_owner == 'tinygrad'
steps:
- name: Checkout Code
uses: actions/checkout@v6
- 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
- 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 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.5
# qwen3.5:35b-a3b doesn't fit on mac
if: ${{ matrix.dev != 'METAL' }}
run: BENCHMARK_LOG=qwen35_35b-a3b JITBEAM=2 IGNORE_BEAM_CACHE=1 python3 -m tinygrad.llm -m qwen3.5: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 process replay tests
uses: ./.github/actions/process-replay
cifarbenchmark:
name: HLB-CIFAR10 (DEV=${{ matrix.dev }})
runs-on: [self-hosted, "${{ matrix.dev == 'METAL' && 'macOS' || matrix.dev == 'AMD' && 'tinybox' || 'tinyboxgreen' }}"]
strategy:
fail-fast: false
@@ -111,6 +162,149 @@ 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 10 CIFAR training steps
env:
ASSERT_MIN_STEP_TIME: ${{ matrix.dev == 'NV' && '130' || matrix.dev == 'AMD' && '200' || '3000' }}
run: BENCHMARK_LOG=cifar_10steps STEPS=10 python3 examples/hlb_cifar10.py
- name: Run 10 CIFAR training steps w HALF
env:
ASSERT_MIN_STEP_TIME: ${{ matrix.dev == 'NV' && '120' || matrix.dev == 'AMD' && '235' || '3000' }}
run: BENCHMARK_LOG=cifar_10steps_half STEPS=10 DEFAULT_FLOAT=HALF python3 examples/hlb_cifar10.py
- name: Run full CIFAR training w 1 GPU
# 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
mlperfbenchmark:
name: MLPerf (${{ matrix.dev }})
runs-on: [self-hosted, Linux, "${{ matrix.dev == 'AMD' && 'tinybox' || 'tinyboxgreen' }}"]
strategy:
fail-fast: false
matrix:
dev: ['AMD', 'NV']
timeout-minutes: 60
defaults:
run:
shell: bash -e -o pipefail {0}
env:
DEV: ${{ matrix.dev }}
if: github.repository_owner == 'tinygrad'
steps:
- name: Checkout Code
uses: actions/checkout@v6
- 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 extra/datasets
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: 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
sdbenchmark:
name: Stable Diffusion (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
defaults:
run:
shell: bash -e -o pipefail {0}
env:
DEV: ${{ matrix.dev }}
if: github.repository_owner == 'tinygrad'
steps:
- name: Checkout Code
uses: actions/checkout@v6
- 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: 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 Stable Diffusion
env:
ASSERT_MIN_STEP_TIME: ${{ matrix.dev == 'METAL' && '720' || matrix.dev == 'AMD' && '550' || '0' }}
run: BENCHMARK_LOG=stable_diffusion python3 examples/stable_diffusion.py --fp16 --seed 0 --noshow --timing
- name: Run SDXL
if: ${{ matrix.dev != 'NV' }}
env:
ASSERT_MIN_STEP_TIME: ${{ matrix.dev == 'METAL' && '5000' || matrix.dev == 'AMD' && '3200' || '2000' }}
run: BENCHMARK_LOG=stable_diffusion_xl CAPTURE_PROCESS_REPLAY=0 python3 examples/sdxl.py --seed 0 --noshow --timing
- 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' }}"]
strategy:
fail-fast: false
matrix:
dev: ['METAL', 'AMD', 'NV']
timeout-minutes: 60
defaults:
run:
shell: bash -e -o pipefail {0}
env:
DEV: ${{ matrix.dev }}
if: github.repository_owner == 'tinygrad'
steps:
- name: Checkout Code
uses: actions/checkout@v6
- 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: 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: Test tiny
run: |
DEBUG=2 python -m pytest -rA test/test_tiny.py
if [[ "${{ matrix.dev }}" == "NV" ]]; then
DEBUG=2 DEV=CUDA python -m pytest -rA test/test_tiny.py
fi
- name: Test tensor cores
run: |
if [[ "${{ matrix.dev }}" == "METAL" ]]; then
@@ -150,109 +344,24 @@ jobs:
env:
HALF: ${{ matrix.dev == 'NV' && '1' || '0' }}
run: CAPTURE_PROCESS_REPLAY=0 BIG=2 ${{ matrix.dev == 'METAL' && 'MPS=1' || 'TORCHCUDA=1' }} python3 test/speed/external_test_speed_v_torch.py
- name: Run Stable Diffusion
env:
ASSERT_MIN_STEP_TIME: ${{ matrix.dev == 'METAL' && '720' || matrix.dev == 'AMD' && '550' || '0' }}
run: BENCHMARK_LOG=stable_diffusion python3 examples/stable_diffusion.py --fp16 --seed 0 --noshow --timing
- name: Run SDXL
if: ${{ matrix.dev != 'NV' }}
env:
ASSERT_MIN_STEP_TIME: ${{ matrix.dev == 'METAL' && '5000' || matrix.dev == 'AMD' && '3200' || '2000' }}
run: BENCHMARK_LOG=stable_diffusion_xl CAPTURE_PROCESS_REPLAY=0 python3 examples/sdxl.py --seed 0 --noshow --timing
- 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.5
# qwen3.5:35b-a3b doesn't fit on mac
- name: Test speed vs theoretical
# no targets for METAL
if: ${{ matrix.dev != 'METAL' }}
run: BENCHMARK_LOG=qwen35_35b-a3b JITBEAM=2 IGNORE_BEAM_CACHE=1 python3 -m tinygrad.llm -m qwen3.5: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
run: IGNORE_BEAM_CACHE=1 CCACHE=0 BEAM_DEBUG=1 DEBUG=1 python -m pytest -rA test/external/speed_v_theoretical.py --durations=20
- name: Train MNIST
run: time TARGET_EVAL_ACC_PCT=96.0 python3 examples/beautiful_mnist.py
- name: Run 10 CIFAR training steps
env:
ASSERT_MIN_STEP_TIME: ${{ matrix.dev == 'NV' && '130' || matrix.dev == 'AMD' && '200' || '3000' }}
run: BENCHMARK_LOG=cifar_10steps STEPS=10 python3 examples/hlb_cifar10.py
- name: Run 10 CIFAR training steps w HALF
env:
ASSERT_MIN_STEP_TIME: ${{ matrix.dev == 'NV' && '120' || matrix.dev == 'AMD' && '230' || '3000' }}
run: BENCHMARK_LOG=cifar_10steps_half STEPS=10 DEFAULT_FLOAT=HALF python3 examples/hlb_cifar10.py
- uses: actions/upload-artifact@v7
if: ${{ matrix.dev != 'AMD' }}
with:
name: Speed (${{ matrix.dev }})
path: |
onnx_inference_speed.csv
- name: Run process replay tests
uses: ./.github/actions/process-replay
tinyboxbenchmark:
name: Tinybox Benchmark (${{ matrix.dev }})
runs-on: [self-hosted, Linux, "${{ matrix.dev == 'AMD' && 'tinybox' || 'tinyboxgreen' }}"]
strategy:
fail-fast: false
matrix:
dev: ['AMD', 'NV']
timeout-minutes: 60
defaults:
run:
shell: bash -e -o pipefail {0}
env:
DEV: ${{ matrix.dev }}
if: github.repository_owner == 'tinygrad'
steps:
- name: Checkout Code
uses: actions/checkout@v6
- 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
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: test/external/process_replay/reset.py
- name: Test tiny
run: |
DEBUG=2 python -m pytest -rA test/test_tiny.py
if [[ "${{ matrix.dev }}" == "NV" ]]; then
DEBUG=2 DEV=CUDA python -m pytest -rA test/test_tiny.py
fi
- name: Test speed vs theoretical
run: IGNORE_BEAM_CACHE=1 CCACHE=0 BEAM_DEBUG=1 DEBUG=1 python -m pytest -rA test/external/speed_v_theoretical.py --durations=20
- 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
- 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 w 1 GPU
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
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 (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
- uses: actions/upload-artifact@v7
if: ${{ matrix.dev != 'AMD' }}
with:
name: Speed (${{ matrix.dev }})
path: |
onnx_inference_speed.csv
- name: Run process replay tests
uses: ./.github/actions/process-replay
@@ -295,7 +404,7 @@ jobs:
testcommalatest:
name: comma Benchmark (0.11.0)
runs-on: [self-hosted, Linux, comma]
timeout-minutes: 10
timeout-minutes: 12
defaults:
run:
shell: bash -e -o pipefail {0}
@@ -326,7 +435,7 @@ jobs:
testcommaold:
name: comma Benchmark (0.10.1)
runs-on: [self-hosted, Linux, comma]
timeout-minutes: 10
timeout-minutes: 12
defaults:
run:
shell: bash -e -o pipefail {0}
@@ -469,8 +578,6 @@ 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
- name: Run full CIFAR training w 1 GPU
run: time BENCHMARK_LOG=cifar DEFAULT_FLOAT=HALF STEPS=1000 TARGET_EVAL_ACC_PCT=93.0 python3 examples/hlb_cifar10.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
+11 -18
View File
@@ -237,7 +237,7 @@ jobs:
deps: testing_unit
llvm: 'true'
- name: Test SPEC=2
run: SPEC=2 pytest --maxfail=10 -n auto --durations=30 test/unit test/backend test/opt --ignore test/backend/test_custom_kernel.py --ignore test/unit/test_hashing.py --timeout 60 -k "not test_setitem_big" -k "not test_conv2d_ceildiv_edge_case" --splits 2 --group ${{ matrix.group }}
run: SPEC=2 pytest --maxfail=10 -n auto --durations=30 test/unit test/backend test/opt --ignore test/backend/test_custom_kernel.py --ignore test/unit/test_hashing.py -k "not test_setitem_big" -k "not test_conv2d_ceildiv_edge_case" --splits 2 --group ${{ matrix.group }}
fuzzing:
name: Fuzzing
@@ -251,14 +251,9 @@ jobs:
with:
key: fuzzing-unit
deps: testing_unit
- name: Fuzz Test symbolic
run: python test/external/fuzz_symbolic.py
- name: Fuzz Test symbolic (symbolic divisors)
run: python test/external/fuzz_symbolic_symbolic_div.py
- name: Fuzz Test fast idiv
run: python test/external/fuzz_fast_idiv.py
- name: Fuzz Test shape ops
run: python test/external/fuzz_shape_ops.py
- name: Fuzz Tests
run: |
parallel --tagstring '[{}]' 'python test/external/fuzz_{}.py' ::: symbolic symbolic_div fast_idiv shape_ops
testopenclimage:
name: CL IMAGE Tests
@@ -296,7 +291,8 @@ 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=55 FLOAT16=1 DEV=CL 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=1391 ALLOWED_GATED_READ_IMAGE=58 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: |
DEV=CL IMAGE=1 SELFTEST=1 python examples/openpilot/compile3.py https://github.com/haraschax/filedump/raw/refs/heads/master/driving_vision_fp32.onnx
@@ -443,9 +439,8 @@ jobs:
uses: ./.github/actions/setup-tinygrad
with:
key: linux-${{ matrix.dev }}
deps: testing_unit
deps: "testing_unit${{ contains(matrix.dev, 'LVP') && ' mesa' || '' }}"
llvm: ${{ contains(matrix.dev, 'LLVM') || contains(matrix.dev, 'LVP') || contains(matrix.dev, 'CLANG') }}
mesa: ${{ contains(matrix.dev, 'LVP') && 'cpu' || 'false' }}
webgpu: ${{ matrix.dev == 'WEBGPU' }}
opencl: ${{ matrix.dev == 'CL' }}
- name: Set env
@@ -497,7 +492,7 @@ jobs:
AMD: 0
run: |
PYTHONPATH=. DEV=NULL:HIP:gfx1100 python extra/mmapeak/mmapeak.py
PYTHONPATH=. DEV=NULL:HIP:gfx950 python3 -m pytest -n=auto test/testextra/test_tk.py test/backend/test_asm_gemm.py
PYTHONPATH=. DEV=NULL:HIP:gfx950 python3 -m pytest -n=auto test/testextra/test_tk.py
- name: Run matmul on MOCKKFD
run: |
PYTHONPATH="." DEV=MOCKKFD+AMD N=256 python3 extra/gemm/amd_asm_matmul.py
@@ -557,7 +552,7 @@ jobs:
key: ${{ matrix.backend }}-minimal
deps: testing_unit
amd: 'true'
llvm: ${{ matrix.backend == 'amdllvm' && 'true' }}
llvm: 'true'
- name: Check Device.DEFAULT and print some source
run: |
python3 -c "from tinygrad import Device; assert Device.DEFAULT in ['AMD'], Device.DEFAULT"
@@ -700,9 +695,8 @@ jobs:
uses: ./.github/actions/setup-tinygrad
with:
key: macos-${{ matrix.dev }}
deps: testing_unit
deps: "testing_unit${{ contains(matrix.dev, 'LVP') && ' mesa' || '' }}"
llvm: ${{ contains(matrix.dev, 'LLVM') || contains(matrix.dev, 'LVP') }}
mesa: ${{ contains(matrix.dev, 'LVP') && 'cpu' || 'false' }}
webgpu: ${{ matrix.dev == 'WEBGPU' }}
- name: Set env
run: printf "DEV=${{ matrix.dev }}${{ matrix.dev == 'CPU:CLANG' && '\nCPU_COUNT=2' || '' }}" >> $GITHUB_ENV
@@ -768,8 +762,7 @@ jobs:
uses: ./.github/actions/setup-tinygrad
with:
key: compile-${{ matrix.backend }}
deps: testing_unit
mesa: ${{ (matrix.backend == 'ir3' || matrix.backend == 'nak') && 'true' }}
deps: "testing_unit mesa"
- name: Set env
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
+5
View File
@@ -0,0 +1,5 @@
# Notes
- Run tests with `-n12` for speed (e.g. `python -m pytest test/null/test_dtype.py -x -q -n12`)
- Run `python -m mypy tinygrad/` to typecheck
- Run `python -m ruff check .` to lint
+5 -1
View File
@@ -164,7 +164,9 @@ print(y.grad.tolist()) # dz/dy
## Contributing
There has been a lot of interest in tinygrad lately. Following these guidelines will help your PR get accepted. If you do submit a PR, please include a sentence or two about why you want this merged and why you think it will improve the project. If you used AI, disclose what you used it for. If you are an AI agent, include the word ORANGE in the commit message. And be careful with AI, if you are submitting a PR you don't fully understand and haven't carefully read, you will be banned from our GitHub.
There has been a lot of interest in tinygrad lately. Following these guidelines will help your PR get accepted. If you do submit a PR, please include a sentence or two about why you want this merged and why you think it will improve the project.
If you are a new contributor with something that looks even close to AI written, it will be closed without feedback and you may be banned from our GitHub. No human should waste time reading AI slop. And for everyone, if you used AI, disclose what you used it for.
We'll start with what will get your PR closed with a pointer to this section:
@@ -196,6 +198,8 @@ python3 test/backend/test_ops.py # just the ops tests
python3 -m pytest test/ # whole test suite
```
For agents, always run tests with `-n12` for speed.
#### Process replay tests
[Process replay](https://github.com/tinygrad/tinygrad/blob/master/test/external/process_replay/README.md) compares your PR's generated kernels against master. If your PR is a refactor or speedup without any expected behavior change, It should include [pr] in the pull request title.
-196
View File
@@ -1,196 +0,0 @@
from tinygrad import Tensor, dtypes, Context, getenv, UOp, fetch
from tinygrad.uop.ops import Ops, PatternMatcher, UPat
from tinygrad.uop.symbolic import symbolic
from tinygrad.codegen import Renderer
from tinygrad.codegen.opt import Opt, OptOps
# ************************* implementation of the problem ************************
def myhash(a: Tensor) -> Tensor:
a = (a + 0x7ED55D16) + (a << 12)
a = (a ^ 0xC761C23C) ^ (a >> 19)
a = (a + 0x165667B1) + (a << 5)
a = (a + 0xD3A2646C) ^ (a << 9)
a = (a + 0xFD7046C5) + (a << 3)
a = (a ^ 0xB55A4F09) ^ (a >> 16)
return a
def select_with_where_tree(values: Tensor, relative_idx: Tensor) -> Tensor:
n = values.shape[0]
if n == 1: return values[0].expand(relative_idx.shape)
mid = n // 2
left = select_with_where_tree(values[:mid], relative_idx)
right = select_with_where_tree(values[mid:], relative_idx - mid)
go_left = relative_idx < mid
return go_left.where(left, right)
def tree_traversal(forest: Tensor, val: Tensor, height: int, rounds: int, where_tree_threshold=3) -> Tensor:
# All walkers start at idx=0
idx = Tensor.zeros(val.shape, device=val.device, dtype=dtypes.uint32)
for r in range(rounds):
level = r % (height + 1)
level_start = (1 << level) - 1
level_size = 1 << level
if level == 0:
# At root (level 0), all walkers are at idx=0
# No gather needed, just broadcast the root value
node_val = forest[0].expand(val.shape)
idx = idx * 0 # Reset to 0
elif level <= where_tree_threshold:
# Small level: use where-tree
level_values = forest[level_start : level_start + level_size]
relative_idx = (idx - level_start)
node_val = select_with_where_tree(level_values, relative_idx)
else:
# Large level: use gather
node_val = forest.gather(0, idx)
val = myhash(val ^ node_val)
idx = (idx << 1) + (1 + (val & 1))
# No wrap check needed! At round 10 (level becomes 0), we reset idx above.
return val.contiguous(arg=(Opt(OptOps.UPCAST, 0, 8),))
# ************************* renderer for VLIW machine *************************
def loop_unrolling(sink:UOp):
rng = [x for x in sink.toposort() if x.op is Ops.RANGE]
if len(rng) == 0: return None
print(f"unrolling loop with size {rng[0].vmax+1}")
unrolled_sinks = [sink.substitute({rng[0]:rng[0].const_like(i)}).src[0] for i in range(rng[0].vmax+1)]
return UOp.sink(*unrolled_sinks, arg=sink.arg)
global_addrs = []
vliw_prepare = PatternMatcher([
# loop unrolling (should be a part of tinygrad)
(UPat(Ops.SINK, name="sink"), loop_unrolling),
# cast is fake
(UPat(Ops.CAST, name="c"), lambda c: c.src[0]),
# rewrites to hardcode the addresses in memory
(UPat(Ops.PARAM, name="dg"), lambda dg: UOp.const(dtypes.uint, global_addrs[dg.arg])),
# INDEX is just plus
(UPat(Ops.INDEX, name="i"), lambda i: i.src[0]+i.src[1]),
])+symbolic
class VLIWRenderer(Renderer):
has_local = False # TODO: this should be the default / cleaned up
# this says this backend supports MULACC + more. decompositions uses this
code_for_op: dict = {Ops.MULACC: None, Ops.ADD: "+", Ops.MUL: "*",
Ops.XOR: "^", Ops.AND: "&", Ops.OR: "|",
Ops.SHL: "<<", Ops.SHR: ">>", Ops.CMPLT: "<"}
# this matcher runs while still in graph form
pre_matcher = vliw_prepare
def render(self, uops:list[UOp]):
# TODO: this is a minimal renderer. for low cycle count, make it good
# to get speed, you need to add VLIW packing
# to get under 1536 regs, you need to add a register allocator
# we left the fun parts to you
print(f"rendering with {len(uops)} uops")
reg, inst = 0, []
r: dict[UOp, int] = {}
for u in uops:
assert u.dtype.count in (1,8), "dtype count must be 1 or 8"
# dumb register allocator
if u.op not in {Ops.STORE, Ops.SINK, Ops.GEP}:
r[u] = reg
reg += u.dtype.count
# render UOps to instructions
match u.op:
case Ops.SINK:
inst.append({"flow": [("halt",)]})
case Ops.CONST:
inst.append({"load": [("const", r[u], u.arg)]})
case Ops.GEP:
# a GEP is just an alias to a special register in the vector
r[u] = r[u.src[0]] + u.arg[0]
case Ops.STACK:
if all(s == u.src[0] for s in u.src):
# if all sources are the same, we can broadcast
inst.append({"valu": [("vbroadcast", r[u], r[u.src[0]])]})
else:
# this is a copy into a contiguous chunk of registers
inst.extend({"flow": [("add_imm", r[u]+i, r[s], 0)]} for i,s in enumerate(u.src) if r[s] != r[u]+i)
case Ops.LOAD:
op = "vload" if u.dtype.count > 1 else "load"
inst.append({"load": [(op, r[u], r[u.src[0]])]})
case Ops.STORE:
op = "vstore" if u.src[1].dtype.count > 1 else "store"
inst.append({"store": [(op, r[u.src[0]], r[u.src[1]])]})
case Ops.MULACC:
assert u.dtype.count == 8
inst.append({"valu": [("multiply_add", r[u], r[u.src[0]], r[u.src[1]], r[u.src[2]])]})
case Ops.WHERE:
assert u.dtype.count == 8
inst.append({"flow": [("vselect", r[u], r[u.src[0]], r[u.src[1]], r[u.src[2]])]})
case _ if u.op in self.code_for_op:
cat = "valu" if u.dtype.count > 1 else "alu"
inst.append({cat: [(self.code_for_op[u.op], r[u], r[u.src[0]], r[u.src[1]])]})
case _:
raise NotImplementedError(f"unhandled op {u.op}")
return repr(inst)
# ************************* test and render *************************
import sys, types
PROBLEM_URL = "https://raw.githubusercontent.com/anthropics/original_performance_takehome/refs/heads/main/tests/frozen_problem.py"
sys.modules["problem"] = problem = types.ModuleType("problem")
exec(fetch(PROBLEM_URL).read_text(), problem.__dict__)
if __name__ == "__main__":
batch_size = getenv("BS", 256)
height = 10
rounds = getenv("ROUNDS", 16)
# build problem
tree = problem.Tree.generate(height)
inp = problem.Input.generate(tree, batch_size, rounds)
mem = problem.build_mem_image(tree, inp)
global_addrs.extend([mem[6], mem[6], mem[4]]) # output, input, forest
# *** verify the kernel in tinygrad compared to reference ***
forest_t = Tensor(tree.values, dtype=dtypes.uint32)
val_t = Tensor(inp.values, dtype=dtypes.uint32)
if getenv("VERIFY", 1):
# verify on normal tinygrad device
with Context(PCONTIG=2):
out = tree_traversal(forest_t, val_t, height, rounds)
val_out = out.tolist()
problem.reference_kernel(tree, inp)
assert val_out == inp.values
print("verification passed")
# *** render to device ***
from tinygrad.codegen import to_program
with Context(PCONTIG=2, SPEC=0):
out = tree_traversal(forest_t, val_t, height, rounds)
sink = out.schedule_linear().src[-1].src[0]
prg = to_program(sink, VLIWRenderer())
# *** run on Machine and compare ***
# NOTE: the scratch size needs to be reduced to 1536 when you have a register allocator
src = eval(prg.src[2].arg)
max_regs = max(t[1] for instr in src for v in instr.values() for t in v if len(t) > 1) + 8
print(f"{max_regs:5d} regs used" + ("" if max_regs <= 1536 else " <-- WARNING: TOO MANY REGISTERS, MUST BE <= 1536"))
machine = problem.Machine(mem, src, problem.DebugInfo(scratch_map={}), n_cores=1, trace=False, scratch_size=max_regs)
machine.run()
print(f"ran for {machine.cycle:5d} cycles" + ("" if machine.cycle <= 1363 else " <-- EVEN CLAUDE GOT 1363"))
# compare to reference
ref_mem = mem.copy()
for _ in problem.reference_kernel2(ref_mem, {}): pass
assert machine.mem[mem[6]:mem[6]+mem[2]] == ref_mem[mem[6]:mem[6]+mem[2]]
print("compare passed!")
+13 -17
View File
@@ -152,24 +152,19 @@ def train_cifar():
# ========== Model ==========
def whitening(X, kernel_size=hyp['net']['kernel_size']):
def _cov(X):
return (X.T @ X) / (X.shape[0] - 1)
def _patches(data, patch_size=(kernel_size,kernel_size)):
def _patches(data:Tensor, patch_size=(kernel_size,kernel_size)):
h, w = patch_size
c = data.shape[1]
axis = (2, 3)
return np.lib.stride_tricks.sliding_window_view(data, window_shape=(h,w), axis=axis).transpose((0,3,2,1,4,5)).reshape((-1,c,h,w))
_, c, _, _ = data.shape
return data._pool((h, w)).permute(1, 4, 5, 0, 3, 2).reshape(c*h*w, -1)
def _eigens(patches):
n,c,h,w = patches.shape
Σ = _cov(patches.reshape(n, c*h*w))
Λ, V = np.linalg.eigh(Σ, UPLO='U')
return np.flip(Λ, 0), np.flip(V.T.reshape(c*h*w, c, h, w), 0)
cov = ((patches @ patches.T) / (patches.shape[1] - 1)).numpy()
eigvals, eigvecs = np.linalg.eigh(cov, UPLO='U')
return np.flip(eigvals, 0), np.flip(eigvecs.T.reshape(patches.shape[0], X.shape[1], kernel_size, kernel_size), 0)
# NOTE: np.linalg.eigh only supports float32 so the whitening layer weights need to be converted to float16 manually
Λ, V = _eigens(_patches(X.float().numpy()))
W = V/np.sqrt(Λ+1e-2)[:,None,None,None]
eigvals, eigvecs = _eigens(_patches(X.float()))
W = eigvecs/np.sqrt(eigvals+1e-2)[:,None,None,None]
return Tensor(W.astype(np.float32)).cast(dtypes.default_float).is_param_(False)
@@ -223,7 +218,7 @@ def train_cifar():
@TinyJit
def augmentations(X:Tensor, Y:Tensor):
perms = Tensor.randperm(X.shape[0], device=X.device) # We reuse perms for cutmix, because they are expensivne to generate
perms = Tensor.randperm(X.shape[0], device=X.device) # We reuse perms for cutmix, because they are expensive to generate
if getenv("RANDOM_CROP", 1):
X = random_crop(X, crop_size=32)
if getenv("RANDOM_FLIP", 1):
@@ -314,6 +309,9 @@ def train_cifar():
opt_bias = optim.SGD(params_bias, lr=0.01, momentum=hyp['opt']['momentum'], nesterov=True, weight_decay=hyp['opt']['bias_decay'])
opt_non_bias = optim.SGD(params_non_bias, lr=0.01, momentum=hyp['opt']['momentum'], nesterov=True, weight_decay=hyp['opt']['non_bias_decay'])
# realize model params and optimizer state before JIT to avoid cache misses
Tensor.realize(*params_dict.values(), *opt_bias.b, *opt_non_bias.b)
# NOTE taken from the hlb_CIFAR repository, might need to be tuned
initial_div_factor = hyp['opt']['initial_div_factor']
final_lr_ratio = hyp['opt']['final_lr_ratio']
@@ -330,9 +328,7 @@ def train_cifar():
# index 0 for bias and 1 for non-bias
optimizer.zero_grad()
loss.backward()
optimizer.step()
lr_scheduler[0].step()
lr_scheduler[1].step()
return loss.realize(*optimizer.schedule_step(), *lr_scheduler[0].schedule_step(), *lr_scheduler[1].schedule_step())
return loss.realize()
train_step_jitted = TinyJit(train_step)
+271 -1
View File
@@ -1658,6 +1658,276 @@ def train_llama3():
if MLLOGGER and RUNMLPERF:
MLLOGGER.start(key=mllog_constants.BLOCK_START, metadata={mllog_constants.SAMPLES_COUNT: sequences_seen})
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
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
SEED = config["SEED"] = getenv("SEED", 5760)
DATA_SEED = config["DATA_SEED"] = getenv("DATA_SEED", SEED)
SEQLEN = config["SEQLEN"] = getenv("SEQLEN", 8192)
TRAIN_ON_VAL = config["TRAIN_ON_VAL"] = getenv("TRAIN_ON_VAL", 0)
MAX_STEPS = config["MAX_STEPS"] = getenv("MAX_STEPS", 1_200_000)
SAMPLES = config["SAMPLES"] = getenv("SAMPLES", 5_760 if TRAIN_ON_VAL else MAX_STEPS * GBS)
EVAL_SAMPLES = config["EVAL_SAMPLES"] = getenv("EVAL_SAMPLES", 1024)
WARMUP_STEPS = config["WARMUP_STEPS"] = getenv("WARMUP_STEPS", 128)
LR = config["LR"] = getenv("LR", 4e-4 * GBS / 16)
END_LR = config["END_LR"] = getenv("END_LR", 4e-5)
EVAL_FREQ = config["EVAL_FREQ"] = getenv("EVAL_FREQ", 12288)
EVAL_BS = config["EVAL_BS"] = getenv("EVAL_BS", 16)
EVAL_TARGET = config["EVAL_TARGET"] = getenv("EVAL_TARGET", 3.34)
opt_adamw_beta_1 = 0.9
opt_adamw_beta_2 = 0.95
opt_adamw_epsilon = 1e-5
opt_adamw_weight_decay = 0.1
opt_learning_rate_warmup_steps = WARMUP_STEPS
opt_learning_rate_decay_steps = MAX_STEPS - opt_learning_rate_warmup_steps
opt_base_learning_rate = LR
opt_end_learning_rate = END_LR
Tensor.manual_seed(SEED) # seed for weight initialization
# ** init wandb **
WANDB = getenv("WANDB")
if WANDB:
import wandb
wandb_args = {"id": wandb_id, "resume": "must"} if (wandb_id := getenv("WANDB_RESUME", "")) else {}
wandb.init(config=config, **wandb_args, project="MLPerf-gpt-oss")
model_params = GPT_OSS_20B
model_params['vocab_size'] = 128256
real_vocab_size = model_params['vocab_size']
if (layers:=getenv("LAYERS")) != 0: model_params['n_layers'] = layers
print(f"model parameters: {model_params}")
model = GPTOSS(**model_params, max_context=SEQLEN)
params = get_parameters(model)
if getenv("EMPTYWEIGHT"):
for v in get_parameters(model):
v = v.assign(Tensor.empty(v.shape, dtype=v.dtype))
is_dp = (DP := getenv("DP", 1)) > 1
is_sharding = is_dp
device_count = DP
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(device_count))
model.shard(device, False)
is_offload_optim = bool(getenv("OFFLOAD_OPTIM"))
is_fake_offload = Device.DEFAULT == "NULL"
optim_device = ("CPU" if not is_fake_offload else "NULL:99") if is_offload_optim else None
optim = GradAccClipAdamW(params, 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)
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()
grads = [p.grad for p in optim.params]
from extra.gemm.cdna_asm_gemm import _mx_block_scale
model_state = get_state_dict(model)
fp8_scale_names = {n: f"{n}_scale" for n, t in model_state.items() if t.dtype == FP8_DTYPE}
fp8_inv_scales = [model_state[sname] for sname in fp8_scale_names.values()]
for wname, sname in fp8_scale_names.items():
w, scale = model_state[wname], model_state[sname]
w._inv_scale = scale
if optim.master_params:
master = optim.master_params[next(j for j, p in enumerate(optim.params) if p is w)]
inv = scale if scale.device == master.device else scale.to(master.device)
bs = _mx_block_scale(inv.reshape(-1, inv.shape[-1])).reshape(w.shape)
master.assign((master * bs).contiguous())
scheduler = CosineAnnealingLRWithWarmup(optim, opt_base_learning_rate, opt_end_learning_rate, opt_learning_rate_warmup_steps, opt_learning_rate_decay_steps)
# realize everything here
if optim.master_params: Tensor.realize(*optim.master_params)
Tensor.realize(*optim.params, *fp8_inv_scales)
@TinyJit
def minibatch(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:])
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)
@TinyJit
def optim_step():
grad_norm = optim.fstep(grads)
scheduler.step()
for g in grads: g.assign(0)
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)
return lr_cpu, grad_norm_cpu
@TinyJit
@Context(TRAINING=0)
def eval_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])
loss = logits.sparse_categorical_crossentropy(tokens[:, 1:])
return loss.flatten().float().to("CPU")
# ** data iters **
def fake_data(bs, samples):
import numpy as np
for _ in range(samples // bs):
fake_data_np = np.random.randint(0, real_vocab_size, size=(bs, SEQLEN + 1), dtype=np.int32)
yield Tensor(fake_data_np, device="NPY")
def get_train_iter():
if getenv("FAKEDATA", 0):
return fake_data(BS, SAMPLES)
else:
from examples.mlperf.dataloader import batch_load_llama3
return batch_load_llama3(BS, SAMPLES, SEQLEN, BASEDIR, seed=DATA_SEED, val=bool(TRAIN_ON_VAL), small=True)
if getenv("FAKEDATA", 0):
eval_dataset = None
else:
from examples.mlperf.dataloader import get_llama3_dataset
eval_dataset = get_llama3_dataset(EVAL_SAMPLES, SEQLEN, BASEDIR, val=True, small=True)
def get_eval_iter():
if eval_dataset is None:
return fake_data(EVAL_BS, EVAL_SAMPLES)
from examples.mlperf.dataloader import iterate_llama3_dataset
return iterate_llama3_dataset(eval_dataset, EVAL_BS)
num_params = sum(p.numel() for p in params) - model_params["vocab_size"]*model_params["dim"]
train_iter = get_train_iter()
i, sequences_seen = 0, 0
step_times = []
while i < MAX_STEPS:
GlobalCounters.reset()
actual_gbs = GBS if i >= 2 else BS
if getenv("TRAIN", 1):
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
gt = time.perf_counter()
ret = optim_step()
lr, grad_norm = ret[0].item(), ret[1].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
sequences_seen += actual_gbs
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
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")
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())))
if WANDB:
wandb.log({
"train/loss": loss,
"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,
"train/GFLOPS": gflops,
"train/MFU": mfu,
"train/sequences_seen": sequences_seen
})
if (ckpt_freq := getenv("CKPT")) and (i % ckpt_freq == 0 and (i != 1 or ckpt_freq == 1)):
tqdm.write("saving checkpoint")
if not os.path.exists(ckpt_dir := "./ckpts"): os.mkdir(ckpt_dir)
fn = f"{ckpt_dir}/gptoss_{i}.safe"
safe_save(get_state_dict(model), fn)
tqdm.write("saving optim checkpoint")
fn = f"{ckpt_dir}/gptoss_{i}_optim.safe"
safe_save(get_state_dict(scheduler), fn)
if i == BENCHMARK:
median_step_time = sorted(step_times)[BENCHMARK // 2]
estimated_steps = MAX_STEPS
estimated_total_minutes = int(median_step_time * estimated_steps / 60)
print(f"Estimated training time: {estimated_total_minutes // 60}h{estimated_total_minutes % 60}m")
print(f"epoch global_ops: {GlobalCounters.global_ops:_}, "
f"epoch global_mem: {GlobalCounters.global_mem:_}")
if (sequences_seen // EVAL_FREQ != (sequences_seen - actual_gbs) // EVAL_FREQ and (i != 1 or EVAL_FREQ == 1)) or (BENCHMARK and i == BENCHMARK):
if EVAL_BS == 0: return
tqdm.write(f"evaluating after {sequences_seen} sequences")
profile_marker(f"eval @ {i}")
# run eval
eval_losses = []
eval_iter = get_eval_iter()
tqdm.write(f"evaluating {EVAL_SAMPLES//EVAL_BS} batches of {EVAL_BS} sequences")
for j,tokens in tqdm(enumerate(eval_iter), total=EVAL_SAMPLES//EVAL_BS):
eval_losses += eval_step(tokens).tolist()
if BENCHMARK and (j+1) == min(BENCHMARK, EVAL_SAMPLES//EVAL_BS):
return
log_perplexity = sum(eval_losses) / len(eval_losses)
tqdm.write(f"eval log perplexity: {log_perplexity:.4f}")
if WANDB:
wandb.log({"eval/log_perplexity": log_perplexity, "eval/sequences_seen": sequences_seen})
if log_perplexity < EVAL_TARGET:
tqdm.write(f"target achieved after {sequences_seen} sequences")
if getenv("CKPT"):
if not os.path.exists(ckpt_dir := "./ckpts"): os.mkdir(ckpt_dir)
fn = f"{ckpt_dir}/gptoss.safe"
safe_save(get_state_dict(model), fn)
break
def train_stable_diffusion():
from extra.models.unet import UNetModel
from examples.mlperf.dataloader import batch_load_train_stable_diffusion
@@ -1736,7 +2006,7 @@ def train_stable_diffusion():
# move to CPU first so more GPU bufs aren't created (can trigger OOM)
for k,v in ckpt.items(): ckpt[k] = v.detach().to("CPU")
Tensor.realize(*[v for v in ckpt.values()])
for k,v in ckpt.items(): ckpt[k] = v.cast(v.dtype.base).contiguous()
for k,v in ckpt.items(): ckpt[k] = v.cast(v.dtype).contiguous()
Tensor.realize(*[v for v in ckpt.values()])
return ckpt
+275
View File
@@ -0,0 +1,275 @@
import math, os, functools
if __name__ == "__main__":
os.environ["DEFAULT_FLOAT"] = "bfloat16"
os.environ["OPTIM_DTYPE"] = "bfloat16"
if "DEV" not in os.environ: os.environ["DEV"] = "NULL::gfx950"
# CDNA
os.environ["DEVICE_IN_FUNCTION_BUG"] = "1"
os.environ["ALL2ALL"] = "1"
os.environ["USE_ATOMICS"] = "1"
from tinygrad import Tensor, nn, function, getenv, dtypes, TinyJit
from tinygrad.helpers import Timing, colored, GlobalCounters, profile_marker
from tinygrad.uop.ops import Ops, UOp
from extra.models.llama import apply_rotary_emb, precompute_freqs_cis
from extra.llama_kernels.rmsnorm import rmsnorm
from extra.gemm.cdna_asm_gemm import _mx_block_scale, quantize_mxfp8
FP8_DTYPE = dtypes.fp8e4m3
FP8_MAX = 448.0
INIT_STD = 0.008
def _quant_dequant_fwd(x:Tensor) -> Tensor:
# x (2d bf16) -> bf16 value after an mxfp8 round-trip (1x32 block scaling on the last axis)
M, K = x.shape
scale_K = K // 32
amax = x.float().reshape(M, scale_K, 32).abs().max(axis=-1)
e8 = (amax.maximum(1e-38).log2().floor() + 127).clamp(0, 254).cast(dtypes.uint8)
qscale = (127.0 - e8.cast(dtypes.float32)).exp2().reshape(M, scale_K, 1).expand(M, scale_K, 32).reshape(M, K)
x_fp8 = (x.float() * qscale).clamp(-FP8_MAX, FP8_MAX).cast(FP8_DTYPE).cast(dtypes.float32)
return (x_fp8 * _mx_block_scale(e8)).cast(dtypes.bfloat16)
@functools.cache
def _quant_dequant_fwd_fxn(x_p, device):
return _quant_dequant_fwd(Tensor(x_p, device=device))
def _quant_dequant_bwd(grad:UOp, call:UOp) -> tuple:
return (Tensor(grad).cast(dtypes.bfloat16).uop,)
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))
def _dequant_fwd(w_q:Tensor, w_scale:Tensor) -> Tensor:
return w_q.cast(dtypes.bfloat16) * _mx_block_scale(w_scale)
@functools.cache
def _dequant_fwd_fxn(wq_p, ws_p, device):
return _dequant_fwd(Tensor(wq_p, device=device), Tensor(ws_p, device=device))
def _dequant_bwd(grad:UOp, call:UOp) -> tuple:
w_scale = Tensor(call.src[2])
return ((Tensor(grad).cast(dtypes.bfloat16) * _mx_block_scale(w_scale).cast(dtypes.bfloat16)).uop, None)
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))
def matmul_mx(x:Tensor, w_q:Tensor, w_scale:Tensor) -> Tensor:
l_shape = x.shape[:-1]
x_phys = quant_dequant_mx(x.reshape(-1, x.shape[-1])).reshape(*l_shape, x.shape[-1])
w_phys = dequant_weight(w_q, w_scale)
return (x_phys @ w_phys.T).cast(dtypes.bfloat16)
def swiglu(x:Tensor, limit:float=7.0, alpha:float=1.702) -> Tensor:
x_glu, x_linear = x[..., ::2], x[..., 1::2]
x_glu = x_glu.clamp(max_=limit)
x_linear = x_linear.clamp(-limit, limit)
return (x_glu * (alpha * x_glu).sigmoid()) * (x_linear + 1)
class GPTOSS:
def __init__(self, dim:int, n_layers:int, n_heads:int, n_kv_heads:int, head_dim:int, n_experts:int, experts_per_tok:int,
intermediate_size:int, vocab_size:int, norm_eps:float=1e-5, rope_theta:int=150000, sliding_window:int=128,
swiglu_limit:float=7.0, max_context:int=8192):
self.dim, self.n_layers, self.n_heads, self.n_kv_heads, self.head_dim = dim, n_layers, n_heads, n_kv_heads, head_dim
self.n_rep = n_heads // n_kv_heads
self.n_experts, self.experts_per_tok, self.intermediate_size = n_experts, experts_per_tok, intermediate_size
self.vocab_size, self.norm_eps, self.sliding_window, self.swiglu_limit = vocab_size, norm_eps, sliding_window, swiglu_limit
self.sm_scale = 1.0 / math.sqrt(head_dim)
scaled_std = INIT_STD / math.sqrt(2 * n_layers)
q_dim, qkv_dim = n_heads * head_dim, head_dim * (n_heads + 2 * n_kv_heads)
# attn
self.wqkv, self.wqkv_scale = self._quant_weight(n_layers, qkv_dim, dim)
self.wqkv_bias = Tensor.zeros(n_layers, qkv_dim, dtype=dtypes.bfloat16).contiguous()
self.wo, self.wo_scale = self._quant_weight(n_layers, dim, q_dim, std=scaled_std)
self.wo_bias = Tensor.zeros(n_layers, dim, dtype=dtypes.bfloat16).contiguous()
self.sinks = Tensor.zeros(n_layers, n_heads, dtype=dtypes.bfloat16).contiguous()
self.attention_norm = Tensor.ones(n_layers, dim).contiguous()
# moe ffn
self.ffn_norm = Tensor.ones(n_layers, dim).contiguous()
self.gate = Tensor.normal(n_layers, n_experts, dim, mean=0.0, std=INIT_STD, dtype=dtypes.bfloat16)
self.gate_bias = Tensor.zeros(n_layers, n_experts, dtype=dtypes.bfloat16).contiguous()
self.w_gate_up, self.w_gate_up_scale = self._quant_weight(n_layers, n_experts, intermediate_size * 2, dim)
self.w_gate_up_bias = Tensor.zeros(n_layers, n_experts, intermediate_size * 2, dtype=dtypes.bfloat16).contiguous()
self.w_down, self.w_down_scale = self._quant_weight(n_layers, n_experts, dim, intermediate_size, std=scaled_std)
self.w_down_bias = Tensor.zeros(n_layers, n_experts, dim, dtype=dtypes.bfloat16).contiguous()
# output
self.norm = nn.RMSNorm(dim, norm_eps)
self.tok_embeddings = nn.Embedding(vocab_size, dim)
self.tok_embeddings.weight = Tensor.normal(vocab_size, dim, mean=0.0, std=INIT_STD, dtype=dtypes.bfloat16)
self.output = Tensor.normal(vocab_size, dim, mean=0.0, std=INIT_STD, dtype=dtypes.bfloat16)
self.freqs_cis = precompute_freqs_cis(head_dim, max_context * 2, rope_theta).contiguous().is_param_(False)
def _quant_weight(self, *shape:int, std:float=INIT_STD):
w = Tensor.zeros(*shape) if getenv("ZEROS") else Tensor.normal(*shape, mean=0.0, std=std)
w_q, w_e8, _ = quantize_mxfp8(w)
return w_q, w_e8.is_param_(False)
def _attn_mask(self, seqlen:int, sliding:bool, dtype) -> Tensor:
i, j = Tensor.arange(seqlen).reshape(seqlen, 1), Tensor.arange(seqlen).reshape(1, seqlen)
allowed = j <= i
if sliding: allowed = allowed & (i - j < self.sliding_window)
return allowed.where(0.0, -1e30).cast(dtype).contiguous()
def attention(self, x:Tensor, freqs_cis:Tensor, mask:Tensor, *, 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
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 = xq.cast(dtypes.bfloat16).reshape(bsz, seqlen, self.n_kv_heads, self.n_rep, self.head_dim).permute(0, 2, 3, 1, 4)
xk = xk.cast(dtypes.bfloat16).permute(0, 2, 1, 3).unsqueeze(2)
xv = xv.cast(dtypes.bfloat16).permute(0, 2, 1, 3).unsqueeze(2)
scores = (xq @ xk.transpose(-2, -1)).float() * self.sm_scale + mask
sink = sinks.reshape(1, self.n_kv_heads, self.n_rep, 1, 1).float()
m = scores.max(-1, keepdim=True).maximum(sink)
e = (scores - m).exp()
w = (e / (e.sum(-1, keepdim=True) + (sink - m).exp())).cast(dtypes.bfloat16)
attn = (w @ xv).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]
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()
thresh = logits.topk(self.experts_per_tok)[0][..., -1:]
weights = (logits >= thresh).where(logits, -float("inf")).softmax(-1)
out = None
for e in range(self.n_experts):
gate_up = matmul_mx(inp, w_gate_up[e], w_gate_up_scale[e]) + w_gate_up_bias[e]
y = (matmul_mx(swiglu(gate_up, self.swiglu_limit), w_down[e], w_down_scale[e]) + w_down_bias[e]).contiguous()
contrib = weights[..., e:e+1].cast(y.dtype) * y
out = contrib if out is None else out + contrib
return out, [x_normed, rrms]
@function(precompile=True, precompile_backward=True)
def run_layer(self, x:Tensor, freqs_cis:Tensor, mask:Tensor, attn_kwargs:dict, ffn_kwargs:dict, save:bool=True):
attn, attn_saves = self.attention(x, freqs_cis, mask, **attn_kwargs)
h = x + attn
ffn, ffn_saves = self.feed_forward(h, **ffn_kwargs)
h = h + ffn
if save: return (h, *attn_saves, *ffn_saves)
return (h,)
def shard(self, device:tuple[str, ...], mp:bool=False):
assert not mp, "MP not supported"
from tinygrad.nn.state import get_parameters
for v in get_parameters(self): v.shard_(device, axis=None)
Tensor.realize(*get_parameters(self))
def __call__(self, tokens:Tensor, save:bool=True):
h = self.tok_embeddings(tokens)
bsz, seqlen = tokens.shape
freqs_cis = self.freqs_cis.cast(h.dtype)[:, :seqlen, :, :, :]
mask_full = self._attn_mask(seqlen, False, dtypes.float32)
mask_sliding = self._attn_mask(seqlen, True, dtypes.float32)
for i in range(self.n_layers):
attn_kwargs = dict(attention_norm=self.attention_norm[i], wqkv=self.wqkv[i], wqkv_scale=self.wqkv_scale[i],
wqkv_bias=self.wqkv_bias[i], wo=self.wo[i], wo_scale=self.wo_scale[i], wo_bias=self.wo_bias[i],
sinks=self.sinks[i])
ffn_kwargs = dict(ffn_norm=self.ffn_norm[i], gate=self.gate[i], gate_bias=self.gate_bias[i],
w_gate_up=self.w_gate_up[i], w_gate_up_scale=self.w_gate_up_scale[i], w_gate_up_bias=self.w_gate_up_bias[i],
w_down=self.w_down[i], w_down_scale=self.w_down_scale[i], w_down_bias=self.w_down_bias[i])
mask = mask_sliding if i % 2 == 0 else mask_full
h, *_ = self.run_layer(h, freqs_cis, mask, attn_kwargs, ffn_kwargs, save=save)
logits = self.norm(h) @ self.output.T
return logits
def _get_pads(uop:UOp) -> list[UOp]:
if uop.op == Ops.ADD: return _get_pads(uop.src[0]) + _get_pads(uop.src[1])
return [uop]
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))
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)])
buf_slice = cur.shrink(grad_shrink)
cur = cur.after(buf_slice.store(buf_slice + pad.src[0].cast(cur.dtype)))
else:
cur = cur.after(cur.store(cur + pad.cast(cur.dtype)))
grad_buf.uop = cur
GPT_OSS_20B = dict(dim=2880, n_layers=24, n_heads=64, n_kv_heads=8, head_dim=64, n_experts=32, experts_per_tok=4,
intermediate_size=2880, vocab_size=128256, norm_eps=1e-5, rope_theta=150000, sliding_window=128,
swiglu_limit=7.0)
if __name__ == "__main__":
config = {}
BS = config["BS"] = getenv("BS", 16)
SEQLEN = config["SEQLEN"] = getenv("SEQLEN", 8192)
model_params = GPT_OSS_20B
real_vocab_size = model_params["vocab_size"]
if (layers := getenv("LAYERS")) != 0: model_params["n_layers"] = layers
model = GPTOSS(**model_params, max_context=SEQLEN)
state = nn.state.get_state_dict(model)
print("tensor count:", len(state))
from tinygrad import Device
is_dp = (DP := getenv("DP", 1)) > 1
device_count = DP
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(device_count))
if is_dp: model.shard(device)
# preallocate all the grad buffers and zero them out
grad_dtype = lambda x: dtypes.bfloat16 if x.dtype in dtypes.fp8s else x.dtype
grads = {x:x.zeros_like(dtype=grad_dtype(x)).contiguous() for x in state.values() if x.is_param}
# print model size
sz = 0
for k,v in state.items():
print(f"{colored(k, 'green' if v in grads else 'white'):30s} {str(v.shape):30s} {str(v.dtype):20s} {v.device} {v.nbytes()/1e9:.2f} GB")
sz += v.nbytes()
print(f"total sz: {sz/1e9:.2f} GB")
with Timing("fake data: "): tokens = Tensor.randint(BS, SEQLEN+1, low=0, high=real_vocab_size, dtype=dtypes.int)
with Timing("realize weights/grads/data: "): Tensor.realize(*state.values(), *grads.values(), tokens)
print("mem per device: " + ', '.join(f"{dev}: {mem/1e9:.2f} GB" for dev, mem in sorted(GlobalCounters.mem_used_per_device.items())))
if is_dp: tokens = tokens.shard(device, axis=0)
@TinyJit
def fwd_bwd(tokens:Tensor):
with Timing("python forward: "):
logits = model(tokens[:, :-1], save=True)
loss = logits.sparse_categorical_crossentropy(tokens[:, 1:])
with Timing("python backward: "):
for t,g in zip(grads, loss.gradient(*grads)):
apply_grad(grads[t], g.uop)
with Timing("run fwd_bwd: "): loss.realize(*grads.values())
@TinyJit
def optim_step():
for g in grads.values(): g.assign(g.zeros_like())
Tensor.realize(*grads.values())
for i in range(6):
GlobalCounters.reset()
profile_marker(f"step {i}")
with Timing(colored(f"*** step {i}: ", "red")):
fwd_bwd(tokens)
optim_step()
print("mem per device: " + ', '.join(f"{dev}: {mem/1e9:.2f} GB" for dev, mem in sorted(GlobalCounters.mem_used_per_device.items())))
+15 -3
View File
@@ -6,6 +6,7 @@ from tinygrad.uop.ops import UOp, Ops
STOCHASTIC_ROUND = getenv("STOCHASTIC_ROUND", 0)
MASTER_WEIGHTS = getenv("MASTER_WEIGHTS", 0)
ZERO_OPTIM = getenv("ZERO_OPTIM", 0)
FP8_AMAX_MARGIN = getenv("FP8_AMAX_MARGIN", 1.1)
IMMEDIATE_SCALE = getenv("IMMEDIATE_SCALE", 0)
MXFP8 = getenv("MXFP8", 0)
@@ -25,14 +26,24 @@ class GradAccClipAdamW(Optimizer):
super().__init__(params, lr, device, fused)
self.b1, self.b2, self.eps, self.wd = b1, b2, eps, weight_decay
self.b1_t, self.b2_t = (Tensor.ones((1,), dtype=dtypes.float32, device=self.device) for _ in [b1, b2])
self.m = self._new_optim_param()
self.v = self._new_optim_param()
self.zero = bool(ZERO_OPTIM) and isinstance(self.device, tuple) and not self.fused
self.m = [self._zero_shard(x) for x in self._new_optim_param()]
self.v = [self._zero_shard(x) for x in self._new_optim_param()]
self.grad_acc, self.clip_norm = grad_acc, clip_norm
if MASTER_WEIGHTS and self.params[0].dtype != dtypes.float32:
self.master_params:list[Tensor]|None = [p.to(self.device).float().contiguous() for p in self.params]
self.master_params:list[Tensor]|None = [self._zero_shard(p.to(self.device).float().contiguous()) for p in self.params]
else:
self.master_params = None
def _zero_shard(self, t:Tensor) -> Tensor:
if not self.zero or (t.shape[0] % len(self.device)) != 0: return t
return Tensor(t.uop._shard(0, len(self.device)).multi(0)).clone()
def _zero_gather(self, t:Tensor) -> Tensor:
if not isinstance(t.device, tuple) or t.uop.axis != 0: return t
n, sz = len(t.device), t.shape[0] // len(t.device)
return Tensor.cat(*[t[p*sz:(p+1)*sz] for p in range(n)], dim=0)
def fstep(self, grads:list[Tensor]):
if self.fused:
out, extra = self._step([], grads)
@@ -85,6 +96,7 @@ class GradAccClipAdamW(Optimizer):
up = up.float().shard_like(w) + self.lr.to(w.device) * wd * w.detach()
new_w = w.detach() - up
if master is not None: master.assign(new_w)
if self.zero: new_w = self._zero_gather(new_w)
# when master is offloaded to a different device than the param, results are resharded back onto the param's (sharded) device
offloaded = master is not None and master.device != t.device
if STOCHASTIC_ROUND and t.dtype == dtypes.bfloat16:
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
export PYTHONPATH="."
export PATH="/opt/rocm-7.1.1/bin:$PATH"
export ROCM_PATH="/opt/rocm-7.1.1"
export DEV=${DEV:-AMD}
export CHECK_OOB=0
export REWRITE_STACK_LIMIT=5000000 HCQDEV_WAIT_TIMEOUT_MS=240000
export DEVICE_IN_FUNCTION_BUG=1
export DEBUG=${DEBUG:-2}
export HK_FLASH_ATTENTION=${HK_FLASH_ATTENTION:-1}
export ALL2ALL=${ALL2ALL:-1}
export LATE_ALLREDUCE=${LATE_ALLREDUCE:-0}
export ALLREDUCE_CAST=${ALLREDUCE_CAST:-1}
export USE_ATOMICS=${USE_ATOMICS:-1}
export MASTER_WEIGHTS=${MASTER_WEIGHTS:-1}
export MXFP8=${MXFP8:-1}
export ZERO_OPTIM=${ZERO_OPTIM:-1}
export OFFLOAD_OPTIM=${OFFLOAD_OPTIM:-0}
export DEFAULT_FLOAT="bfloat16" OPTIM_DTYPE="bfloat16"
export DP=${DP:-8} BS=${BS:-16} EVAL_BS=${EVAL_BS:-8} GRADIENT_ACC_STEPS=${GRADIENT_ACC_STEPS:-2}
export GBS=$((BS * GRADIENT_ACC_STEPS))
export MODEL="gptoss"
export BASEDIR="/raid/datasets/c4-8b/"
export EVAL_TARGET=3.34 EVAL_FREQ=12288
export END_LR="4e-5" WARMUP_STEPS=128 MAX_STEPS=1200000
export SAMPLES=$((MAX_STEPS * GBS))
export SEQLEN=${SEQLEN:-8192}
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 FAKEDATA=${FAKEDATA:-1} BENCHMARK=${BENCHMARK:-10}
if [ -z "$FULL_LAYERS" ]; then
export LAYERS=${LAYERS:-2}
fi
python3 examples/mlperf/model_train.py
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
export PYTHONPATH="."
export PATH="/opt/rocm-7.1.1/bin:$PATH"
export ROCM_PATH="/opt/rocm-7.1.1"
export DEV=${DEV:-AMD}
export CHECK_OOB=0
export REWRITE_STACK_LIMIT=5000000 HCQDEV_WAIT_TIMEOUT_MS=240000
export DEVICE_IN_FUNCTION_BUG=1
export DEBUG=${DEBUG:-0}
export HK_FLASH_ATTENTION=${HK_FLASH_ATTENTION:-1}
export ALL2ALL=${ALL2ALL:-1}
export LATE_ALLREDUCE=${LATE_ALLREDUCE:-0}
export ALLREDUCE_CAST=${ALLREDUCE_CAST:-1}
export USE_ATOMICS=${USE_ATOMICS:-1}
export MASTER_WEIGHTS=${MASTER_WEIGHTS:-1}
export MXFP8=${MXFP8:-1}
export ZERO_OPTIM=${ZERO_OPTIM:-1}
export OFFLOAD_OPTIM=${OFFLOAD_OPTIM:-0}
export DEFAULT_FLOAT="bfloat16" OPTIM_DTYPE="bfloat16"
export DP=${DP:-8} BS=${BS:-16} EVAL_BS=${EVAL_BS:-8} GRADIENT_ACC_STEPS=${GRADIENT_ACC_STEPS:-2}
export GBS=$((BS * GRADIENT_ACC_STEPS))
export MODEL="gptoss"
export BASEDIR="/raid/datasets/c4-8b/"
export EVAL_TARGET=3.34 EVAL_FREQ=12288
export END_LR="4e-5" WARMUP_STEPS=128 MAX_STEPS=1200000
export SAMPLES=$((MAX_STEPS * GBS))
export SEQLEN=${SEQLEN:-8192}
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
python3 examples/mlperf/model_train.py
@@ -14,7 +14,6 @@ export ALL2ALL=${ALL2ALL:-1}
export LATE_ALLREDUCE=${LATE_ALLREDUCE:-0}
export USE_ATOMICS=${USE_ATOMICS:-1}
export ASM_GEMM=${ASM_GEMM:-1}
export USE_HK_BF16_GEMM=${USE_HK_BF16_GEMM:-1}
export WQKV=${WQKV:-1}
export MASTER_WEIGHTS=${MASTER_WEIGHTS:-1}
export FP8=${FP8:-1}
@@ -14,7 +14,6 @@ export ALL2ALL=${ALL2ALL:-1}
export LATE_ALLREDUCE=${LATE_ALLREDUCE:-1}
export USE_ATOMICS=${USE_ATOMICS:-1}
export ASM_GEMM=${ASM_GEMM:-1}
export USE_HK_BF16_GEMM=${USE_HK_BF16_GEMM:-1}
export WQKV=${WQKV:-1}
export MASTER_WEIGHTS=${MASTER_WEIGHTS:-1}
export FP8=${FP8:-1}
@@ -14,7 +14,6 @@ export ALL2ALL=${ALL2ALL:-1}
export LATE_ALLREDUCE=${LATE_ALLREDUCE:-0}
export USE_ATOMICS=${USE_ATOMICS:-1}
export ASM_GEMM=${ASM_GEMM:-1}
export USE_HK_BF16_GEMM=${USE_HK_BF16_GEMM:-1}
export WQKV=${WQKV:-1}
export MASTER_WEIGHTS=${MASTER_WEIGHTS:-1}
export FP8=${FP8:-1}
@@ -14,7 +14,6 @@ export ALL2ALL=${ALL2ALL:-1}
export LATE_ALLREDUCE=${LATE_ALLREDUCE:-1}
export USE_ATOMICS=${USE_ATOMICS:-1}
export ASM_GEMM=${ASM_GEMM:-1}
export USE_HK_BF16_GEMM=${USE_HK_BF16_GEMM:-1}
export WQKV=${WQKV:-1}
export MASTER_WEIGHTS=${MASTER_WEIGHTS:-1}
export FP8=${FP8:-1}
+2 -2
View File
@@ -193,8 +193,8 @@ class SPPF:
self.cv1 = Conv_Block(c1, c_, 1, 1, padding=None)
self.cv2 = Conv_Block(c_ * 4, c2, 1, 1, padding=None)
# TODO: this pads with 0s, whereas torch function pads with -infinity. This results in a < 2% difference in prediction which does not make a difference visually.
self.maxpool = lambda x : x.pad((k // 2, k // 2, k // 2, k // 2)).max_pool2d(kernel_size=k, stride=1)
# Pad with -inf to match PyTorch's MaxPool2d behavior.
self.maxpool = lambda x : x.pad((k // 2, k // 2, k // 2, k // 2), value=float('-inf')).max_pool2d(kernel_size=k, stride=1)
def __call__(self, x):
x = self.cv1(x)
+3 -3
View File
@@ -1,14 +1,14 @@
#!/usr/bin/env python3
import time, mmap, sys, shutil, os, glob, subprocess, argparse, collections
from tinygrad.helpers import DEBUG, colored, ansilen
from tinygrad.helpers import DEBUG, NO_COLOR, colored, ansilen
from tinygrad.runtime.autogen import libc
from tinygrad.runtime.autogen.am import am
from tinygrad.runtime.support.hcq import MMIOInterface
from tinygrad.runtime.support.am.amdev import AMDev, AMMemoryManager, AMPageTableEntry
from tinygrad.runtime.support.am.ip import AM_SOC, AM_GMC, AM_IH, AM_PSP, AM_SMU, AM_GFX, AM_SDMA
def bold(s): return f"\033[1m{s}\033[0m"
def bold(s): return s if NO_COLOR else f"\033[1m{s}\033[0m"
def trim(s:str, length:int) -> str:
if len(s) > length: return s[:length-3] + "..."
@@ -276,7 +276,7 @@ class SMICtx:
return usage
def draw(self, once):
terminal_width, terminal_height = shutil.get_terminal_size()
terminal_width, terminal_height = shutil.get_terminal_size(fallback=(231, 24))
if not once and (self.prev_terminal_width != terminal_width or self.prev_terminal_height != terminal_height):
os.system('clear')
self.prev_terminal_width, self.prev_terminal_height = terminal_width, terminal_height
+1 -1
View File
@@ -24,7 +24,7 @@ def custom_matmul(output: UOp, inp: UOp, weight: UOp) -> UOp:
reduce_idx = UOp.range(IN, 0, AxisType.REDUCE)
product = (inp.index((seq_idx*IN+reduce_idx+batch_idx*IN*SEQ)) * weight.index((out_idx*IN+reduce_idx))).cast(dtypes.float)
reduced = product.reduce(reduce_idx, arg=Ops.ADD)
store_op = output.index((seq_idx*OUT+out_idx+batch_idx*OUT*SEQ), ptr=True).store(reduced).end(batch_idx, seq_idx, out_idx)
store_op = output.index((seq_idx*OUT+out_idx+batch_idx*OUT*SEQ)).store(reduced).end(batch_idx, seq_idx, out_idx)
return store_op.sink(arg=KernelInfo(name=f"fp8_matmul_{inp.shape}x{weight.shape}"))
def custom_matmul_backward(gradient: UOp, kernel: UOp) -> tuple[UOp, UOp]:
+4 -4
View File
@@ -1,5 +1,5 @@
from tinygrad import Device, UOp, getenv
from tinygrad.uop.ops import AxisType, KernelInfo, Ops
from tinygrad.uop.ops import AxisType, KernelInfo
from tinygrad.dtype import AddrSpace, dtypes
N = getenv("N", 4096)
@@ -46,8 +46,8 @@ def block_128x128_gemm(c:UOp, a:UOp, b:UOp) -> UOp:
# -- GLOBAL -> LOCAL --
# wmma: spatial outer, k inner (k contiguous for vectorized WMMA tile loads)
# gemm: k outer, spatial inner
A_local = UOp.placeholder((BLOCK_M, BLOCK_K) if use_wmma else (BLOCK_K, BLOCK_M), a.dtype.base, slot=0, addrspace=AddrSpace.LOCAL)
B_local = UOp.placeholder((BLOCK_N, BLOCK_K) if use_wmma else (BLOCK_K, BLOCK_N), b.dtype.base, slot=1, addrspace=AddrSpace.LOCAL)
A_local = UOp.placeholder((BLOCK_M, BLOCK_K) if use_wmma else (BLOCK_K, BLOCK_M), a.dtype, slot=0, addrspace=AddrSpace.LOCAL)
B_local = UOp.placeholder((BLOCK_N, BLOCK_K) if use_wmma else (BLOCK_K, BLOCK_N), b.dtype, slot=1, addrspace=AddrSpace.LOCAL)
a = a.reshape(K // BLOCK_K, BLOCK_K, BLOCK_M)
b = b.reshape(K // BLOCK_K, BLOCK_K, BLOCK_N)
@@ -80,7 +80,7 @@ def block_128x128_gemm(c:UOp, a:UOp, b:UOp) -> UOp:
# NOTE: since this is part of K, these 2 can be anywhere in the frags and long as a and b match
a_frag = a_frag.reshape(2, 8)[lane_m, :]
b_frag = b_frag.reshape(2, 8)[lane_m, :]
wmma = UOp(Ops.SHAPED_WMMA, dtypes.float, (a_frag, b_frag, acc_frag.after(k)), arg=((16, 16, 16), 'AMD', 32))
wmma = UOp.wmma(a_frag, b_frag, acc_frag.after(k), ((16, 16, 16), 'AMD', 32))
acc_store = acc_frag.store(wmma).end(tile_m, tile_n)
else:
# registers for LOCAL -> REG
+4 -6
View File
@@ -19,6 +19,7 @@ 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})))")
@@ -96,7 +97,7 @@ def amd_flash_attention(o:UOp, q:UOp, k:UOp, v:UOp) -> UOp:
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(Ops.SHAPED_WMMA, dtypes.float, (q_frag, k_frag, S_frag.after(k_qk)), arg=WMMA_ARG)
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)
@@ -126,10 +127,7 @@ def amd_flash_attention(o:UOp, q:UOp, k:UOp, v:UOp) -> UOp:
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)
# TODO: P_write[tid].store(S_reg.cast(dtypes.half)) -- shaped store fails due to RESHAPE(local BUFFER) surviving linearization
rw1 = UOp.range(TM, 296, AxisType.LOOP)
rw2 = UOp.range(TN, 297, AxisType.LOOP)
P_store = P_write[tid, rw1, rw2].store(S_reg[rw1, rw2].cast(dtypes.half)).end(rw1, rw2)
P_store = P_write[tid].store(S_reg.cast(dtypes.half))
# -- online softmax correction --
ri4 = UOp.range(TM, 330, AxisType.LOOP)
@@ -160,7 +158,7 @@ def amd_flash_attention(o:UOp, q:UOp, k:UOp, v:UOp) -> UOp:
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(Ops.SHAPED_WMMA, dtypes.float, (p_frag, v_frag, acc_frag.after(k_pv)), arg=WMMA_ARG)
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).barrier().end(n_tile)
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -20,8 +20,8 @@ def hand_spec_tc_cores():
gk = UOp.range(N // 8, 0, AxisType.REDUCE)
a_tc = UOp.vectorize(*[mat_idx(a, gx, gk, warp, i) for i in range(2)])
b_tc = UOp.vectorize(*[mat_idx(b, gk, gy, warp, i) for i in range(2)])
a_tc = UOp.stack(*[mat_idx(a, gx, gk, warp, i) for i in range(2)])
b_tc = UOp.stack(*[mat_idx(b, gk, gy, warp, i) for i in range(2)])
acc = UOp.placeholder((2,), dtypes.float, slot=0, addrspace=AddrSpace.REG)
acc = acc[0].set(0.0)
@@ -30,10 +30,10 @@ def hand_spec_tc_cores():
# TODO: make this simple
wmma_arg = ('WMMA_8_8_8_float_float', (8, 8, 8), dtypes.float, dtypes.float, 'METAL', 32, (((3, 2),), ((3, 2),), ((3, 2),)), ())
acc_load = UOp.vectorize(acc.after(gk)[0], acc.after(gk)[1])
acc_load = UOp.stack(acc.after(gk)[0], acc.after(gk)[1])
out = UOp(Ops.WMMA, dtypes.float.vec(2), (a_tc, b_tc, acc_load), arg=wmma_arg)
end_loop = UOp.group(*[acc[i].store(out.gep(i)) for i in range(2)]).end(gk)
end_loop = UOp.group(*[acc[i].store(out.index(i)) for i in range(2)]).end(gk)
sink = UOp.group(*[mat_idx(c.after(end_loop), gx, gy, warp, i).store(acc[i]) for i in range(2)])
return sink.sink(arg=KernelInfo(name="custom_metal_matmul", opts_to_apply=())).simplify()
+4 -4
View File
@@ -79,7 +79,7 @@ def custom_gemm(C:UOp, A:UOp, B:UOp) -> UOp:
# this is the big accumulator
acc = UOp.placeholder((BLOCK_N//TC_N, BLOCK_M//TC_M//WARPGROUP_SIZE), dtypes.float.vec(4), 0, AddrSpace.REG)
assert acc.size*WARP_SIZE*WARPGROUP_SIZE*4 == BLOCK_M*BLOCK_N
acc = acc[init_l:=UOp.range(acc.size, 500)].set(UOp.const(dtypes.float.vec(4), 0.0), end=init_l)
acc = acc[init_l:=UOp.range(acc.size, 500)].set(UOp.const(dtypes.float, (0.0,)*4), end=init_l)
# create locals (note A is permuted, and the stride is changed to avoid bank conflicts)
def make_locals(slot) -> tuple[UOp, UOp]:
@@ -180,7 +180,7 @@ def custom_gemm(C:UOp, A:UOp, B:UOp) -> UOp:
# store the acc into gmem
cp_i, cp_j = UOp.range(BLOCK_M//TC_M//WARPGROUP_SIZE, 10004), UOp.range(BLOCK_N//TC_N, 10005)
c_load = lambda i: C[gx, cp_i*TC_M*WARPGROUP_SIZE + warpgroup*TC_M + (warp//16)*4+i, gy, cp_j*TC_N + warp%16]
store = UOp.group(*[c_load(i).store(acc[cp_j, cp_i].gep(i)) for i in range(4)])
store = UOp.group(*[c_load(i).store(acc[cp_j, cp_i].index(i)) for i in range(4)])
store = store.end(cp_i, cp_j)
return store.sink(arg=KernelInfo(name="custom_gemm", opts_to_apply=())).simplify()
@@ -192,12 +192,12 @@ acc = UOp.placeholder((4,), dtypes.float, 0, AddrSpace.REG)
acc = acc[init_l:=UOp.range(4, 1)].set(0.0, end=init_l)
# do the wmma
acc_load = UOp.vectorize(*[acc.after(K_loop)[i] for i in range(4)])
acc_load = UOp.stack(*[acc.after(K_loop)[i] for i in range(4)])
wmma_arg = ('WMMA_16_16_32_half_float', (16, 16, 32), dtypes.half, dtypes.float, 'AMD', 64, ((), (), ((3, 2), (2, 2))), ())
out = UOp(Ops.WMMA, dtypes.float.vec(4), (A_in, B_in, acc_load), arg=wmma_arg)
# store back the acc
acc = acc.after(UOp.group(*[acc[i].store(out.gep(i)) for i in range(4)]).end(K_loop))
acc = acc.after(UOp.group(*[acc[i].store(out.index(i)) for i in range(4)]).end(K_loop))
# store the acc into gmem
store = UOp.group(*[C[gx, (warp//16)*4+i, gy, warp%16].store(acc[i]) for i in range(4)])
+1 -1
View File
@@ -72,7 +72,7 @@ def custom_gemm(C:UOp, A:UOp, B:UOp) -> UOp:
K_outer_loop = UOp.range(K//BLOCK_K, 0, AxisType.REDUCE)
# split out the globals into blocks
C = C.src[0].cast(dtypes.float.vec(4).ptr(C.ptrdtype.size)).reshape((M//BLOCK_M, BLOCK_M, N//BLOCK_N, BLOCK_N))
C = C.src[0].cast(dtypes.float.vec(4)).reshape((M//BLOCK_M, BLOCK_M, N//BLOCK_N, BLOCK_N))
A = A.reshape((M//BLOCK_M, BLOCK_M, K//BLOCK_K, BLOCK_K))[gx, :, K_outer_loop, :]
B = B.reshape((K//BLOCK_K, BLOCK_K, N//BLOCK_N, BLOCK_N))[K_outer_loop, :, gy, :]
+498 -486
View File
File diff suppressed because it is too large Load Diff
+58 -57
View File
@@ -3,7 +3,8 @@ from typing import cast, Any, Callable
import os, ctypes, struct, hashlib, functools, importlib, mmap, errno, array, contextlib, sys, weakref, itertools, collections, atexit
assert sys.platform != 'win32'
from dataclasses import dataclass
from extra.hcq2.hcq2 import HCQ2Compiled, HCQAllocator, HCQ2Buffer, make_getaddr, make_ins, make_cmdbuf
from extra.hcq2.hcq2 import HCQ2Compiled, HCQAllocator, HCQ2Buffer, encode_kernargs_clike, make_getaddr, make_ins, make_cmdbuf, make_placeholder
from extra.hcq2.hcq2 import make_binary_patch
from tinygrad.uop.ops import sint, UOp
from tinygrad.device import Compiled, BufferSpec, Buffer, Device
from tinygrad.dtype import dtypes
@@ -102,11 +103,12 @@ def pm4_timestamp(ctx, dst):
return release_mem(ctx, make_getaddr(dst, ctx.devs), 0, ctx.pm4.data_sel__mec_release_mem__send_gpu_clock_counter,
ctx.pm4.int_sel__mec_release_mem__none)
def pm4_program(ctx, prg):
def pm4_program(ctx, call, prg):
data, info = prg.arg
lib_gpu, args = prg.src
lib_gpu = prg.src[0]
args = encode_kernargs_clike(call, prg, ctx.devs)
prog_addr = make_getaddr(lib_gpu, ctx.devs) + data.entry_point_offset
scratch_addr = make_getaddr(UOp.new_buffer(lib_gpu.device, data.private_segment_size, dtypes.uint8).rtag("scratch"), ctx.devs)
scratch_addr = make_getaddr(make_placeholder(ctx.devs, data.private_segment_size, dtypes.uint8, "scratch", unique=False), ctx.devs)
args_addr = make_getaddr(args, ctx.devs)
user_regs = []
@@ -134,9 +136,10 @@ def pm4_program(ctx, prg):
return UOp(Ops.LINEAR, dtypes.void, tuple(ins))
pm_pm4_opsel = PatternMatcher([
(UPat(Ops.CALL, src=(UPat(Ops.PROGRAM, name="prg"),), name="call", allow_any_len=True), pm4_program),
(UPat(Ops.WAIT, src=(UPat(name="dst"), UPat(name="val"))), pm4_wait),
(UPat(Ops.BARRIER), pm4_barrier),
(UPat(Ops.PROGRAM, name="prg"), pm4_program),
(UPat(Ops.CUSTOM_FUNCTION, arg="timestamp", src=(UPat(name="dst"),)), pm4_timestamp),
(UPat(Ops.STORE, src=(UPat((Ops.BUFFER, Ops.PARAM), name="dst"), UPat(name="val"))), pm4_store),
])
@@ -146,7 +149,7 @@ def pm4_submit(cmdbuf, devs):
# the compute queue's ring and its host-side ring/write/put pointers (placeholders, resolved in pm_bufferize)
for d in devs: q = Device[d].compute_queue
ring, wptr, doorbell, put_ptr = (UOp.new_buffer(devs, b.size, b.dtype).rtag(("COMPUTE:0", name))
ring, wptr, doorbell, put_ptr = (make_placeholder(devs, b.size, b.dtype, ("COMPUTE:0", name), unique=False)
for name, b in (("ring", q.ring), ("write_ptr", q.write_ptr), ("doorbell", q.doorbell), ("put_value", q.put_value)))
# place the cmdbuf at the ring's write offset, wrapping the ring
@@ -156,29 +159,30 @@ def pm4_submit(cmdbuf, devs):
ring_idx = ((put + i.cast(put.dtype)) % q.ring.size).cast(dtypes.int)
# copy the cmdbuf into the ring and advance the put/write pointers
copy_to_ring = ring.index(ring_idx, dtype=ring.dtype.ptr()).store(
cmdbuf.index(i*4, dtype=cmdbuf.dtype.ptr()).cast(dtypes.uint32.ptr()).load()).end(i)
bump_put_ptr = put_ptr.index(zero, dtype=put_ptr.dtype.ptr()).store(next_put)
bump_wptr = wptr.index(zero, dtype=wptr.dtype.ptr()).store(next_put)
copy_to_ring = ring.index(ring_idx).store(cmdbuf.index(i).load()).end(i)
bump_put_ptr = put_ptr.index(zero).store(next_put)
bump_wptr = wptr.index(zero).store(next_put)
# ring the doorbell once the copy and pointer bumps have landed
flush = UOp.barrier(copy_to_ring, bump_put_ptr, bump_wptr)
return doorbell.after(flush).index(zero, dtype=doorbell.dtype.ptr()).store(next_put)
return doorbell.after(flush).index(zero).store(next_put)
pm_pm4_submit = PatternMatcher([(UPat(Ops.LINEAR, name="lin"),
lambda lin: pm4_submit(make_cmdbuf(lin, to_tuple(lin.arg[0]), "compute"), to_tuple(lin.arg[0])))])
lambda lin: pm4_submit(make_cmdbuf(lin, to_tuple(lin.arg[0])), to_tuple(lin.arg[0])))])
# *****************
# SDMA
class SDMAOps(FastEnum): COPY = auto(); POLL_REGMEM = auto(); FENCE = auto(); TRAP = auto(); TIMESTAMP = auto() # noqa: E702
def sdma_copy(ctx, dst, src, copy):
def sdma_copy(ctx, call):
dst, src = call.src[1], call.src[2]
sz = src.max_numel() * src.dtype.itemsize
src_addr, dst_addr = make_getaddr(src, ctx.devs), make_getaddr(dst, ctx.devs)
return UOp(Ops.LINEAR, dtypes.void, tuple([make_ins(SDMAOps.COPY,
ctx.sdma.SDMA_OP_COPY | ctx.sdma.SDMA_PKT_COPY_LINEAR_HEADER_SUB_OP(ctx.sdma.SDMA_SUBOP_COPY_LINEAR),
ctx.sdma.SDMA_PKT_COPY_LINEAR_COUNT_COUNT(min(copy.arg - off, ctx.max_copy_size) - 1), 0,
*data64_le(src_addr + off), *data64_le(dst_addr + off)) for off in range(0, copy.arg, ctx.max_copy_size)]))
ctx.sdma.SDMA_PKT_COPY_LINEAR_COUNT_COUNT(min(sz - off, ctx.max_copy_size) - 1), 0,
*data64_le(src_addr + off), *data64_le(dst_addr + off)) for off in range(0, sz, ctx.max_copy_size)]))
def sdma_wait(ctx, dst, val):
op = ctx.sdma.SDMA_OP_POLL_REGMEM | ctx.sdma.SDMA_PKT_POLL_REGMEM_HEADER_FUNC(WAIT_REG_MEM_FUNCTION_GEQ) \
@@ -196,9 +200,10 @@ def sdma_timestamp(ctx, dst):
return make_ins(SDMAOps.TIMESTAMP, op, *data64_le(make_getaddr(dst, ctx.devs)))
pm_sdma_opsel = PatternMatcher([
(UPat(Ops.CALL, src=(UPat(Ops.COPY),), name="call", allow_any_len=True), sdma_copy),
(UPat(Ops.BARRIER), lambda: UOp(Ops.NOOP, dtypes.void, ())),
(UPat(Ops.WAIT, src=(UPat(name="dst"), UPat(name="val"))), sdma_wait),
(UPat(Ops.COPY, src=(UPat(name="dst"), UPat(name="src")), name="copy"), sdma_copy),
(UPat(Ops.CUSTOM_FUNCTION, arg="timestamp", src=(UPat(name="dst"),)), sdma_timestamp),
(UPat(Ops.STORE, src=(UPat((Ops.BUFFER, Ops.PARAM), name="dst"), UPat(name="val"))), sdma_store),
])
@@ -209,7 +214,7 @@ def sdma_submit(cmdbuf, devs):
# the sdma queue's ring and its host-side ring/write/put pointers
for d in devs: q = Device[d].sdma_queue(0)
ring, wptr, doorbell, put_ptr = (UOp.new_buffer(devs, b.size, b.dtype).rtag(("COPY:0", name))
ring, wptr, doorbell, put_ptr = (make_placeholder(devs, b.size, b.dtype, ("COPY:0", name), unique=False)
for name, b in (("ring", q.ring), ("write_ptr", q.write_ptr), ("doorbell", q.doorbell), ("put_value", q.put_value)))
# sdma needs the cmdbuf contiguous: if it won't fit before the ring end, restart at 0 and zero the tail
@@ -221,22 +226,32 @@ def sdma_submit(cmdbuf, devs):
# zero the wrapped tail, then copy the cmdbuf into the ring
zi = UOp.range(zero_amt_dw, 0, dtype=dtypes.int, src=(cmdbuf,))
zero_tail = ring.index(tail_off_dw + zi, dtype=ring.dtype.ptr()).store(UOp.const(dtypes.uint32, 0)).end(zi)
zero_tail = ring.index(tail_off_dw + zi).store(UOp.const(dtypes.uint32, 0)).end(zi)
i = UOp.range(UOp.const(dtypes.int, size_dw), 0, dtype=dtypes.int, src=(cmdbuf,))
copy_to_ring = ring.index(start_dw + i, dtype=ring.dtype.ptr()).store(
cmdbuf.index(i*4, dtype=cmdbuf.dtype.ptr()).cast(dtypes.uint32.ptr()).load()).end(i)
copy_to_ring = ring.index(start_dw + i).store(cmdbuf.index(i).load()).end(i)
# advance the put/write pointers past the zeroed tail and the cmdbuf
next_put_b = put_b + ((zero_amt_dw + size_dw) * 4).cast(put_b.dtype)
bump_put_ptr = put_ptr.index(zero, dtype=put_ptr.dtype.ptr()).store(next_put_b)
bump_wptr = wptr.index(zero, dtype=wptr.dtype.ptr()).store(next_put_b)
bump_put_ptr = put_ptr.index(zero).store(next_put_b)
bump_wptr = wptr.index(zero).store(next_put_b)
# ring the doorbell once the writes have landed
flush = UOp.barrier(zero_tail, copy_to_ring, bump_put_ptr, bump_wptr)
return doorbell.after(flush).index(zero, dtype=doorbell.dtype.ptr()).store(next_put_b)
return doorbell.after(flush).index(zero).store(next_put_b)
pm_sdma_submit = PatternMatcher([(UPat(Ops.LINEAR, name="lin"),
lambda lin: sdma_submit(make_cmdbuf(lin, to_tuple(lin.arg[0]), "copy"), to_tuple(lin.arg[0])))])
lambda lin: sdma_submit(make_cmdbuf(lin, to_tuple(lin.arg[0])), to_tuple(lin.arg[0])))])
@dataclass(frozen=True)
class AMDEncodeCtx: # encode-time constants for one queue: devs (every cmdbuf address resolves into these) + gfx version + packet/ip modules
devs: tuple[str, ...]; target: tuple[int, ...]; pm4: Any; sdma: Any; soc: Any # noqa: E702
gc: AMDIP; nbio: AMDIP; xccs: int; max_copy_size: int; tmpring_size: Callable # noqa: E702
def encode_queue(q:UOp) -> UOp|None:
d = Device[(devs:=to_tuple(q.arg[0]))[0]]
ctx = AMDEncodeCtx(devs, d.target, d.pm4, d.sdma, d.soc, d.gc, d.nbio, d.xccs, d.max_copy_size, d.tmpring_size)
opsel, submit = (pm_pm4_opsel, pm_pm4_submit) if q.arg[1].startswith("COMPUTE") else (pm_sdma_opsel, pm_sdma_submit)
return submit.rewrite(graph_rewrite(q, opsel + pm_flatten_linear, walk=True, ctx=ctx, name=f"{q.arg[1]} opsel"))
@dataclass(frozen=True)
class AMDProgramData:
@@ -245,7 +260,6 @@ class AMDProgramData:
enable_dispatch_ptr:int; enable_private_segment_sgpr:int
_amd_program_cache:dict[tuple[bytes,str], tuple[AMDProgramData,bytes]] = {}
def amd_build_program(prg:UOp) -> UOp:
dev = Device[to_tuple(prg.device)[0]] # TODO: rm this
if (cached:=_amd_program_cache.get(key:=(lib:=prg.src[3].arg, dev.device))) is None:
@@ -258,22 +272,17 @@ def amd_build_program(prg:UOp) -> UOp:
if (lds:=((desc.group_segment_fixed_size+511)//512)&0x1FF) > (dev.iface.props['lds_size_in_kb']*1024)//512:
raise RuntimeError("Too many resources requested: group_segment_size")
edp = desc.kernel_code_properties & hsa.AMD_KERNEL_CODE_PROPERTIES_ENABLE_SGPR_DISPATCH_PTR
cached = _amd_program_cache[key] = (AMDProgramData(
entry_point_offset=rodata + desc.kernel_code_entry_byte_offset,
data = AMDProgramData(entry_point_offset=rodata + desc.kernel_code_entry_byte_offset,
rsrc1=desc.compute_pgm_rsrc1 | ((1<<20) if dev.target[0]==11 else 0), # priv=1 on gfx11 for cwsr
rsrc2=desc.compute_pgm_rsrc2 | (lds<<15), rsrc3=desc.compute_pgm_rsrc3,
wave32=bool(desc.kernel_code_properties & 0x400),
private_segment_size=desc.private_segment_fixed_size,
kernargs_segment_size=desc.kernarg_size,
kernargs_alloc_size=desc.kernarg_size + (ctypes.sizeof(hsa.hsa_kernel_dispatch_packet_t) if edp else 0),
enable_dispatch_ptr=edp,
enable_private_segment_sgpr=desc.kernel_code_properties & hsa.AMD_KERNEL_CODE_PROPERTIES_ENABLE_SGPR_PRIVATE_SEGMENT_BUFFER), bytes(image))
wave32=bool(desc.kernel_code_properties & 0x400), private_segment_size=desc.private_segment_fixed_size, kernargs_segment_size=desc.kernarg_size,
kernargs_alloc_size=desc.kernarg_size + (ctypes.sizeof(hsa.hsa_kernel_dispatch_packet_t) if edp else 0), enable_dispatch_ptr=edp,
enable_private_segment_sgpr=desc.kernel_code_properties & hsa.AMD_KERNEL_CODE_PROPERTIES_ENABLE_SGPR_PRIVATE_SEGMENT_BUFFER)
buf = make_placeholder(prg.device, len(image), dtypes.uint8, "program")
cached = _amd_program_cache[key] = prg.replace(src=(buf.after(make_binary_patch(buf, bytes(image))),), arg=(data, prg.arg))
return cached
pm_prep_program = PatternMatcher([
(UPat(Ops.PROGRAM, src=(UPat(), UPat(), UPat(), UPat(Ops.BINARY)), name="prg"), amd_build_program),
])
class AMDAllocator(HCQAllocator['AMDDevice']):
def __init__(self, dev:AMDDevice):
super().__init__(dev, supports_copy_from_disk=dev.has_sdma_queue, supports_transfer=dev.has_sdma_queue and not dev.is_usb())
@@ -516,23 +525,15 @@ class PCIIface(PCIIfaceBase):
def _mock(iface, name=None): return type(name or f"MOCK{iface.__name__}", (iface,), {})
@dataclass(frozen=True)
class AMDEncodeCtx: # encode-time constants for one queue: devs (every cmdbuf address resolves into these) + gfx version + packet/ip modules
devs: tuple[str, ...]; target: tuple[int, ...]; pm4: Any; sdma: Any; soc: Any # noqa: E702
gc: AMDIP; nbio: AMDIP; xccs: int; max_copy_size: int; tmpring_size: Callable # noqa: E702
def encode_queue(q:UOp) -> UOp|None:
if not (isinstance(q.arg, tuple) and len(q.arg) == 2 and isinstance(q.arg[1], str) and q.arg[1].startswith(("COMPUTE", "COPY"))): return None
d = Device[(devs:=to_tuple(q.arg[0]))[0]]
ctx = AMDEncodeCtx(devs, d.target, d.pm4, d.sdma, d.soc, d.gc, d.nbio, d.xccs, d.max_copy_size, d.tmpring_size)
opsel, submit = (pm_pm4_opsel, pm_pm4_submit) if q.arg[1].startswith("COMPUTE") else (pm_sdma_opsel, pm_sdma_submit)
return submit.rewrite(graph_rewrite(q, opsel + pm_flatten_linear, walk=True, ctx=ctx, name=f"{q.arg[1]} opsel"))
pm_lower = PatternMatcher([
(UPat(Ops.CUSTOM_FUNCTION, arg="submit", src=(UPat(Ops.LINEAR, name="q"),)), encode_queue),
])
class AMDDevice(HCQ2Compiled):
pm_lower = PatternMatcher([
# prep program
(UPat(Ops.PROGRAM, src=(UPat(), UPat(), UPat(), UPat(Ops.BINARY)), name="prg"), amd_build_program),
# encoding of cmdbuf
(UPat(Ops.CUSTOM_FUNCTION, arg="submit_cmdbuf", src=(UPat(Ops.LINEAR, name="q"),)), encode_queue),
])
timestamp_divider = 100.0 # AMD GPU clock: ticks/us
ifaces = [KFDIface, PCIIface]
@@ -579,7 +580,7 @@ class AMDDevice(HCQ2Compiled):
# Scratch setup
self.max_private_segment_size = 0
self.pm_bufferize = PatternMatcher([(UPat(Ops.BUFFER, tag="scratch", name="b"), lambda ctx, b: ctx.scratch_buffer(b.max_numel()))]) + self.pm_bufferize
self.pm_bufferize = PatternMatcher([(UPat(Ops.PARAM, tag="scratch", name="b"), lambda ctx, b: ctx[0].scratch_buffer(b.max_numel()))]) + self.pm_bufferize
self.pmc_enabled:bool = PROFILE > 0 and PMC > 0
if self.pmc_enabled:
@@ -627,10 +628,10 @@ class AMDDevice(HCQ2Compiled):
qname = f"{'COPY' if queue_type == kfd.KFD_IOC_QUEUE_TYPE_SDMA else 'COMPUTE'}:{idx}"
self.pm_bufferize = PatternMatcher([
(UPat(Ops.BUFFER, tag={(qname, name)}), lambda ctx, b=getattr(queue, name): b) for name in ["ring", "write_ptr", "doorbell", "put_value"]
(UPat(Ops.PARAM, tag={(qname, name)}), lambda ctx, b=getattr(queue, name): b) for name in ["ring", "write_ptr", "doorbell", "put_value"]
] + [
(UPat(Ops.BUFFER, tag={(qname, "timeline_signal")}), lambda ctx, q=qname: ctx.timeline_signal(q)),
(UPat(Ops.BUFFER, tag={(qname, "timeline_value")}), lambda ctx, q=qname: ctx.timeline_value(q)),
(UPat(Ops.PARAM, tag={(qname, "timeline_signal")}), lambda ctx, q=qname: ctx[0].timeline_signal(q)),
(UPat(Ops.PARAM, tag={(qname, "timeline_value")}), lambda ctx, q=qname: ctx[0].timeline_value(q)),
]) + self.pm_bufferize
return queue
+1 -1
View File
@@ -18,7 +18,7 @@ prg = dev.runtime("write_ones", mbin)
prg(buf0._buf, global_size=(1,65537,1), local_size=(1,1,1), wait=True)
import numpy as np
def to_np(buf): return np.frombuffer(buf.as_memoryview().cast(buf.dtype.base.fmt), dtype=_to_np_dtype(buf.dtype.base))
def to_np(buf): return np.frombuffer(buf.as_memoryview().cast(buf.dtype.fmt), dtype=_to_np_dtype(buf.dtype))
big = to_np(buf0)
print(big)
+3 -3
View File
@@ -16,7 +16,7 @@ def _custom_fused_ce_loss_fwd(loss_out:UOp, max_out:UOp, lse_out:UOp, logits:UOp
row_lse = (logits[b, s, v_lse].cast(dtypes.float) - row_max).exp().reduce(v_lse, arg=Ops.ADD).log() + row_max
v_smooth = UOp.range(vocab, 3, axis_type=AxisType.REDUCE)
target = logits[b, s, targets[row].cast(dtypes.weakint)].cast(dtypes.float)
target = logits[b, s, targets[row].cast(dtypes.index)].cast(dtypes.float)
mean_logits = logits[b, s, v_smooth].cast(dtypes.float).reduce(v_smooth, arg=Ops.ADD) / vocab
loss = row_lse - (1.0 - label_smoothing) * target - label_smoothing * mean_logits
stores = UOp.group(loss_out[row].store(loss), max_out[row].store(row_max), lse_out[row].store(row_lse))
@@ -32,11 +32,11 @@ def _custom_fused_ce_loss_bwd(d_logits:UOp, logits:UOp, lse:UOp, targets:UOp, sc
s = row % seq
prob = (logits[b, s, v].cast(dtypes.float) - lse[row]).exp()
target = v.eq(targets[row].cast(dtypes.weakint)).where(1.0 - label_smoothing, 0.0)
target = v.eq(targets[row].cast(dtypes.index)).where(1.0 - label_smoothing, 0.0)
smooth = label_smoothing / vocab
grad = (prob - target - smooth) * scale[0]
return d_logits[b, s, v].store(grad.cast(d_logits.dtype.base)).end(v, row).sink(arg=KernelInfo(f"fused_ce_loss_bwd_{rows}_{vocab}"))
return d_logits[b, s, v].store(grad.cast(d_logits.dtype)).end(v, row).sink(arg=KernelInfo(f"fused_ce_loss_bwd_{rows}_{vocab}"))
def _fused_ce_loss_bwd(gradient:UOp, kernel:UOp, label_smoothing:float):
# NOTE: forward inputs are (loss_out, max_out, lse_out, logits, targets)
@@ -41,7 +41,7 @@ def _custom_silu_mul_quantize_mxfp8(fp8_out:UOp, e8_out:UOp, si_out:UOp, x_w1:UO
scaled = (act * qscale).maximum(-FP8_MAX).minimum(FP8_MAX)
e8u8 = e8f.cast(dtypes.uint8)
fp8_store = fp8_out[idx].store(scaled.cast(fp8_out.dtype.base)).end(lane)
fp8_store = fp8_out[idx].store(scaled.cast(fp8_out.dtype)).end(lane)
e8_store = e8_out.after(fp8_store)[super_idx * PACK + sb].store(e8u8)
packed = (e8u8.cast(dtypes.uint32) << (sb.cast(dtypes.uint32) * 8)).reduce(sb, arg=Ops.ADD)
row, col4 = super_idx // sk4, super_idx % sk4
@@ -72,8 +72,8 @@ def _custom_silu_mul_bwd_mxfp8(gx1_out:UOp, gx3_out:UOp, x_w1:UOp, x_w3:UOp, gra
sig = (1.0 + (w1 * -LOG2E).exp2()).reciprocal()
s = w1 * sig
sprime = sig * (1.0 + w1 * (1.0 - sig))
gx1 = gx1_out[idx].store((ga * sprime * w3).cast(gx1_out.dtype.base))
gx3 = gx3_out.after(gx1)[idx].store((ga * s).cast(gx3_out.dtype.base))
gx1 = gx1_out[idx].store((ga * sprime * w3).cast(gx1_out.dtype))
gx3 = gx3_out.after(gx1)[idx].store((ga * s).cast(gx3_out.dtype))
return gx3.end(lane, tid, wg).sink(arg=KernelInfo(f"silu_mul_bwd_mxfp8_{n_elems}", opts_to_apply=()))
def _silu_mul_quantize_mxfp8_bwd(gradient:UOp, kernel:UOp):
@@ -27,7 +27,7 @@ def _custom_quantize_fp8_with_amax(fp8_out:UOp, amax_partial:UOp, x:UOp, amax_st
abs_x = (x_f < 0.0).where(-x_f, x_f)
scaled = (x_f * scale).maximum(-FP8_MAX).minimum(FP8_MAX)
fp8_store = fp8_out[idx].store(scaled.cast(fp8_out.dtype.base)).end(lane)
fp8_store = fp8_out[idx].store(scaled.cast(fp8_out.dtype)).end(lane)
lane_max = abs_x.reduce(lane, arg=Ops.MAX)
lmax = UOp.placeholder((1,), dtypes.float, slot=1, addrspace=AddrSpace.REG)
@@ -56,7 +56,7 @@ def _custom_quantize_fp8_scalar(fp8_out:UOp, x:UOp, amax_state:UOp) -> UOp:
x_f = x.reshape(n_elems)[i].cast(dtypes.float)
scale = FP8_MAX / (amax_state[0].cast(dtypes.float) + 1e-8)
store = fp8_out.reshape(n_elems)[i].store((x_f * scale).cast(fp8_out.dtype.base))
store = fp8_out.reshape(n_elems)[i].store((x_f * scale).cast(fp8_out.dtype))
return store.end(i).sink(arg=KernelInfo(f"quantize_fp8_scalar_{n_elems}"))
@@ -38,7 +38,7 @@ def _custom_quantize_mxfp8(fp8_out:UOp, e8_out:UOp, si_out:UOp, x:UOp) -> UOp:
scaled = (x_f * qscale).maximum(-FP8_MAX).minimum(FP8_MAX)
e8u8 = e8f.cast(dtypes.uint8)
fp8_store = fp8_out[idx].store(scaled.cast(fp8_out.dtype.base)).end(lane)
fp8_store = fp8_out[idx].store(scaled.cast(fp8_out.dtype)).end(lane)
e8_store = e8_out.after(fp8_store)[super_idx * PACK + sb].store(e8u8)
# pack the 4 e8 of this super-block into one uint32 (little-endian: byte sb), write transposed (sk4, row)
+1 -3
View File
@@ -78,9 +78,7 @@ hexdump(to_mv(cl_buf_desc_ptr, 0x100))
rawbuf_ptr = to_mv(cl_buf_desc_ptr, 0x100).cast('Q')[20] # offset 0xA0 is a raw gpu pointer.
# create QCOM tensor with the externally managed buffer
# dtypes.imageh = cl.cl_image_format(cl.CL_RGBA, cl.CL_HALF_FLOAT)
# dtypes.imagef = cl.cl_image_format(cl.CL_RGBA, cl.CL_FLOAT)
x = Tensor.from_blob(rawbuf_ptr, (h*w*4,), dtype=dtypes.imagef((h,w)), device='QCOM')
x = Tensor.from_blob(rawbuf_ptr, (h,w,4), dtype=dtypes.float, device='QCOM')
y = (x + 1).tolist()
print(y[:10])
+26 -16
View File
@@ -20,8 +20,8 @@ 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):
def grad(dou:UOp, ker:UOp) -> tuple[None, None, UOp, UOp, UOp]:
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 grad(dou:UOp, ker:UOp) -> tuple:
do = Tensor(dou, device=dou.device)
attn = Tensor(ker.src[1].after(ker), device=ker.src[1].device)
l_vec = Tensor(ker.src[2].after(ker), device=ker.src[2].device)
@@ -40,25 +40,33 @@ def _fa_grad_fxn(B, H, N, D, H_local, H_KV_local, H_KV, B_local, shard_axis, sha
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]
# unshuffle dq: atomic_pk_add_bf16_with_warpid creates a shuffled layout within each 16x128 tile
# decompose each tile into (j=4, a=2, b=2, d=4, e=4, k=4, c=2) and permute to (e, k, j, a, d, b, c) = standard row-major
dq = dq.reshape(B, H, N//16, 4, 2, 2, 4, 4, 4, 2).permute(0, 1, 2, 7, 8, 3, 4, 6, 5, 9).reshape(B, H, N, D).transpose(1, 2)
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)
else:
dq = dq.reshape(B, H, N//16, 4, 2, 2, D//32, 4, 4, 2).permute(0, 1, 2, 7, 8, 3, 4, 6, 5, 9).reshape(B, H, N, D).transpose(1, 2)
# reduce partial dK/dV across GROUP_SIZE query heads
dk = dk_partial.reshape(B, GROUP_SIZE, N, H_KV, D).sum(1)
dv = dv_partial.reshape(B, GROUP_SIZE, N, H_KV, D).sum(1)
return None, None, dq.uop, dk.uop, dv.uop
if not has_sink: return None, None, dq.uop, dk.uop, dv.uop
sinks = Tensor(ker.src[6], device=ker.src[6].device)
p_sink = (sinks.reshape(1, H, 1, 1) - l_vec).exp()
dsink = -(delta_vec.float() * p_sink).sum(axis=(0, 2, 3))
return None, None, dq.uop, dk.uop, dv.uop, dsink.uop
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):
def flash_attention(xq, xk, xv, attn_mask:Tensor|None=None, is_causal:bool=False, write_flat:bool=False, sinks:Tensor|None=None):
assert attn_mask is None, "attn_mask not supported"
assert is_causal, "only causal attention supported"
B, N, H, D = xq.shape
H_KV = xk.shape[2]
assert D == 128, "only D=128 supported"
assert D in (64, 128), "only D=64 or D=128 supported"
has_sink = sinks is not None
if has_sink: sinks = sinks.float()
num_devices = len(xq.device) if isinstance(xq.device, tuple) else 1
is_dp = xq.uop.axis == 0
@@ -77,17 +85,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)
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)
attn, l_vec = Tensor.custom_kernel(attn, l_vec, xq, xk, xv, 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), grad_fxn=grad)[:2]
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]
return attn, attn, l_vec
@functools.cache
def custom_fa_forward(o:UOp, l_vec:UOp, q:UOp, k:UOp, v:UOp, device:str, arch:str, B:int, N:int, H:int, H_KV:int, D:int):
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):
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_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)}"]
Q_BLOCK_SIZE = 32
NUM_WARPS = 8
@@ -100,7 +109,8 @@ def custom_fa_forward(o:UOp, l_vec:UOp, q:UOp, k:UOp, v:UOp, device:str, arch:st
el = q.dtype.itemsize
mem = (2*B*N*H*D + 2*B*N*H_KV*D) * el + B*H*N * l_vec.dtype.itemsize
estimates = Estimates(ops=2*B*H*N*N*D, lds=mem, mem=mem)
sink = UOp.sink(o.base, l_vec.base, q.base, k.base, v.base,
buf_inputs = (o.base, l_vec.base, q.base, k.base, v.base) + ((sinks.base,) if has_sink else ())
sink = UOp.sink(*buf_inputs,
threadIdx_x, blockIdx_x, blockIdx_y, blockIdx_z,
arg=KernelInfo(name="custom_fa_forward", estimates=estimates))
@@ -117,7 +127,7 @@ def custom_fa_forward(o:UOp, l_vec:UOp, q:UOp, k:UOp, v:UOp, device:str, arch:st
def custom_fa_backward_pre(delta_vec:UOp, dq:UOp, o:UOp, do:UOp, device:str, arch:str, B:int, N:int, H:int, H_KV:int, D:int):
code = (pathlib.Path(__file__).parent / "fa_bwd_pre.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_B={B}", f"-DATTN_N={N}", f"-DATTN_H={H}", f"-DATTN_D={D}"]
DOT_SLICE_QO = 16
NUM_WARPS = 4
@@ -147,7 +157,7 @@ def custom_fa_backward_pre(delta_vec:UOp, dq:UOp, o:UOp, do:UOp, device:str, arc
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):
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_B={B}", f"-DATTN_N={N}", f"-DATTN_H={H}", f"-DATTN_H_KV={H_KV}", f"-DATTN_D={D}"]
BLOCK_SIZE_KV = 256
NUM_WARPS = 4
@@ -177,7 +187,7 @@ def custom_fa_backward(dq:UOp, dk:UOp, dv:UOp, do:UOp, q:UOp, k:UOp, v:UOp, l_ve
def custom_fa_backward_post(dq_out:UOp, dq_in:UOp, device:str, arch:str, B:int, N:int, H:int, H_KV:int, D:int):
code = (pathlib.Path(__file__).parent / "fa_bwd_post.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_B={B}", f"-DATTN_N={N}", f"-DATTN_H={H}", f"-DATTN_D={D}"]
DOT_SLICE_QO = 16
NUM_WARPS = 4
File diff suppressed because it is too large Load Diff
+2
View File
@@ -12,7 +12,9 @@ constexpr int ATTN_H = 64; // number of query heads
constexpr int ATTN_N = 1024; // sequence length
#endif
#ifndef ATTN_D
constexpr int ATTN_D = 128; // dimension
#endif
constexpr int DOT_SLICE_QO = 16;
#define NUM_WARPS 4
+2
View File
@@ -18,7 +18,9 @@ constexpr int GROUP_SIZE = ATTN_H / ATTN_H_KV; // queries per KV head group
constexpr int ATTN_N = 1024; // sequence length
#endif
#ifndef ATTN_D
constexpr int ATTN_D = 128; // dimension
#endif
constexpr int STEP_QO = 64; // block size for QO
constexpr int BLOCK_SIZE_KV = 256; // block size for KV
constexpr int SLICE_QO = 32;
+42 -12
View File
@@ -18,7 +18,19 @@ constexpr int GROUP_SIZE = ATTN_H / ATTN_H_KV; // queries per KV head group
constexpr int ATTN_N = 8192; // sequence length
#endif
#ifndef ATTN_D
constexpr int ATTN_D = 128; // dimension
#endif
#ifndef ATTN_SINK
#define ATTN_SINK 0
#endif
#if ATTN_D == 64
#define FA_VM2 "1"
#define FA_VM4 "2"
#else
#define FA_VM2 "2"
#define FA_VM4 "4"
#endif
constexpr int Q_BLOCK_SIZE = 32; // q block size
constexpr int KV_BLOCK_SIZE = 64; // kv block size
constexpr bool causal = true;
@@ -156,7 +168,11 @@ template<int D> struct attn_globals {
};
template<int D> __launch_bounds__(NUM_THREADS, 2)
__global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_ptr, bf16 *V_ptr) {
__global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_ptr, bf16 *V_ptr
#if ATTN_SINK
, float *Sinks_ptr
#endif
) {
_gl_QKVO Og{O_ptr, ATTN_B, ATTN_N, ATTN_H, ATTN_D};
_gl_QKVO Qg{Q_ptr, ATTN_B, ATTN_N, ATTN_H, ATTN_D};
_gl_QKVO Kg{K_ptr, ATTN_B, ATTN_N, ATTN_H_KV, ATTN_D};
@@ -233,7 +249,7 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
load(k_reg, k_smem[0]);
__builtin_amdgcn_sched_barrier(0);
asm volatile("s_waitcnt lgkmcnt(0)");
asm volatile("s_waitcnt vmcnt(2)");
asm volatile("s_waitcnt vmcnt(" FA_VM2 ")");
__builtin_amdgcn_sched_barrier(0);
__builtin_amdgcn_s_barrier();
@@ -272,7 +288,7 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
// 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);
asm volatile("s_waitcnt lgkmcnt(0)");
asm volatile("s_waitcnt vmcnt(4)");
asm volatile("s_waitcnt vmcnt(" FA_VM4 ")");
__builtin_amdgcn_sched_barrier(0);
__builtin_amdgcn_s_barrier();
@@ -301,7 +317,7 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
// Load V0 into registers
load(v_reg, v_smem[0]);
asm volatile("s_waitcnt lgkmcnt(0)");
asm volatile("s_waitcnt vmcnt(4)");
asm volatile("s_waitcnt vmcnt(" FA_VM4 ")");
__builtin_amdgcn_sched_barrier(0);
__builtin_amdgcn_s_barrier();
__builtin_amdgcn_sched_barrier(0);
@@ -332,7 +348,7 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
// Load K2 into registers
load(k_reg, k_smem[0]);
asm volatile("s_waitcnt lgkmcnt(0)");
asm volatile("s_waitcnt vmcnt(4)");
asm volatile("s_waitcnt vmcnt(" FA_VM4 ")");
__builtin_amdgcn_sched_barrier(0);
__builtin_amdgcn_s_barrier();
__builtin_amdgcn_sched_barrier(0);
@@ -368,7 +384,7 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
}
}
asm volatile("s_waitcnt lgkmcnt(0)");
asm volatile("s_waitcnt vmcnt(4)");
asm volatile("s_waitcnt vmcnt(" FA_VM4 ")");
__builtin_amdgcn_sched_barrier(0);
__builtin_amdgcn_s_barrier();
__builtin_amdgcn_sched_barrier(0);
@@ -399,7 +415,7 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
// Load K3 into registers
load(k_reg, k_smem[1]);
asm volatile("s_waitcnt lgkmcnt(0)");
asm volatile("s_waitcnt vmcnt(4)");
asm volatile("s_waitcnt vmcnt(" FA_VM4 ")");
__builtin_amdgcn_sched_barrier(0);
__builtin_amdgcn_s_barrier();
__builtin_amdgcn_sched_barrier(0);
@@ -436,7 +452,7 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
}
}
asm volatile("s_waitcnt lgkmcnt(0)");
asm volatile("s_waitcnt vmcnt(4)");
asm volatile("s_waitcnt vmcnt(" FA_VM4 ")");
__builtin_amdgcn_sched_barrier(0);
__builtin_amdgcn_s_barrier();
__builtin_amdgcn_sched_barrier(0);
@@ -467,7 +483,7 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
// Load K4 into registers
load(k_reg, k_smem[0]);
asm volatile("s_waitcnt lgkmcnt(0)");
asm volatile("s_waitcnt vmcnt(4)");
asm volatile("s_waitcnt vmcnt(" FA_VM4 ")");
__builtin_amdgcn_sched_barrier(0);
__builtin_amdgcn_s_barrier();
__builtin_amdgcn_sched_barrier(0);
@@ -499,7 +515,7 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
}
}
asm volatile("s_waitcnt lgkmcnt(0)");
asm volatile("s_waitcnt vmcnt(2)");
asm volatile("s_waitcnt vmcnt(" FA_VM2 ")");
__builtin_amdgcn_sched_barrier(0);
__builtin_amdgcn_s_barrier();
__builtin_amdgcn_sched_barrier(0);
@@ -529,7 +545,7 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
// Load K5 into registers
load(k_reg, k_smem[1]);
asm volatile("s_waitcnt lgkmcnt(0)");
asm volatile("s_waitcnt vmcnt(2)");
asm volatile("s_waitcnt vmcnt(" FA_VM2 ")");
__builtin_amdgcn_sched_barrier(0);
__builtin_amdgcn_s_barrier();
__builtin_amdgcn_sched_barrier(0);
@@ -604,6 +620,16 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
// Cluster 12:
// A5V5
mma_AtB(o_reg, v_reg, att_block_bf16_in, o_reg);
#if ATTN_SINK
{
const float sink_l2 = Sinks_ptr[head_idx] * 1.44269504089f;
typename attn_tile<float, col_l, rt_32x32_s>::row_vec sink_term;
mul(sink_term, max_vec, -1.0f);
add(sink_term, sink_term, sink_l2);
exp2(sink_term, sink_term);
add(norm_vec, norm_vec, sink_term);
}
#endif
div_col(o_reg, o_reg, norm_vec);
__builtin_amdgcn_sched_barrier(0);
__builtin_amdgcn_s_barrier();
@@ -625,4 +651,8 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
store(g.L_vec, norm_vec, {batch_idx, head_idx, 0, tile_idx});
}
template __global__ void attend_ker<ATTN_D>(bf16*, float*, bf16*, bf16*, bf16*);
template __global__ void attend_ker<ATTN_D>(bf16*, float*, bf16*, bf16*, bf16*
#if ATTN_SINK
, float*
#endif
);
+2 -2
View File
@@ -23,7 +23,7 @@ __device__ inline static void atomic_pk_add_bf16_with_warpid(const GL &dst, cons
std::uint64_t as_u64 = static_cast<std::uint64_t>(as_int);
buffer_resource br = make_buffer_resource(as_u64, buffer_size, 0x00020000);
int lane_offset = laneid * 2 + warpid * 512;
int lane_offset = laneid * 2 + warpid * (RT::rows * RT::cols);
using range_type = ducks::art::get_nth_range_t<typename RT::register_ranges, N * RT::width + M>;
@@ -65,7 +65,7 @@ __device__ inline static void atomic_pk_add_bf16_with_warpid(const GL &dst, cons
std::uint64_t as_u64 = static_cast<std::uint64_t>(as_int);
buffer_resource br = make_buffer_resource(as_u64, buffer_size, 0x00020000);
int lane_offset = laneid * 2 + warpid * 512;
int lane_offset = laneid * 2 + warpid * (RT::rows * RT::cols);
auto perform_atomic_pk_add_bf16_with_warpid = [&]<int N, int M>() {
using range_type = ducks::art::get_nth_range_t<typename RT::register_ranges, N * RT::width + M>;
+56 -60
View File
@@ -2,7 +2,7 @@ import math
from typing import cast, Callable
from tinygrad import dtypes
from tinygrad.uop.ops import AxisType, UOp, Ops
from tinygrad.dtype import AddrSpace, PtrDType
from tinygrad.dtype import AddrSpace
from tinygrad.helpers import prod
from extra.thunder.tiny.tk import WARP_THREADS
@@ -47,8 +47,8 @@ class Group:
rngs_for_shape = tuple(self.ker.raw_range(dim) for dim in dst.shape)
src_load = src[*rngs_for_shape]
if src.dtype.base != dst.dtype.base:
src_load = src_load.cast(dst.dtype.base)
if src.dtype != dst.dtype:
src_load = src_load.cast(dst.dtype)
dst_store = dst[*rngs_for_shape].store(src_load).end(*rngs_for_shape)
self.ker.push_store(dst_store, dst)
@@ -62,8 +62,8 @@ class Group:
for width in self.ker.range(src.shape[-2], track=False):
for inner in self.ker.range(src.shape[-1], track=False):
src_load = src[height, width, inner]
if src.dtype.base != dst.dtype.base:
src_load = src_load.cast(dst.dtype.base)
if src.dtype != dst.dtype:
src_load = src_load.cast(dst.dtype)
dst_store = dst[width, height, inner].store(src_load).end(height, width, inner)
self.ker.push_store(dst_store, dst)
@@ -84,16 +84,16 @@ class Group:
for width in self.ker.range(c.shape[-2], track=False):
for inner in self.ker.range(a.shape[-2], axis_type=AxisType.REDUCE, track=False):
if a_base_shape.cols == 16:
a_in = UOp.vectorize(*[a[height, inner, i] for i in range(4)])
b_in = UOp.vectorize(*[b[inner, width, i] for i in range(4)])
a_in = UOp.stack(*[a[height, inner, i] for i in range(4)])
b_in = UOp.stack(*[b[inner, width, i] for i in range(4)])
elif a_base_shape.cols == 32:
a_in = UOp.vectorize(*[a[height, inner, i] for i in range(8)])
b_in = UOp.vectorize(*[b[inner, width, i] for i in range(8)])
a_in = UOp.stack(*[a[height, inner, i] for i in range(8)])
b_in = UOp.stack(*[b[inner, width, i] for i in range(8)])
else: raise NotImplementedError(f"mma_AB not implemented for {a_base_shape.cols=}")
d_in = UOp.vectorize(*[c[height, width, i] for i in range(4)])
d_in = UOp.stack(*[c[height, width, i] for i in range(4)])
out = UOp(Ops.WMMA, dtypes.float32.vec(4), (a_in, b_in, d_in), arg=wmma_arg)
c_i = [c[height, width, i].store(out.gep(i)) for i in range(4)]
c_i = [c[height, width, i].store(out.index(i)) for i in range(4)]
c_store = UOp.group(*c_i).end(height, width, inner)
self.ker.push_store(c_store, c)
@@ -114,16 +114,16 @@ class Group:
for width in self.ker.range(c.shape[-2], track=False):
for inner in self.ker.range(a.shape[-2], axis_type=AxisType.REDUCE, track=False):
if a_base_shape.cols == 16:
a_in = UOp.vectorize(*[a[height, inner, i] for i in range(4)])
b_in = UOp.vectorize(*[b[width, inner, i] for i in range(4)])
a_in = UOp.stack(*[a[height, inner, i] for i in range(4)])
b_in = UOp.stack(*[b[width, inner, i] for i in range(4)])
elif a_base_shape.cols == 32:
a_in = UOp.vectorize(*[a[height, inner, i] for i in range(8)])
b_in = UOp.vectorize(*[b[width, inner, i] for i in range(8)])
a_in = UOp.stack(*[a[height, inner, i] for i in range(8)])
b_in = UOp.stack(*[b[width, inner, i] for i in range(8)])
else: raise NotImplementedError(f"mma_ABt not implemented for {a_base_shape.cols=}")
d_in = UOp.vectorize(*[c[height, width, i] for i in range(4)])
d_in = UOp.stack(*[c[height, width, i] for i in range(4)])
out = UOp(Ops.WMMA, dtypes.float32.vec(4), (a_in, b_in, d_in), arg=wmma_arg)
c_i = [c[height, width, i].store(out.gep(i)) for i in range(4)]
c_i = [c[height, width, i].store(out.index(i)) for i in range(4)]
c_store = UOp.group(*c_i).end(height, width, inner)
self.ker.push_store(c_store, c)
@@ -144,16 +144,16 @@ class Group:
for width in self.ker.range(c.shape[-2], track=False):
for inner in self.ker.range(a.shape[-3], axis_type=AxisType.REDUCE, track=False):
if a_base_shape.cols == 16:
a_in = UOp.vectorize(*[a[inner, height, i] for i in range(4)])
b_in = UOp.vectorize(*[b[inner, width, i] for i in range(4)])
a_in = UOp.stack(*[a[inner, height, i] for i in range(4)])
b_in = UOp.stack(*[b[inner, width, i] for i in range(4)])
elif a_base_shape.cols == 32:
a_in = UOp.vectorize(*[a[inner, height, i] for i in range(8)])
b_in = UOp.vectorize(*[b[inner, width, i] for i in range(8)])
a_in = UOp.stack(*[a[inner, height, i] for i in range(8)])
b_in = UOp.stack(*[b[inner, width, i] for i in range(8)])
else: raise NotImplementedError(f"mma_AtB not implemented for {a_base_shape.cols=}")
d_in = UOp.vectorize(*[c[height, width, i] for i in range(4)])
d_in = UOp.stack(*[c[height, width, i] for i in range(4)])
out = UOp(Ops.WMMA, dtypes.float32.vec(4), (a_in, b_in, d_in), arg=wmma_arg)
c_i = [c[height, width, i].store(out.gep(i)) for i in range(4)]
c_i = [c[height, width, i].store(out.index(i)) for i in range(4)]
c_store = UOp.group(*c_i).end(height, width, inner)
self.ker.push_store(c_store, c)
@@ -174,16 +174,16 @@ class Group:
for width in self.ker.range(c.shape[-2], track=False):
for inner in self.ker.range(a.shape[-3], axis_type=AxisType.REDUCE, track=False):
if a_base_shape.cols == 16:
a_in = UOp.vectorize(*[a[inner, height, i] for i in range(4)])
b_in = UOp.vectorize(*[b[width, inner, i] for i in range(4)])
a_in = UOp.stack(*[a[inner, height, i] for i in range(4)])
b_in = UOp.stack(*[b[width, inner, i] for i in range(4)])
elif a_base_shape.cols == 32:
a_in = UOp.vectorize(*[a[inner, height, i] for i in range(8)])
b_in = UOp.vectorize(*[b[width, inner, i] for i in range(8)])
a_in = UOp.stack(*[a[inner, height, i] for i in range(8)])
b_in = UOp.stack(*[b[width, inner, i] for i in range(8)])
else: raise NotImplementedError(f"mma_AtBt not implemented for {a_base_shape.cols=}")
d_in = UOp.vectorize(*[c[height, width, i] for i in range(4)])
d_in = UOp.stack(*[c[height, width, i] for i in range(4)])
out = UOp(Ops.WMMA, dtypes.float32.vec(4), (a_in, b_in, d_in), arg=wmma_arg)
c_i = [c[height, width, i].store(out.gep(i)) for i in range(4)]
c_i = [c[height, width, i].store(out.index(i)) for i in range(4)]
c_store = UOp.group(*c_i).end(height, width, inner)
self.ker.push_store(c_store, c)
@@ -209,8 +209,8 @@ class Group:
vec, src = cast(UOp, vec), cast(UOp, src)
assert self.warps == 1
red_local = self.ker.alloc((self.group_threads,), src.dtype.base, AddrSpace.LOCAL)
red_reg = self.ker.alloc((1,), src.dtype.base, AddrSpace.REG)
red_local = self.ker.alloc((self.group_threads,), src.dtype, AddrSpace.LOCAL)
red_reg = self.ker.alloc((1,), src.dtype, AddrSpace.REG)
for height in self.ker.range(src.shape[-3], track=False):
i = self.ker.raw_range(red_reg.size)
@@ -243,8 +243,8 @@ class Group:
vec, src = cast(UOp, vec), cast(UOp, src)
assert self.warps == 1
red_local = self.ker.alloc((self.group_threads,), src.dtype.base, AddrSpace.LOCAL)
red_reg = self.ker.alloc((1,), src.dtype.base, AddrSpace.REG)
red_local = self.ker.alloc((self.group_threads,), src.dtype, AddrSpace.LOCAL)
red_reg = self.ker.alloc((1,), src.dtype, AddrSpace.REG)
for width in self.ker.range(src.shape[-2], track=False):
i = self.ker.raw_range(red_reg.size)
@@ -277,9 +277,7 @@ class Group:
def load(self, dst:ALL_TILES, src:ALL_TILES, dst_idxs:tuple[UOp|int,...]=(), idxs:tuple[UOp|int,...]=(), axis:int=0):
dst, src = cast(UOp, dst), cast(UOp, src)
assert isinstance(dst.dtype, PtrDType) and isinstance(src.dtype, PtrDType)
dst_dtype, src_dtype = dst.dtype, src.dtype
if dst_dtype.addrspace == AddrSpace.REG and src_dtype.addrspace == AddrSpace.LOCAL:
if dst.addrspace == AddrSpace.REG and src.addrspace == AddrSpace.LOCAL:
laneid = self.ker.laneid
rt, st = cast(RT, dst), cast(ST, src)
elements_per_thread = rt.base_shape.elements_per_thread
@@ -308,11 +306,11 @@ class Group:
srow, scol = cast(ST, src).swizzle(row, col)
src_load = src[*idxs[:-2], sheight, swidth, srow, scol]
if src.dtype.base != dst.dtype.base:
src_load = src_load.cast(dst.dtype.base)
if src.dtype != dst.dtype:
src_load = src_load.cast(dst.dtype)
dst_store = dst[*dst_idxs, height, width, inner].store(src_load)
dst_store = dst_store.end(height, width, inner)
elif dst_dtype.addrspace == AddrSpace.LOCAL and src_dtype.addrspace == AddrSpace.GLOBAL:
elif dst.addrspace == AddrSpace.LOCAL and src.addrspace == AddrSpace.GLOBAL:
srcf = src.flatten()
row_stride = prod(src.shape[axis+1:])
@@ -342,11 +340,11 @@ class Group:
src_i += row * row_stride + col
src_load = srcf[src_i]
if src.dtype.base != dst.dtype.base:
src_load = src_load.cast(dst.dtype.base)
if src.dtype != dst.dtype:
src_load = src_load.cast(dst.dtype)
dst_store = dst[*dst_idxs, height, width, srow, scol].store(src_load)
dst_store = dst_store.end(height, width, outer, inner).barrier()
elif dst_dtype.addrspace == AddrSpace.REG and src_dtype.addrspace == AddrSpace.GLOBAL and isinstance(dst, RT):
elif dst.addrspace == AddrSpace.REG and src.addrspace == AddrSpace.GLOBAL and isinstance(dst, RT):
srcf = src.flatten()
row_stride = prod(src.shape[axis+1:])
@@ -376,10 +374,10 @@ class Group:
src_i += srow * row_stride + scol
src_load = srcf[src_i]
if src.dtype.base != dst.dtype.base:
src_load = src_load.cast(dst.dtype.base)
if src.dtype != dst.dtype:
src_load = src_load.cast(dst.dtype)
dst_store = dst[*dst_idxs, height, width, inner].store(src_load).end(height, width, inner)
elif dst_dtype.addrspace == AddrSpace.REG and src_dtype.addrspace == AddrSpace.GLOBAL and isinstance(dst, RV):
elif dst.addrspace == AddrSpace.REG and src.addrspace == AddrSpace.GLOBAL and isinstance(dst, RV):
srcf = src.flatten()
row_stride = prod(src.shape[axis+1:])
@@ -396,20 +394,18 @@ class Group:
src_i += outer * reductions + (laneid % reductions)
src_load = srcf[src_i]
if src.dtype.base != dst.dtype.base:
src_load = src_load.cast(dst.dtype.base)
if src.dtype != dst.dtype:
src_load = src_load.cast(dst.dtype)
dst_store = dst[outer, 0].store(src_load).end(outer)
else:
raise NotImplementedError(f"load from {src_dtype.addrspace} to {dst_dtype.addrspace} not implemented for {type(dst)=}")
raise NotImplementedError(f"load from {src.addrspace} to {dst.addrspace} not implemented for {type(dst)=}")
self.ker.push_store(dst_store, dst)
return dst.after(dst_store).reshape(dst.shape)
def store(self, dst:ALL_TILES, src:ALL_TILES, idxs:tuple[UOp|int,...]=(), src_idxs:tuple[UOp|int,...]=(), axis:int=0):
dst, src = cast(UOp, dst), cast(UOp, src)
assert isinstance(dst.dtype, PtrDType) and isinstance(src.dtype, PtrDType)
dst_dtype, src_dtype = dst.dtype, src.dtype
if src_dtype.addrspace == AddrSpace.REG and dst_dtype.addrspace == AddrSpace.LOCAL:
if src.addrspace == AddrSpace.REG and dst.addrspace == AddrSpace.LOCAL:
laneid = self.ker.laneid
st, rt = cast(ST, dst), cast(RT, src)
elements_per_thread = rt.base_shape.elements_per_thread
@@ -427,11 +423,11 @@ class Group:
srow, scol = cast(ST, dst).swizzle(row, col)
src_load = src[*src_idxs, height, width, inner]
if src.dtype.base != dst.dtype.base:
src_load = src_load.cast(dst.dtype.base)
if src.dtype != dst.dtype:
src_load = src_load.cast(dst.dtype)
dst_store = dst[*idxs[:-2], height, width, srow, scol].store(src_load)
dst_store = dst_store.end(height, width, inner)
elif src_dtype.addrspace == AddrSpace.REG and dst_dtype.addrspace == AddrSpace.GLOBAL and isinstance(src, RT):
elif src.addrspace == AddrSpace.REG and dst.addrspace == AddrSpace.GLOBAL and isinstance(src, RT):
dstf = dst.flatten()
row_stride = prod(dst.shape[axis+1:])
@@ -461,10 +457,10 @@ class Group:
dst_i += srow * row_stride + scol
src_load = src[*src_idxs, height, width, inner]
if src.dtype.base != dst.dtype.base:
src_load = src_load.cast(dst.dtype.base)
if src.dtype != dst.dtype:
src_load = src_load.cast(dst.dtype)
dst_store = dstf[dst_i].store(src_load).end(height, width, inner)
elif src_dtype.addrspace == AddrSpace.REG and dst_dtype.addrspace == AddrSpace.GLOBAL and isinstance(src, RV):
elif src.addrspace == AddrSpace.REG and dst.addrspace == AddrSpace.GLOBAL and isinstance(src, RV):
dstf = dst.flatten()
row_stride = prod(dst.shape[axis+1:])
@@ -481,11 +477,11 @@ class Group:
dst_i += outer * reductions + (laneid % reductions)
src_load = src[outer, 0]
if src.dtype.base != dst.dtype.base:
src_load = src_load.cast(dst.dtype.base)
if src.dtype != dst.dtype:
src_load = src_load.cast(dst.dtype)
dst_store = dstf[dst_i].store(src_load).end(outer)
else:
raise NotImplementedError(f"store from {src_dtype.addrspace} to {dst_dtype.addrspace} not implemented for {type(src)=}")
raise NotImplementedError(f"store from {src.addrspace} to {dst.addrspace} not implemented for {type(src)=}")
self.ker.push_store(dst_store, dst)
return dst.after(dst_store).reshape(dst.shape)
+2 -2
View File
@@ -3,7 +3,7 @@ import functools
from typing import Callable
from dataclasses import dataclass
from tinygrad.dtype import AddrSpace, DType
from tinygrad.mixin import ElementwiseMixin
from tinygrad.mixin.elementwise import ElementwiseMixin
from tinygrad.uop.ops import UOp, Ops
from extra.thunder.tiny.tk import WARP_THREADS
@@ -209,7 +209,7 @@ class ST:
return cls(uop, rows, cols, layout, base_shape, ker)
def swizzle(self, row, col):
swizzled_offset = self.base_shape.swizzle(row, col, self._uop.dtype.base.scalar())
swizzled_offset = self.base_shape.swizzle(row, col, self._uop.dtype.scalar())
row = swizzled_offset // self.base_shape.cols
col = swizzled_offset % self.base_shape.cols
+8 -6
View File
@@ -1,4 +1,6 @@
from tinygrad.tensor import Tensor
from tinygrad.helpers import CHUNK_SIZE
from tinygrad.nn.state import fs_load
import argparse, math, hashlib
def _python_hash_1mb(data:bytes|bytearray):
@@ -7,15 +9,15 @@ def _python_hash_1mb(data:bytes|bytearray):
return hashlib.shake_128(b''.join(chunk_hashes)).digest(16)
def hash_file(data: bytes|bytearray):
if len(data) % Tensor.CHUNK_SIZE != 0: data += bytes(Tensor.CHUNK_SIZE - len(data) % Tensor.CHUNK_SIZE)
base_chunks = math.ceil(len(data) / Tensor.CHUNK_SIZE)
tree_depth = math.ceil(math.log(base_chunks, Tensor.CHUNK_SIZE // 16))
if len(data) % CHUNK_SIZE != 0: data += bytes(CHUNK_SIZE - len(data) % CHUNK_SIZE)
base_chunks = math.ceil(len(data) / CHUNK_SIZE)
tree_depth = math.ceil(math.log(base_chunks, CHUNK_SIZE // 16))
for _ in range(tree_depth + 1):
data_chunks = [data[i:i+Tensor.CHUNK_SIZE] for i in range(0, len(data), Tensor.CHUNK_SIZE)]
data_chunks = [data[i:i+CHUNK_SIZE] for i in range(0, len(data), CHUNK_SIZE)]
data_chunk_hashes = [_python_hash_1mb(chunk) for chunk in data_chunks]
data = b''.join(data_chunk_hashes)
if len(data) % Tensor.CHUNK_SIZE != 0: data += bytes(Tensor.CHUNK_SIZE - len(data) % Tensor.CHUNK_SIZE)
if len(data) % CHUNK_SIZE != 0: data += bytes(CHUNK_SIZE - len(data) % CHUNK_SIZE)
return data[:16]
@@ -27,7 +29,7 @@ if __name__ == "__main__":
parser.add_argument("--check", action="store_true", help="verify the file hash after fetching")
args = parser.parse_args()
Tensor(bytes.fromhex(args.hash), device="CPU").fs_load(args.len).to(f"disk:{args.dest}").realize()
fs_load(Tensor(bytes.fromhex(args.hash), device="CPU"), args.len).to(f"disk:{args.dest}").realize()
if args.check:
with open(args.dest, "rb") as f:
+3 -2
View File
@@ -3,6 +3,7 @@ from pathlib import Path
from tinygrad.tensor import Tensor
from tinygrad.helpers import tqdm, getenv
from tinygrad.nn.state import fs_load
raid_root = Path(getenv("RAID_ROOT", "/raid"))
@@ -14,7 +15,7 @@ def fetch_file(item):
path.parent.mkdir(parents=True, exist_ok=True)
try:
pt = Tensor(bytes.fromhex(h), device="CPU").fs_load(size).to(f"disk:{path.as_posix()}").realize()
pt = fs_load(Tensor(bytes.fromhex(h), device="CPU"), size).to(f"disk:{path.as_posix()}").realize()
except Exception as e:
print(f"error fetching {path}, {h}, {size}: {e}")
raise
@@ -22,7 +23,7 @@ def fetch_file(item):
pt.uop.buffer.deallocate()
def fetch_mapping(h, l):
mapping_tensor = Tensor(bytes.fromhex(h)).fs_load(l).realize()
mapping_tensor = fs_load(Tensor(bytes.fromhex(h)), l).realize()
mapping = mapping_tensor.data().tobytes().decode()
mapping = json.loads(mapping)
mapped_files = mapping.items()
+3 -2
View File
@@ -3,12 +3,13 @@ import multiprocessing, json
from tinygrad.tensor import Tensor
from tinygrad.helpers import tqdm
from tinygrad.nn.state import fs_store
raid_root = Path("/raid")
def upload_file(path: Path):
pt = Tensor(path).realize()
h = pt.fs_store().realize()
h = fs_store(pt).realize()
pt.uop.realized.deallocate()
return h.data().hex(), path, pt.nbytes()
@@ -26,6 +27,6 @@ if __name__ == "__main__":
mapping = json.dumps(mapping).encode()
mapping_tensor = Tensor(mapping, device="CPU")
h = mapping_tensor.fs_store().realize()
h = fs_store(mapping_tensor).realize()
print(f"final hash: {h.data().hex()}, size: {len(mapping)}")
+1
View File
@@ -111,6 +111,7 @@ docs = [
"black",
"numpy",
]
mesa = ["tinymesa==25.2.7.2"]
[tool.mutmut]
BIN
View File
Binary file not shown.
+7 -7
View File
@@ -73,7 +73,7 @@ All nodes in the tinygrad graph are \textbf{UOps}. A UOp is a tuple $(\mathrm{op
\op{Permute} & $(T,)$ & axis order $\pi$ & Reorder axes. $\pi = (1,0)$ is transpose. \\
\op{Flip} & $(T,)$ & bools $\mathbf{f}$ & Reverse along flagged axes. \\
\op{Reshape} & $(T, \mathbf{s'})$ & --- & Reinterpret in row-major order. $\prod s_k = \prod s'_k$. \\
\op{Expand} & $(T, \mathbf{s'})$ & --- & Broadcast size-1 axes. $s_k \in \{1, s'_k\}$. \\
\op{Expand} & $(T, \mathbf{n})$ & --- & Prepend axes $\mathbf{n}$ on the left. Output shape is $\mathbf{n} + T.\mathrm{shape}$. \\
\op{Pad} & $(T, \mathbf{o}, \mathbf{s'})$ & --- & Place $T$ at offset $o_k$ in an invalid-filled output of shape $s'_k$. \\
\op{Shrink} & $(T, \mathbf{o}, \mathbf{s'})$ & --- & Keep $s'_k$ elements starting at offset $o_k$ per axis. Inverse of \op{Pad}. \\
\op{Index} & $(T, i_0, i_1, \ldots)$ & --- & Index from left. $()$-shaped $i$ removes dim; $(k,)$-shaped makes it $k$. \\
@@ -83,13 +83,13 @@ All nodes in the tinygrad graph are \textbf{UOps}. A UOp is a tuple $(\mathrm{op
\end{tabular}
%% ============================================================
\subsection*{{\color{reducered}Reduce Ops} \normalfont\small--- collapse axes to size $1$}
\subsection*{{\color{reducered}Reduce Ops} \normalfont\small--- remove axes}
\begin{tabular}{@{}l l l l@{}}
\toprule
\textbf{Op} & \textbf{src} & \textbf{arg} & \textbf{Semantics} \\
\midrule
\op{Reduce} & ($T$, $r_0$, $r_1$, \ldots) & op, axes & Reduce $T$ along axes or ranges. Op is \op{Add}, \op{Max}, or \op{Mul}. \\
\op{Reduce} & ($T$, $r_0$, $r_1$, \ldots) & op, $n$ & Reduce the first $n$ axes of $T$. Op is \op{Add}, \op{Max}, or \op{Mul}. \\
\bottomrule
\end{tabular}
@@ -227,7 +227,7 @@ Ternary & $(P, A, B)$
\midrule
\op{Barrier} & (deps\ldots) & --- & Synchronize threads within a workgroup. \\
\op{Ins} & \ldots & \ldots & A single machine instruction (e.g.\ AMD ISA). \\
\op{GetAddr} & (buf, dev) & --- & Lower buf to its address on device dev. \\
\op{GetAddr} & (buf,) & dev & Lower buf to its address on device dev. \\
\op{Special} & (bound,) & name & GPU thread/workgroup index (e.g.\ \texttt{gidx0}, \texttt{lidx1}). \\
\op{If} & (gate,) & --- & Begin conditional execution block. \\
\op{Endif} & (if,) & --- & End conditional execution block. \\
@@ -258,7 +258,7 @@ Every UOp has a \textbf{dtype}, \textbf{shape}, \textbf{device}, \textbf{addrspa
\op{Const} & from arg & $()$ & \textsc{null} & $[v, v]$ \\
\op{Param} & from arg & from $\mathrm{src}[0]$ & from arg & from src or dtype range \\[3pt]
Movement ops & $\mathrm{src}[0].\mathrm{dtype}$ & (see op) & $\mathrm{src}[0].\mathrm{device}$ & $\mathrm{src}[0]$ \\
\op{Reduce} & $\mathrm{src}[0].\mathrm{dtype}$ & collapse axes to $1$ & $\mathrm{src}[0].\mathrm{device}$ & dtype range \\[3pt]
\op{Reduce} & $\mathrm{src}[0].\mathrm{dtype}$ & remove first $n$ axes & $\mathrm{src}[0].\mathrm{device}$ & dtype range \\[3pt]
\op{Cast} & from arg & $\mathrm{src}[0].\mathrm{shape}$ & $\mathrm{src}[0].\mathrm{device}$ & clamped to dtype \\
\op{Bitcast} & from arg & $\mathrm{src}[0].\mathrm{shape}$ & $\mathrm{src}[0].\mathrm{device}$ & dtype range \\
\op{Copy} & $\mathrm{src}[0].\mathrm{dtype}$ & $\mathrm{src}[0].\mathrm{shape}$ & from arg & $\mathrm{src}[0]$ \\
@@ -284,8 +284,8 @@ Default \emph{dtype range}: $[\mathrm{dtype\_min},\, \mathrm{dtype\_max}]$.
\medskip
\textbf{axis} tracks the multi-device sharding dimension. \op{Buffer} with $n$-tuple device: axis $= 0$ (device dim).
\op{Reshape} remaps axis to preserve the shard boundary. \op{Permute} follows the permutation.
\op{Reduce} on the shard axis $\to$ \textsc{null}. \op{Replicated} on the shard axis $\to$ \textsc{null}. \op{Copy} $\to$ \textsc{null}. ALU ops inherit from sources. Default: \textsc{null}.
\op{Reshape} remaps axis to preserve the shard boundary. \op{Permute} follows the permutation. \op{Expand} shifts axis right by $|\mathbf{n}|$.
\op{Reduce} on the shard axis $\to$ \textsc{null} (shard axis is among the first $n$ axes). \op{Replicated} on the shard axis $\to$ \textsc{null}. \op{Copy} $\to$ \textsc{null}. ALU ops inherit from sources. Default: \textsc{null}.
%% ============================================================
\subsection*{Kernel Optimizations (OptOps) \normalfont\small--- schedule-level transforms on kernel ranges}
+3 -3
View File
@@ -16,7 +16,7 @@ from extra.gemm.amd_asm_matmul import Kernel
def custom_add_one(A:UOp) -> UOp:
A = A.flatten()
assert dtypes.is_float(A.dtype.base), f"buffer dtype must be float32, got {A.dtype}"
assert dtypes.is_float(A.dtype), f"buffer dtype must be float32, got {A.dtype}"
threads = UOp.special(A.numel(), "lidx0")
insts = [
s_load_b64(s[0:1], s[0:1], soffset=NULL),
@@ -34,9 +34,9 @@ def custom_add_one(A:UOp) -> UOp:
def custom_add_var(A:UOp, B:UOp) -> UOp:
A,B = A.flatten(), B.flatten()
assert A.dtype.base == dtypes.uint32, f"buffer dtype must be uint32, got {A.dtype}"
assert A.dtype == dtypes.uint32, f"buffer dtype must be uint32, got {A.dtype}"
threads = UOp.special(A.numel(), "lidx0")
var = UOp.param(2, dtypes.weakint, vmin_vmax=(0, 10), name="var", addrspace=AddrSpace.ALU)
var = UOp.param(2, dtypes.index, vmin_vmax=(0, 10), name="var", addrspace=AddrSpace.ALU)
insts = [
s_load_b128(s[4:7], s[0:1]),
s_load_b32(s[8], s[0:1], offset=0x10), # all threads load the same variable
+5 -5
View File
@@ -167,7 +167,7 @@ class TestDSPcodePatterns(unittest.TestCase):
def test_global_atomic_add_f32_parsing(self):
"""Test GLOBAL_ATOMIC_ADD_F32 keeps memory values in float dtype."""
vmem = UOp.param(2, dtypes.uint32.ptr(1024))
vmem = UOp.param(2, dtypes.uint32, (1024,))
srcs = {
'ADDR': UOp.const(dtypes.uint64, 0),
'DATA': UOp.const(dtypes.uint32, 0x3f800000),
@@ -198,7 +198,7 @@ class TestDSPcodePatterns(unittest.TestCase):
def test_mem_read_parsing(self):
"""Test MEM[addr].type read expression parsing."""
# Create a mock LDS buffer
lds = UOp.param(3, dtypes.uint32.ptr(16384))
lds = UOp.param(3, dtypes.uint32, (16384,))
addr = UOp.const(dtypes.uint32, 0)
vrs = {'_lds': lds, 'ADDR': addr, 'OFFSET': UOp.const(dtypes.uint32, 0)}
@@ -233,7 +233,7 @@ class TestDSPcodePatterns(unittest.TestCase):
pcode = PCODE.get(DSOp.DS_LOAD_2ADDR_B32)
self.assertIsNotNone(pcode)
assert pcode is not None
lds = UOp.param(3, dtypes.uint32.ptr(16384))
lds = UOp.param(3, dtypes.uint32, (16384,))
srcs = {
'ADDR': UOp.const(dtypes.uint32, 0),
'OFFSET0': UOp.const(dtypes.uint32, 0),
@@ -314,7 +314,7 @@ class TestConcatWidthParsing(unittest.TestCase):
self.assertEqual(parsed.simplify().arg, expected)
def test_permlane64_wave64_pcode_indices(self):
vgpr = UOp.param(0, dtypes.uint32.ptr(256))
vgpr = UOp.param(0, dtypes.uint32, (256,))
srcs = {
'SRC0': UOp.const(dtypes.uint32, 0),
'VDST': UOp.const(dtypes.uint32, 1),
@@ -347,7 +347,7 @@ class TestAllPcode(unittest.TestCase):
def _make_srcs(self):
"""Create dummy source variables for pcode parsing."""
u32, u64 = lambda v=0: UOp.const(dtypes.uint32, v), lambda v=0: UOp.const(dtypes.uint64, v)
lds = UOp.param(3, dtypes.uint32.ptr(16384))
lds = UOp.param(3, dtypes.uint32, (16384,))
return {'laneId': u32(), 'laneID': u32(), 'S0': u32(), 'S1': u32(), 'S2': u32(), 'S3': u32(), 'SRC0': u32(),
'D0': u32(), 'D1': u32(), 'DST': u32(), 'VDST': u32(), 'SDST': u32(),
'VCC': u64(), 'VCCZ': u32(), 'EXEC': u64(), 'EXEC_LO': u32(), 'EXECZ': u32(), 'SCC': u32(),
+1
View File
@@ -88,6 +88,7 @@ def run_rocprof_decoder(blobs: list[bytes], lib: bytes, base: int, target: str):
if t.is_alive(): raise RuntimeError("rocprof decoder timeout")
return occupancy_records, wave_insts
@unittest.skip("TODO: fix to not require unpickling UOps.")
class SQTTExamplesTestBase(unittest.TestCase):
target: str
examples: dict
+1
View File
@@ -64,6 +64,7 @@ def rocprof_inst_traces_match(sqtt, prg, target):
return passed_insts, len(rwaves), len(rwaves_iter)
@unittest.skip("TODO: fix to not require unpickling UOps.")
class TestSQTTMapBase(unittest.TestCase):
target: str
examples: dict
+21
View File
@@ -115,6 +115,7 @@ class TestIndexing(unittest.TestCase):
@unittest.skip("not ready")
def test_index_fused_opt(self): self.test_index_fused(0)
@unittest.skipIf(Device.DEFAULT == "CL", "rusticl/llvmpipe bug: https://gitlab.freedesktop.org/mesa/mesa/-/work_items/15667")
def test_index_fused_out_of_bounds(self):
dataset = Tensor.rand(256, 256).realize()
idxs = Tensor([-19238, -257, 256, 495, 10982377]).realize()
@@ -187,6 +188,26 @@ class TestIndexing(unittest.TestCase):
for i in idx.flatten().numpy(): expected_grad[i] += 2
np.testing.assert_allclose(emb.weight.grad.numpy(), expected_grad, rtol=1e-5, atol=1e-5)
@unittest.skipIf(Device.DEFAULT not in ("CPU", "AMD"), "atomics only on AMD/CPU")
@Context(USE_ATOMICS=1, SPEC=1)
def test_embedding_backward_padded_embed(self):
from tinygrad.renderer.cstyle import CStyleLanguage
if Device.DEFAULT == "CPU" and not isinstance(Device["CPU"].renderer, CStyleLanguage): self.skipTest("CPU needs Clang renderer")
vocab_size, embed_size = 1000, 300
bs, seqlen = 4, 256
idx = Tensor.randint(bs, seqlen, high=vocab_size)
emb = nn.Embedding(vocab_size, embed_size)
emb.weight = Tensor.ones(vocab_size, embed_size)
gt = Tensor.zeros(bs, seqlen, embed_size)
Tensor.realize(idx, emb.weight, gt)
loss = (emb(idx)-gt).square().sum()
loss.backward()
emb.weight.grad.realize()
# correctness check
expected_grad = np.zeros((vocab_size, embed_size), dtype=np.float32)
for i in idx.flatten().numpy(): expected_grad[i] += 2
np.testing.assert_allclose(emb.weight.grad.numpy(), expected_grad, rtol=1e-5, atol=1e-5)
@needs_second_gpu
@unittest.skipIf(Device.DEFAULT not in ("CPU", "AMD"), "atomics only on AMD/CPU")
@Context(USE_ATOMICS=1, SPEC=1)
+52 -70
View File
@@ -9,7 +9,12 @@ from examples.mlperf.models.flat_llama import FP8_DTYPE, quantize_fp8, FP8_MAX
# Use DEV=NULL:HIP:gfx950 to also test the assembly
def is_cdna4(): return Device[Device.DEFAULT].renderer.target.arch.startswith("gfx950")
def run_asm_gemm(a_shape, b_shape, dtype=dtypes.float16, a_shard=None, b_shard=None, gpus:int=1) -> None:
def has_hipcc():
try: system("hipcc --version")
except Exception: return False
return True
def run_asm_gemm(a_shape, b_shape, dtype=dtypes.bfloat16, a_shard=None, b_shard=None, gpus:int=1) -> None:
Tensor.manual_seed(0)
input_dtype = dtypes.bfloat16 if dtype == FP8_DTYPE else dtype
a_rand = Tensor.randn(a_shape, dtype=dtypes.float).sub(0.5).cast(input_dtype)
@@ -20,8 +25,10 @@ def run_asm_gemm(a_shape, b_shape, dtype=dtypes.float16, a_shard=None, b_shard=N
devs = tuple(f"{Device.DEFAULT}:{i}" for i in range(gpus)) if (multi:=gpus>1) else None
if dtype == FP8_DTYPE:
a_rand, x_scale, _ = quantize_fp8(a_rand)
b_rand, w_scale, _ = quantize_fp8(b_rand)
x_scale = Tensor.full((), FP8_MAX, dtype=dtypes.float32, device=devs).contiguous()
a_rand, _, _ = quantize_fp8(a_rand.shard(devs, axis=a_shard) if multi else a_rand, amax_state=x_scale)
b_rand, w_scale, _ = quantize_fp8(b_rand.T.contiguous())
if multi: b_rand, w_scale = b_rand.shard(devs, axis=None if b_shard is None else 1-b_shard), w_scale.to(devs).contiguous()
grad_amax_state = Tensor.full((), FP8_MAX, dtype=dtypes.float32, device=devs).contiguous()
with Context(DEBUG=0):
Tensor.realize(a_rand, x_scale, b_rand, w_scale, grad_amax_state)
@@ -32,24 +39,24 @@ def run_asm_gemm(a_shape, b_shape, dtype=dtypes.float16, a_shard=None, b_shard=N
a_ref, b_ref = a_rand.detach().cast(dtypes.bfloat16), b_rand.detach().cast(dtypes.bfloat16)
else:
a_ref, b_ref = a_rand.clone(), b_rand.clone()
if multi: a, b = a.shard(devs, axis=a_shard), b.shard(devs, axis=b_shard)
if multi and isinstance(a.device, str): a, b = a.shard(devs, axis=a_shard), b.shard(devs, axis=b_shard)
if dtype == FP8_DTYPE:
tst = asm_gemm(a, b, x_scale=x_scale, w_scale=w_scale, grad_amax_state=grad_amax_state)
tst = asm_gemm(a, b.T, x_scale=x_scale, w_scale=w_scale, grad_amax_state=grad_amax_state)
else:
tst = asm_gemm(a, b)
tst.sum().backward()
Tensor.realize(tst, a.grad, b.grad)
if multi: a_ref, b_ref = a_ref.shard(devs, axis=a_shard), b_ref.shard(devs, axis=b_shard)
if multi and isinstance(a_ref.device, str): a_ref, b_ref = a_ref.shard(devs, axis=a_shard), b_ref.shard(devs, axis=b_shard)
if dtype == FP8_DTYPE:
ref = ((a_ref @ b_ref) * x_scale * w_scale).cast(dtypes.bfloat16)
ref = ((a_ref @ b_ref.T) * ((x_scale.float() + 1e-8) / FP8_MAX) * w_scale).cast(dtypes.bfloat16)
else:
ref = a_ref @ b_ref
ref.sum().backward()
Tensor.realize(ref, a_ref.grad, b_ref.grad)
# no validation on the NULL device
if a_rand.device.startswith("NULL"): return None
if Device.DEFAULT.startswith("NULL"): return None
atol, rtol = (2e-1, 1e-2) if dtype == dtypes.bfloat16 else (256, 1e-2) if dtype == FP8_DTYPE else (1e-2, 1e-3)
# allow more rtol for multi because of ALLREDUCE_CAST
grad_atol, grad_rtol = (16895, 0.125) if dtype == FP8_DTYPE else (atol, 2e-2 if multi else rtol)
@@ -64,31 +71,31 @@ def run_asm_gemm(a_shape, b_shape, dtype=dtypes.float16, a_shard=None, b_shard=N
assert a.grad.allclose(a_ref.grad, atol=grad_atol, rtol=grad_rtol).item(), "grad_a mismatch"
assert b.grad.allclose(b_ref.grad, atol=grad_atol, rtol=grad_rtol).item(), "grad_b mismatch"
def verify_asm_gemm(batch:int, M:int, N:int, K:int, dtype=dtypes.float16, gpus:int=1) -> None:
def verify_asm_gemm(batch:int, M:int, N:int, K:int, dtype=dtypes.bfloat16, gpus:int=1) -> None:
run_asm_gemm((batch, M, K), (K, N), dtype=dtype, a_shard=0, b_shard=None, gpus=gpus)
def verify_asm_gemm_k_sharded(M:int, N:int, K:int, dtype=dtypes.float16, gpus:int=8) -> None:
def verify_asm_gemm_k_sharded(M:int, N:int, K:int, dtype=dtypes.bfloat16, gpus:int=8) -> None:
run_asm_gemm((M, K), (K, N), dtype=dtype, a_shard=1, b_shard=0, gpus=gpus)
def verify_asm_gemm_n_sharded(batch:int, M:int, N:int, K:int, dtype=dtypes.float16, gpus:int=2) -> None:
def verify_asm_gemm_n_sharded(batch:int, M:int, N:int, K:int, dtype=dtypes.bfloat16, gpus:int=2) -> None:
run_asm_gemm((batch, M, K), (K, N), dtype=dtype, a_shard=None, b_shard=1, gpus=gpus)
def verify_asm_gemm_m_sharded(M:int, N:int, K:int, dtype=dtypes.float16, gpus:int=2) -> None:
def verify_asm_gemm_m_sharded(M:int, N:int, K:int, dtype=dtypes.bfloat16, gpus:int=2) -> None:
run_asm_gemm((M, K), (K, N), dtype=dtype, a_shard=0, b_shard=None, gpus=gpus)
def verify_asm_gemm_n_sharded_2d(M:int, N:int, K:int, dtype=dtypes.float16, gpus:int=2) -> None:
def verify_asm_gemm_n_sharded_2d(M:int, N:int, K:int, dtype=dtypes.bfloat16, gpus:int=2) -> None:
run_asm_gemm((M, K), (K, N), dtype=dtype, a_shard=None, b_shard=1, gpus=gpus)
def verify_asm_gemm_k_sharded_3d(batch:int, M:int, N:int, K:int, dtype=dtypes.float16, gpus:int=2) -> None:
def verify_asm_gemm_k_sharded_3d(batch:int, M:int, N:int, K:int, dtype=dtypes.bfloat16, gpus:int=2) -> None:
run_asm_gemm((batch, M, K), (K, N), dtype=dtype, a_shard=2, b_shard=0, gpus=gpus)
# 128x smaller than usual
# uses the UOp GEMM, runs on non CDNA4 and CI
@unittest.skipUnless(dtypes.half in Device[Device.DEFAULT].renderer.supported_dtypes(), "need half")
@unittest.skipUnless(dtypes.bfloat16 in Device[Device.DEFAULT].renderer.supported_dtypes(), "need half")
class TestGemm(unittest.TestCase):
def setUp(self):
if is_cdna4(): self.skipTest("shapes are too small for the assembly GEMM")
def test_simple(self): verify_asm_gemm(1, N:=getenv("N", 32), N, N, dtype=dtypes.half)
def test_simple(self): verify_asm_gemm(1, N:=getenv("N", 32), N, N, dtype=dtypes.bfloat16)
def test_gemm(self): verify_asm_gemm(1, 64, 32, 112)
def test_gemm_batched(self): verify_asm_gemm(2, 64, 32, 32)
@needs_second_gpu
@@ -107,24 +114,25 @@ class TestGemm(unittest.TestCase):
# uses the smallest size for the cdna assembly gemm
class TestAsmGEMM(unittest.TestCase):
def setUp(self):
if not is_cdna4():
if not is_cdna4() or not has_hipcc():
self.skipTest("assembly gemm is only for cdna4")
def test_tiny(self): verify_asm_gemm(1, 256, 256, 64)
def test_tiny(self): verify_asm_gemm(1, 256, 256, 256)
def test_verify_with_numpy(self):
import numpy as np
M, N, K = 256, 256, 64
M, N, K = 256, 256, 256
rng = np.random.default_rng(0)
a_np = (rng.random((M, K), dtype=np.float32) - 0.5).astype(np.half)
b_np = (rng.random((K, N), dtype=np.float32) - 0.5).astype(np.half)
c_np = a_np @ b_np
a, b = Tensor(a_np), Tensor(b_np)
a_np = (rng.random((M, K), dtype=np.float32) - 0.5).astype(np.float32)
b_np = (rng.random((K, N), dtype=np.float32) - 0.5).astype(np.float32)
c_np = (a_np.astype(np.float32) @ b_np.astype(np.float32)).astype(np.float32)
Tensor.manual_seed(0)
a, b = Tensor(a_np).cast(dtypes.bfloat16), Tensor(b_np).cast(dtypes.bfloat16)
c = asm_gemm(a, b)
c.realize()
# no validation on the NULL device
if a.device.startswith("NULL"): return None
np.testing.assert_allclose(c.numpy(), c_np, atol=2e-3, rtol=5e-2)
np.testing.assert_allclose(c.numpy(), c_np, atol=2e-1, rtol=1e-2)
def test_unsupported_batch(self):
with self.assertRaisesRegex(AssertionError, "batch size"):
@@ -141,8 +149,10 @@ class TestAsmGEMM(unittest.TestCase):
verify_asm_gemm(1, 256, 1000, 256)
# test the Asm GEMM with Llama shapes, only run on the real machine for speed
@unittest.skipUnless(has_hipcc(), "requires hipcc to compile")
class TestGemmLlama(unittest.TestCase):
dtype = dtypes.bfloat16
dtype = FP8_DTYPE
def setUp(self):
if not is_cdna4() or DEV.interface.startswith("MOCK"):
@@ -172,18 +182,16 @@ class TestGemmLlama(unittest.TestCase):
def test_gemm_batched(self): verify_asm_gemm(2, 8192, 4096, 4096, dtype=self.dtype)
def test_gemm1(self): verify_asm_gemm(8, 8192, 4096, 14336, dtype=self.dtype, gpus=8)
@unittest.skip("disabled, asm in this shape is slower than tinygrad")
def test_gemm2(self): verify_asm_gemm(8, 8192, 128256, 4096, dtype=self.dtype, gpus=8)
def test_gemm3(self): verify_asm_gemm(8, 8192, 14336, 4096, dtype=self.dtype, gpus=8)
def test_gemm4(self): verify_asm_gemm(8, 4096, 14336, 4096, dtype=self.dtype, gpus=8)
def test_gemm5(self): verify_asm_gemm(8, 4096, 4096, 14336, dtype=self.dtype, gpus=8)
def test_gemm6(self): verify_asm_gemm(16, 4096, 4096, 14336, dtype=self.dtype, gpus=8)
@unittest.skip("disabled, asm in this shape is slower than tinygrad")
def test_gemm7(self): verify_asm_gemm(1, 8192, 128256, 4096, dtype=self.dtype)
def test_gemm8(self): verify_asm_gemm(1, 4096, 14336, 8192, dtype=self.dtype)
def test_gemm9(self): verify_asm_gemm(8, 4096, 14336, 8192, dtype=self.dtype, gpus=8)
def test_gemm10(self): verify_asm_gemm(1, 4096, 8192, 4096, dtype=self.dtype)
def test_gemm_previously_unsupported(self): verify_asm_gemm(8, 1024, 1024, 4096, gpus=8)
def test_gemm11(self): verify_asm_gemm(8, 1024, 1024, 4096, dtype=self.dtype, gpus=8)
def test_k_sharded_1(self): verify_asm_gemm_k_sharded(14336, 4096, 8*8192, dtype=self.dtype, gpus=8)
def test_k_sharded_2(self): verify_asm_gemm_k_sharded(4096, 14336, 8*8192, dtype=self.dtype, gpus=8)
def test_k_sharded_3(self): verify_asm_gemm_k_sharded(4096, 4096, 8*8192, dtype=self.dtype, gpus=8)
@@ -203,33 +211,25 @@ class TestGemmLlama(unittest.TestCase):
def test_tp_k_sharded_w2(self): verify_asm_gemm_k_sharded_3d(1, 8192, 4096, 14336, dtype=self.dtype, gpus=8)
# more shapes: vary M, N, K independently
def test_shape_small_square(self): verify_asm_gemm(1, 256, 256, 256)
def test_shape_small_rect_m(self): verify_asm_gemm(1, 512, 256, 256)
def test_shape_small_rect_n(self): verify_asm_gemm(1, 256, 512, 256)
def test_shape_small_rect_k(self): verify_asm_gemm(1, 256, 256, 512)
def test_shape_tall(self): verify_asm_gemm(1, 2048, 256, 256)
def test_shape_wide(self): verify_asm_gemm(1, 256, 2048, 256)
def test_shape_deep(self): verify_asm_gemm(1, 256, 256, 4096)
def test_shape_non_square(self): verify_asm_gemm(1, 1024, 2048, 512)
def test_shape_batched_small(self): verify_asm_gemm(2, 256, 256, 256)
def test_shape_batched_rect(self): verify_asm_gemm(2, 512, 1024, 256)
# K edge cases: iters=1,2,3 exercise different loop paths
def test_shape_k64(self): verify_asm_gemm(1, 256, 256, 64)
def test_shape_k128(self): verify_asm_gemm(1, 256, 256, 128)
def test_shape_k192(self): verify_asm_gemm(1, 256, 256, 192)
def test_shape_small_square(self): verify_asm_gemm(1, 256, 256, 256, dtype=self.dtype)
def test_shape_small_rect_m(self): verify_asm_gemm(1, 512, 256, 256, dtype=self.dtype)
def test_shape_small_rect_n(self): verify_asm_gemm(1, 256, 512, 256, dtype=self.dtype)
def test_shape_small_rect_k(self): verify_asm_gemm(1, 256, 256, 512, dtype=self.dtype)
def test_shape_tall(self): verify_asm_gemm(1, 2048, 256, 256, dtype=self.dtype)
def test_shape_wide(self): verify_asm_gemm(1, 256, 2048, 256, dtype=self.dtype)
def test_shape_deep(self): verify_asm_gemm(1, 256, 256, 4096, dtype=self.dtype)
def test_shape_non_square(self): verify_asm_gemm(1, 1024, 2048, 512, dtype=self.dtype)
def test_shape_batched_small(self): verify_asm_gemm(2, 256, 256, 256, dtype=self.dtype)
def test_shape_batched_rect(self): verify_asm_gemm(2, 512, 1024, 256, dtype=self.dtype)
# K edge cases: change iters to exercise different loop paths, k big enough for hk kernel
def test_shape_k256(self): verify_asm_gemm(1, 256, 256, 256, dtype=self.dtype)
def test_shape_k512(self): verify_asm_gemm(1, 256, 256, 512, dtype=self.dtype)
def test_shape_k768(self): verify_asm_gemm(1, 256, 256, 768, dtype=self.dtype)
def test_llama3_out1(self): verify_asm_gemm(1, 8192, 128256, 4096, dtype=self.dtype)
def test_llama3_out2(self): verify_asm_gemm(1, 8192, 4096, 128256, dtype=self.dtype)
def test_llama3_out3(self): verify_asm_gemm(1, 4096, 128256, 8192, dtype=self.dtype)
def has_hipcc():
try: system("hipcc --version")
except Exception: return False
return True
@unittest.skipUnless(has_hipcc(), "FP8 gemm requires hipcc to compile")
class TestGemmLlamaFP8(TestGemmLlama): dtype = FP8_DTYPE
# mxfp8: 1x32 block scaling along K, e8m0 scales packed iteration-major (K/128, dim) uint32
def quantize_mxfp8(x:Tensor) -> tuple[Tensor, Tensor, Tensor]:
rows, K = x.shape
@@ -327,7 +327,7 @@ def run_mx_prequant(M:int, N:int, K:int) -> None:
err = ((t.float() - r.float()).abs().mean() / (r.float().abs().mean() + 1e-8)).item()
assert err < 6e-2, f"{name} prequant vs analytic rel err {err}"
@unittest.skipUnless(has_hipcc(), "MXFP8 gemm requires hipcc to compile")
@unittest.skipUnless(has_hipcc(), "requires hipcc to compile")
class TestGemmMXFP8(unittest.TestCase):
def setUp(self):
if not is_cdna4() or DEV.interface.startswith("MOCK"): self.skipTest("mxfp8 gemm is only for cdna4")
@@ -368,7 +368,7 @@ def run_atb_gemm(rows, M, N, a_shard=None, b_shard=None, gpus=1, atol=1.0, rtol=
out = hk_bf16_atb_gemm(a, b)
np.testing.assert_allclose(out.float().numpy(), ref.numpy(), atol=atol, rtol=rtol)
@unittest.skipUnless(has_hipcc(), "MXFP8 gemm requires hipcc to compile")
@unittest.skipUnless(has_hipcc(), "requires hipcc to compile")
class TestHkBf16AtbGemm(unittest.TestCase):
def setUp(self):
if not is_cdna4(): self.skipTest("hk bf16 atb gemm is cdna4 only")
@@ -380,23 +380,5 @@ class TestHkBf16AtbGemm(unittest.TestCase):
@needs_second_gpu
def test_m_sharded(self): run_atb_gemm(256, 512, 256, a_shard=2, b_shard=None, gpus=2)
class TestMagicGu(unittest.TestCase):
def test_magicgu_matches_old(self):
from extra.gemm.cdna_asm_gemm import _magicgu_mulhi, TILE_M, TILE_N, TILE_K
old_iters_args = {64: (67108864, 0), 128: (33554432, 0), 224: (613566757, 2147483656)}
old_gemm_shapes = [
(8192, 4096, 4096), (8192, 14336, 4096), (8192, 4096, 14336),
(8192, 8192, 8192), (4096, 4096, 4096), (4096, 14336, 4096),
(4096, 14336, 8192), (4096, 4096, 14336), (14336, 4096, 8192),
(4096, 8192, 14336), (4096, 4096, 8192), (4096, 8192, 4096),
]
for M, N, K in old_gemm_shapes:
iters = K // TILE_K
total = (M // TILE_M) * (N // TILE_N) * iters
for batch in [1, 2]:
magic, shift = _magicgu_mulhi(iters, total * batch)
old_magic, old_shift = old_iters_args[iters]
self.assertEqual((magic, shift), (old_magic, old_shift), f"mismatch for ({M},{N},{K}) batch={batch} iters={iters}")
if __name__ == "__main__":
unittest.main()
+3 -1
View File
@@ -2,6 +2,7 @@ import unittest, math
from tinygrad import Tensor, Device, dtypes
from tinygrad.dtype import DTYPES_DICT
from tinygrad.uop.ops import Ops, UOp
from tinygrad.codegen.decomp.op import threefry2x32
import numpy as np
from test.helpers import not_support_multi_device
@@ -167,7 +168,8 @@ class TestMultiConstFolding(unittest.TestCase):
class TestThreefryConstFolding(unittest.TestCase):
def test_threefry(self):
x = UOp.const(dtypes.uint64, 5).threefry(UOp.const(dtypes.uint64, 10))
# THREEFRY(const,const) folds to a const once decomposed
x = threefry2x32(UOp.const(dtypes.uint64, 5), UOp.const(dtypes.uint64, 10))
self.assertIs(x.simplify().op, Ops.CONST)
class TestTautologicalCompare(unittest.TestCase):
+6 -6
View File
@@ -7,12 +7,12 @@ from tinygrad.uop.ops import KernelInfo, AxisType, Ops
def custom_arange_kernel(C:UOp) -> UOp:
i = UOp.range(C.shape[0], 0)
return C[i].store(i.cast(C.dtype.base)).end(i).sink(arg=KernelInfo(name=f"custom_arange_{C.shape[0]}"))
return C[i].store(i.cast(C.dtype)).end(i).sink(arg=KernelInfo(name=f"custom_arange_{C.shape[0]}"))
def custom_eye_kernel(C:UOp) -> UOp:
i = UOp.range(C.shape[0], 0)
j = UOp.range(C.shape[1], 1)
return C[i, j].store((i.eq(j)).cast(C.dtype.base)).end(i, j).sink(arg=KernelInfo(name=f"custom_eye_{C.numel()}"))
return C[i, j].store((i.eq(j)).cast(C.dtype)).end(i, j).sink(arg=KernelInfo(name=f"custom_eye_{C.numel()}"))
def custom_add_one_kernel(B:UOp, A:UOp) -> UOp:
A,B = A.flatten(), B.flatten()
@@ -51,13 +51,13 @@ def flip_contract_kernel(dest:UOp, src:UOp):
i = UOp.range(dest.shape[0], 0)
j = UOp.range(dest.shape[1], 1, AxisType.UPCAST)
vec = src[i, j].contract(j)
store = UOp.group(*[dest[i, k].store(vec.gep(3-k)) for k in range(4)])
store = UOp.group(*[dest[i, k].store(vec.index(3-k)) for k in range(4)])
return store.end(i, j).sink(arg=KernelInfo(name=f"flip_contract_{dest.numel()}", opts_to_apply=()))
def slice_sum_kernel(dest:UOp, src:UOp):
G = UOp.range(src.shape[0], 0)
slice_src = src[G, :]
reg = UOp.placeholder((1,), dest.dtype.base, 0, addrspace=AddrSpace.REG)
reg = UOp.placeholder((1,), dest.dtype, 0, addrspace=AddrSpace.REG)
reg = reg.after(G)[0].set(0)
R = UOp.range(src.shape[1], 1, AxisType.REDUCE)
reg = reg[0].set(reg.after(R)[0] + slice_src[R], end=R)
@@ -73,12 +73,12 @@ def simple_qkv_kernel(O:UOp, Q:UOp, K:UOp, V:UOp) -> UOp:
j = UOp.range(N, 2, axis_type=AxisType.REDUCE)
k_inner = UOp.range(d, 3, axis_type=AxisType.REDUCE)
qk_acc = UOp.placeholder((1,), Q.dtype.base, 0, addrspace=AddrSpace.REG)
qk_acc = UOp.placeholder((1,), Q.dtype, 0, addrspace=AddrSpace.REG)
qk_acc = qk_acc.after(i, j)[0].set(0.0)
qk_acc = qk_acc[0].set(qk_acc.after(k_inner)[0] + Q[i, k_inner] * K[j, k_inner], end=k_inner)
qk_score = qk_acc[0] / (d ** 0.5)
out_acc = UOp.placeholder((1,), Q.dtype.base, 1, addrspace=AddrSpace.REG)
out_acc = UOp.placeholder((1,), Q.dtype, 1, addrspace=AddrSpace.REG)
out_acc = out_acc.after(i, d_out)[0].set(0.0)
out_acc = out_acc[0].set(out_acc.after(j)[0] + qk_score * V[j, d_out], end=j)
+1 -1
View File
@@ -281,7 +281,7 @@ class TestBitCast(unittest.TestCase):
def test_shape_change_bitcast_exceptions(self):
with self.assertRaises(RuntimeError):
# should fail because 3 int8 is 3 bytes but float16 is two and 3 isn't a multiple of 2
Tensor.empty((3,), dtype=dtypes.int8).bitcast(dtypes.float16)
Tensor.empty((3,), dtype=dtypes.int8).bitcast(dtypes.float16).shape
def test_bitcast_float_to_int32(self):
a = Tensor([1.,2,3])
+31 -28
View File
@@ -43,9 +43,12 @@ def get_buf_uop(buf:Buffer, cache:dict[Buffer,UOp]) -> UOp:
buffers[u] = buf
return cache[buf]
def copy_call(dst:Buffer, src:Buffer, c:dict[Buffer,UOp]) -> UOp:
return get_buf_uop(src,c).copy_to_device(dst.device).call(get_buf_uop(dst,c), get_buf_uop(src,c))
def make_graph(graph_cls, calls:list[UOp]):
linear = compile_linear(UOp(Ops.LINEAR, src=tuple(calls)))
cf = UOp(Ops.CUSTOM_FUNCTION, dtypes.void, src=(linear,), arg="graph")
cf = UOp(Ops.CUSTOM_FUNCTION, src=(linear,), arg="graph")
return graph_cls(cf, [])
def run_schedule(calls:list[UOp]):
@@ -73,8 +76,8 @@ class TestGraph(unittest.TestCase):
c: dict[Buffer,UOp] = {}
calls = [
get_ast(d0, 2).call(get_buf_uop(b[0],c), get_buf_uop(b[1],c), get_buf_uop(b[2],c), metadata=()),
get_ast(d0, 2).call(get_buf_uop(b[0],c), get_buf_uop(b[3],c), get_buf_uop(b[4],c), metadata=()),
get_ast(d0, 2).call(get_buf_uop(b[0],c), get_buf_uop(b[1],c), get_buf_uop(b[2],c)),
get_ast(d0, 2).call(get_buf_uop(b[0],c), get_buf_uop(b[3],c), get_buf_uop(b[4],c)),
]
zero_bufs([b[0]])
@@ -92,8 +95,8 @@ class TestGraph(unittest.TestCase):
c: dict[Buffer,UOp] = {}
calls = [
get_ast(d0, 2).call(get_buf_uop(b[0],c), get_buf_uop(b[1],c), get_buf_uop(b[2],c), metadata=()),
get_ast(d0, 2).call(get_buf_uop(b[1],c), get_buf_uop(b[3],c), get_buf_uop(b[4],c), metadata=()),
get_ast(d0, 2).call(get_buf_uop(b[0],c), get_buf_uop(b[1],c), get_buf_uop(b[2],c)),
get_ast(d0, 2).call(get_buf_uop(b[1],c), get_buf_uop(b[3],c), get_buf_uop(b[4],c)),
]
zero_bufs([b[0], b[1]])
@@ -111,8 +114,8 @@ class TestGraph(unittest.TestCase):
c: dict[Buffer,UOp] = {}
calls = [
get_ast(d0, 2).call(get_buf_uop(b[0],c), get_buf_uop(b[1],c), get_buf_uop(b[2],c), metadata=()),
get_ast(d0, 2).call(get_buf_uop(b[1],c), get_buf_uop(b[0],c), get_buf_uop(b[4],c), metadata=()),
get_ast(d0, 2).call(get_buf_uop(b[0],c), get_buf_uop(b[1],c), get_buf_uop(b[2],c)),
get_ast(d0, 2).call(get_buf_uop(b[1],c), get_buf_uop(b[0],c), get_buf_uop(b[4],c)),
]
zero_bufs([b[0], b[1]])
@@ -131,8 +134,8 @@ class TestGraph(unittest.TestCase):
c: dict[Buffer,UOp] = {}
calls = [
get_ast(d0, 2).call(get_buf_uop(b[0],c), get_buf_uop(b[1],c), get_buf_uop(b[2],c), metadata=()),
UOp(Ops.COPY).call(get_buf_uop(b[3],c), get_buf_uop(b[0],c), metadata=()),
get_ast(d0, 2).call(get_buf_uop(b[0],c), get_buf_uop(b[1],c), get_buf_uop(b[2],c)),
copy_call(b[3], b[0], c),
]
zero_bufs([b[0], b[3]])
@@ -151,8 +154,8 @@ class TestGraph(unittest.TestCase):
c: dict[Buffer,UOp] = {}
calls = [
UOp(Ops.COPY).call(get_buf_uop(b[1],c), get_buf_uop(b[0],c), metadata=()),
get_ast(d0, 2).call(get_buf_uop(b[3],c), get_buf_uop(b[1],c), get_buf_uop(b[2],c), metadata=()),
copy_call(b[1], b[0], c),
get_ast(d0, 2).call(get_buf_uop(b[3],c), get_buf_uop(b[1],c), get_buf_uop(b[2],c)),
]
zero_bufs([b[1], b[3]])
@@ -169,9 +172,9 @@ class TestGraph(unittest.TestCase):
b = [make_buffer(d0, fill=True) for _ in range(8)]
c: dict[Buffer,UOp] = {}
calls1 = [get_ast(d0, 2).call(get_buf_uop(b[3],c), get_buf_uop(b[1],c), get_buf_uop(b[2],c), metadata=())]
calls2 = [get_ast(d0, 2).call(get_buf_uop(b[4],c), get_buf_uop(b[1],c), get_buf_uop(b[3],c), metadata=())]
calls3 = [get_ast(d0, 2).call(get_buf_uop(b[5],c), get_buf_uop(b[4],c), get_buf_uop(b[2],c), metadata=())]
calls1 = [get_ast(d0, 2).call(get_buf_uop(b[3],c), get_buf_uop(b[1],c), get_buf_uop(b[2],c))]
calls2 = [get_ast(d0, 2).call(get_buf_uop(b[4],c), get_buf_uop(b[1],c), get_buf_uop(b[3],c))]
calls3 = [get_ast(d0, 2).call(get_buf_uop(b[5],c), get_buf_uop(b[4],c), get_buf_uop(b[2],c))]
out = [b[3], b[4], b[5]]
zero_bufs(out)
@@ -194,8 +197,8 @@ class TestGraph(unittest.TestCase):
c: dict[Buffer,UOp] = {}
calls = [
UOp(Ops.COPY).call(get_buf_uop(b1[0],c), get_buf_uop(b0[0],c), metadata=()),
get_ast(d0, 2).call(get_buf_uop(b0[2],c), get_buf_uop(b0[0],c), get_buf_uop(b0[1],c), metadata=()),
copy_call(b1[0], b0[0], c),
get_ast(d0, 2).call(get_buf_uop(b0[2],c), get_buf_uop(b0[0],c), get_buf_uop(b0[1],c)),
]
out = [b1[0], b0[2]]
@@ -219,8 +222,8 @@ class TestGraph(unittest.TestCase):
c: dict[Buffer,UOp] = {}
calls = [
UOp(Ops.COPY).call(get_buf_uop(b0,c), get_buf_uop(b2,c), metadata=()),
get_ast(d0, 2).call(get_buf_uop(b1,c), get_buf_uop(b0,c), get_buf_uop(b2,c), metadata=()),
copy_call(b0, b2, c),
get_ast(d0, 2).call(get_buf_uop(b1,c), get_buf_uop(b0,c), get_buf_uop(b2,c)),
]
zero_bufs([b0])
@@ -245,9 +248,9 @@ class TestGraph(unittest.TestCase):
c: dict[Buffer,UOp] = {}
calls = [
UOp(Ops.COPY).call(get_buf_uop(base,c), get_buf_uop(copy_src_full,c), metadata=()),
UOp(Ops.COPY).call(get_buf_uop(v_lo,c), get_buf_uop(copy_src_lo,c), metadata=()),
get_ast(d0, 2).call(get_buf_uop(out,c), get_buf_uop(v_hi,c), get_buf_uop(a,c), metadata=()),
copy_call(base, copy_src_full, c),
copy_call(v_lo, copy_src_lo, c),
get_ast(d0, 2).call(get_buf_uop(out,c), get_buf_uop(v_hi,c), get_buf_uop(a,c)),
]
zero_bufs([base, out])
@@ -272,9 +275,9 @@ class TestGraph(unittest.TestCase):
c: dict[Buffer,UOp] = {}
calls = [
UOp(Ops.COPY).call(get_buf_uop(copy_dst,c), get_buf_uop(base,c), metadata=()),
UOp(Ops.COPY).call(get_buf_uop(v_lo,c), get_buf_uop(copy_src_lo,c), metadata=()),
get_ast(d0, 2).call(get_buf_uop(v_hi,c), get_buf_uop(a,c), get_buf_uop(b,c), metadata=()),
copy_call(copy_dst, base, c),
copy_call(v_lo, copy_src_lo, c),
get_ast(d0, 2).call(get_buf_uop(v_hi,c), get_buf_uop(a,c), get_buf_uop(b,c)),
]
zero_bufs([copy_dst, base])
@@ -299,10 +302,10 @@ class TestGraph(unittest.TestCase):
c: dict[Buffer,UOp] = {}
calls = [
UOp(Ops.COPY).call(get_buf_uop(base,c), get_buf_uop(copy_src_full,c), metadata=()),
UOp(Ops.COPY).call(get_buf_uop(v_mid,c), get_buf_uop(copy_src_mid,c), metadata=()),
get_ast(d0, 2).call(get_buf_uop(out1,c), get_buf_uop(v_lo,c), get_buf_uop(a,c), metadata=()),
get_ast(d0, 2).call(get_buf_uop(out2,c), get_buf_uop(v_hi,c), get_buf_uop(a,c), metadata=()),
copy_call(base, copy_src_full, c),
copy_call(v_mid, copy_src_mid, c),
get_ast(d0, 2).call(get_buf_uop(out1,c), get_buf_uop(v_lo,c), get_buf_uop(a,c)),
get_ast(d0, 2).call(get_buf_uop(out2,c), get_buf_uop(v_hi,c), get_buf_uop(a,c)),
]
outs = [base, out1, out2]
+2 -97
View File
@@ -33,93 +33,14 @@ class TestIselX86(unittest.TestCase):
# both comparisons become the same instruction
self.assertTrue(n.src[0].src[2] == n.src[1].src[2] and n.src[0].src[2].arg is X86Ops.CMP)
def test_vmax(self):
dt_op = [(dtypes.float32, X86Ops.VMAXSS), (dtypes.float64, X86Ops.VMAXSD),
(dtypes.float32.vec(4), X86Ops.VMAXPS), (dtypes.float64.vec(4), X86Ops.VMAXPD)]
self._check_op(dt_op, lambda a,b: (a < b).where(b, a))
def test_vmin(self):
dt_op = [(dtypes.float32, X86Ops.VMINSS), (dtypes.float64, X86Ops.VMINSD),
(dtypes.float32.vec(4), X86Ops.VMINPS), (dtypes.float64.vec(4), X86Ops.VMINPD)]
self._check_op(dt_op, lambda a,b: (a < b).where(a, b))
def test_vfmadd(self):
dt_op = [(dtypes.float32, X86Ops.VFMADD213SS), (dtypes.float64, X86Ops.VFMADD213SD),
(dtypes.float32.vec(4), X86Ops.VFMADD213PS), (dtypes.float64.vec(4), X86Ops.VFMADD213PD)]
self._check_op(dt_op, lambda a,b,c: a * b + c)
# don't use fmadd if op being fused (mul) is used multiple times
def test_no_vfmadd(self):
dt_op = [(dtypes.float32, X86Ops.VADDSS), (dtypes.float64, X86Ops.VADDSD),
(dtypes.float32.vec(4), X86Ops.VADDPS), (dtypes.float64.vec(4), X86Ops.VADDPD)]
self._check_op(dt_op, lambda a,b: a * b + a * b)
def test_vpbroadcast(self):
a = UOp.variable("a", 0, 0, dtypes.int32)
n = self.isel_rewrite(a.broadcast(4))
# need to move src from gpr to xmm before broadcasting
self.assertTrue(n.arg is X86Ops.VPBROADCASTD and n.src[0].arg is X86Ops.VMOVD)
# if we can fuse a load we can skip the move and access memory directly
load = UOp.param(0, dtypes.int32, (16,)).index(UOp.const(dtypes.int32, 0)).load()
n = self.isel_rewrite(load.broadcast(4))
self.assertTrue(n.arg is X86Ops.VPBROADCASTD and len(n.src) == 4)
def test_vbroadcastss(self):
a = UOp.variable("a", 0, 0, dtypes.float32)
valid = [UOp.vectorize(a, a, a, a), UOp.vectorize(a, a, a, a, a, a, a, a)]
for shuf in valid: self.assertIs(self.isel_rewrite(shuf).arg, X86Ops.VBROADCASTSS)
def test_vshufps(self):
a = UOp.variable("a", 0, 0, dtypes.float32.vec(8))
b = UOp.variable("b", 0, 0, dtypes.float32.vec(8))
c = UOp.variable("c", 0, 0, dtypes.float32)
d = UOp.variable("d", 0, 0, dtypes.float32)
valid = [UOp.vectorize(c, c, d, d),
UOp.vectorize(lane(a, 0), lane(a, 1), c, c),
UOp.vectorize(lane(a, 0), lane(a, 1), lane(b, 2), lane(b, 3)),
UOp.vectorize(lane(a, 1), lane(a, 2), lane(a, 3), lane(a, 0)),
UOp.vectorize(lane(a, 3), lane(a, 2), lane(a, 1), lane(a, 0), lane(a, 7), lane(a, 6), lane(a, 5), lane(a, 4)),
UOp.vectorize(lane(a, 0), lane(a, 0), lane(b, 1), lane(b, 1), lane(a, 4), lane(a, 4), lane(b, 5), lane(b, 5))]
for shuf in valid: self.assertIs(self.isel_rewrite(shuf).arg, X86Ops.VSHUFPS)
invalid = [UOp.vectorize(lane(a, 0), lane(a, 1), lane(b, 4), lane(b, 5)),
UOp.vectorize(lane(a, 0), lane(a, 5), lane(b, 2), lane(b, 3)),
UOp.vectorize(lane(a, 0), lane(a, 0), lane(a, 0), lane(a, 0), lane(a, 4), lane(a, 4), lane(a, 4), lane(a, 5)),
UOp.vectorize(lane(a, 0), lane(a, 0), lane(b, 0), lane(b, 0), lane(a, 4), lane(a, 4), lane(b, 4), lane(a, 4))]
for shuf in invalid: self.assertIsNot(self.isel_rewrite(shuf).arg, X86Ops.VSHUFPS)
def test_vshufpd(self):
a = UOp.variable("a", 0, 0, dtypes.float64.vec(4))
b = UOp.variable("b", 0, 0, dtypes.float64.vec(4))
c = UOp.variable("c", 0, 0, dtypes.float64)
d = UOp.variable("d", 0, 0, dtypes.float64)
valid = [UOp.vectorize(c, d),
UOp.vectorize(lane(a, 0), c),
UOp.vectorize(lane(a, 1), lane(b, 1)),
UOp.vectorize(lane(a, 0), lane(b, 1), lane(a, 2), lane(b, 3)),
UOp.vectorize(lane(a, 1), lane(a, 1), lane(a, 3), lane(a, 3))]
for shuf in valid: self.assertIs(self.isel_rewrite(shuf).arg, X86Ops.VSHUFPD)
invalid = [UOp.vectorize(c, c, c, c),
UOp.vectorize(lane(a, 0), lane(a, 1), lane(b, 2), lane(b, 3)),
UOp.vectorize(lane(a, 2), lane(b, 3), lane(a, 2), lane(b, 3)),
UOp.vectorize(lane(a, 0), lane(b, 1), lane(a, 0), lane(b, 1))]
for shuf in invalid: self.assertIsNot(self.isel_rewrite(shuf).arg, X86Ops.VSHUFPD)
def test_vinsertps(self):
a = UOp.variable("a", 0, 0, dtypes.float32.vec(4))
b = UOp.variable("b", 0, 0, dtypes.float32.vec(4))
c = UOp.variable("c", 0, 0, dtypes.float32.vec(4))
d = UOp.variable("e", 0, 0, dtypes.float32)
# moving 0th element to position 0 does nothing so only 1 vinsertps is generated
n = self.isel_rewrite(UOp.vectorize(lane(a, 0), d))
self.assertIs(n.arg, X86Ops.VINSERTPS)
self.assertIsNot(n.src[0].arg, X86Ops.VINSERTPS)
valid = [UOp.vectorize(lane(a, 0), lane(b, 1), lane(a, 2), lane(b, 3)),
UOp.vectorize(lane(a, 3), lane(b, 2), lane(c, 1), d)]
valid = [UOp.stack(lane(a, 0), lane(b, 1), lane(a, 2), lane(b, 3)),
UOp.stack(lane(a, 3), lane(b, 2), lane(c, 1), d)]
for shuf in valid: self.assertIs(self.isel_rewrite(shuf).arg, X86Ops.VINSERTPS)
# complex address is [base + index*scale + displacement]
@@ -130,21 +51,5 @@ class TestIselX86(unittest.TestCase):
# displacement is the constant in "a" scaled to the buffer element size, dtype is int8 when the value fits otherwise int32
self.assertTrue(n.src[2].op is Ops.CONST and n.src[2].dtype is dtypes.int8 and n.src[2].arg == 4)
def test_fold_load(self):
load1 = UOp.param(0, dtypes.int32, (16,)).index(UOp.const(dtypes.int32, 0)).load()
load2 = UOp.param(0, dtypes.int32, (16,)).index(UOp.const(dtypes.int32, 1)).load()
n = self.isel_rewrite(load1 + load2)
self.assertTrue(len(n.src) == 5)
# don't fold when used multiple times
def test_dont_fold_load(self):
load = UOp.param(0, dtypes.int32, (16,)).index(UOp.const(dtypes.int32, 0)).load()
# used by multiple users
n = self.isel_rewrite(load + 1 + load)
self.assertTrue(len(n.src) == 2)
# used mutiple times by same user
n = self.isel_rewrite(load * load)
self.assertTrue(len(n.src) == 2)
if __name__ == "__main__":
unittest.main()
+6 -6
View File
@@ -8,7 +8,7 @@ from tinygrad.tensor import Tensor, _to_np_dtype
from tinygrad.engine.realize import run_linear
from tinygrad.codegen import to_program
from tinygrad.helpers import Context, flatten, dedup, TC_SELECT, TC_OPT, DEV
from tinygrad.dtype import DType, dtypes, PtrDType, AddrSpace
from tinygrad.dtype import DType, dtypes, AddrSpace
from tinygrad.renderer.ptx import PTXRenderer
from tinygrad.renderer.cstyle import CUDARenderer
from tinygrad.renderer.isa import ISARenderer
@@ -211,14 +211,14 @@ class TestLinearizer(unittest.TestCase):
realized_ast = a.schedule_linear().src[-1].src[0]
program = to_program(replace_opts(realized_ast, []), renderer=Device[Device.DEFAULT].renderer)
local = [uop for uop in tuple(program.src[1].src) if uop.op is Ops.BUFFER and uop.addrspace in (AddrSpace.LOCAL, AddrSpace.REG)]
assert local[0].dtype.base == acc_dtype
assert local[0].dtype == acc_dtype
def test_arg_acc_dtype(self):
def helper_arg_acc_dtype(c: Tensor, expected_dtype:DType):
realized_ast = c.schedule_linear().src[-1].src[0]
program = to_program(replace_opts(realized_ast, []), renderer=Device[Device.DEFAULT].renderer)
local = [uop for uop in tuple(program.src[1].src) if uop.op is Ops.BUFFER and uop.addrspace in (AddrSpace.LOCAL, AddrSpace.REG)]
self.assertEqual(local[0].dtype.base, expected_dtype)
self.assertEqual(local[0].dtype, expected_dtype)
tests = (
(dtypes.float16, None, dtypes.float),
@@ -249,7 +249,7 @@ class TestLinearizer(unittest.TestCase):
end_range = [i for i, x in enumerate(uops) if x.op is Ops.END][0]
for i,u in enumerate(uops): print(i, u.op, [uops.index(s) for s in u.src], u.arg, u.dtype)
for u in uops:
if u.op is Ops.STORE and isinstance(dt:=u.src[0].dtype, PtrDType) and dt.addrspace is AddrSpace.REG:
if u.op is Ops.STORE and u.src[0].addrspace is AddrSpace.REG:
if uops.index(u) < begin_range:
assert u.src[1].op is Ops.CONST
else:
@@ -276,7 +276,7 @@ class TestLinearizer(unittest.TestCase):
sched = [si for si in t.schedule_linear().src if si.src[0].op is Ops.SINK]
# sum_collapse is a full collapse now
assert len(sched) == 1
assert not any(u.op is Ops.REDUCE and len(u.arg[1]) > 0 for u in sched[0].src[0].toposort()), "found reduce in sum collapse"
assert not any(u.op is Ops.REDUCE and u.arg[1] > 0 for u in sched[0].src[0].toposort()), "found reduce in sum collapse"
#lin = Kernel(sched[0].ast)
#assert not any(u.op is Ops.RANGE for u in lin.linearize().uops), "found loop in sum collapse"
@@ -307,7 +307,7 @@ class TestLinearizer(unittest.TestCase):
if if_op:=next((u for u in uops if u.op is Ops.IF), None):
uops = uops[:uops.index(if_op)]
assert len(set([u.op for u in uops if u.op in {Ops.RANGE, Ops.SPECIAL}])) == 1, "has either specials or ranges, not both"
reg_stores = [u for u in uops if u.op is Ops.STORE and isinstance(dt:=u.src[0].dtype, PtrDType) and dt.addrspace == AddrSpace.REG]
reg_stores = [u for u in uops if u.op is Ops.STORE and u.src[0].addrspace == AddrSpace.REG]
assert len(reg_stores) == 0, "STORE to reg should have been simplified"
assert len([u for u in uops if u.op is Ops.MAX]) <= max_ops, "no unnecessary MAX ops"
+12 -12
View File
@@ -11,19 +11,19 @@ from tinygrad.codegen import to_program
class TestLinearizerFailure(unittest.TestCase):
@unittest.skipUnless(Device.DEFAULT == "METAL", "only tested on METAL")
def test_failure_beam_mnist(self):
c0 = UOp.param(0, dtypes.uchar.ptr(4014080))
c1 = UOp.range(UOp.const(dtypes.weakint, 512), 0, AxisType.GLOBAL)
c2 = UOp.range(UOp.const(dtypes.weakint, 784), 1, AxisType.GLOBAL)
c3 = UOp.range(UOp.const(dtypes.weakint, 10), 3, AxisType.GLOBAL)
c4 = UOp.param(1, dtypes.int.ptr(512))
c0 = UOp.param(0, dtypes.uchar, (4014080,))
c1 = UOp.range(UOp.const(dtypes.index, 512), 0, AxisType.GLOBAL)
c2 = UOp.range(UOp.const(dtypes.index, 784), 1, AxisType.GLOBAL)
c3 = UOp.range(UOp.const(dtypes.index, 10), 3, AxisType.GLOBAL)
c4 = UOp.param(1, dtypes.int, (512,))
c5 = c4.index(c1.valid(UOp.const(dtypes.bool, True)))
c6 = UOp.range(UOp.const(dtypes.weakint, 6000), 1004, AxisType.REDUCE)
c7 = UOp.range(UOp.const(dtypes.weakint, 3750), 2006, AxisType.REDUCE)
c8 = UOp.range(UOp.const(dtypes.weakint, 16), 2007, AxisType.GROUP_REDUCE)
c9 = UOp.param(2, dtypes.uchar.ptr(47040000))
c10 = c9.index((((c3*UOp.const(dtypes.weakint, 4704000))+c2)+(c6*UOp.const(dtypes.weakint, 784))).valid(UOp.const(dtypes.bool, True)))
c11 = c5.alu(Ops.CMPNE, ((((c3*UOp.const(dtypes.weakint, 6000))+c6)+((c7*UOp.const(dtypes.weakint, 16))+c8)).alu(Ops.CMPLT, UOp.const(dtypes.weakint, 59999)).where(UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 1)).reduce(c7, c8, arg=Ops.ADD)+UOp.const(dtypes.int, -1))).where(UOp.const(dtypes.uchar, 0), c10).reduce(c6, arg=Ops.ADD)
c12 = c0.index((((c1*UOp.const(dtypes.weakint, 7840))+(c2*UOp.const(dtypes.weakint, 10)))+c3).valid(UOp.const(dtypes.bool, True))).store(c11).end(c1, c2, c3)
c6 = UOp.range(UOp.const(dtypes.index, 6000), 1004, AxisType.REDUCE)
c7 = UOp.range(UOp.const(dtypes.index, 3750), 2006, AxisType.REDUCE)
c8 = UOp.range(UOp.const(dtypes.index, 16), 2007, AxisType.GROUP_REDUCE)
c9 = UOp.param(2, dtypes.uchar, (47040000,))
c10 = c9.index((((c3*UOp.const(dtypes.index, 4704000))+c2)+(c6*UOp.const(dtypes.index, 784))).valid(UOp.const(dtypes.bool, True)))
c11 = c5.alu(Ops.CMPNE, ((((c3*UOp.const(dtypes.index, 6000))+c6)+((c7*UOp.const(dtypes.index, 16))+c8)).alu(Ops.CMPLT, UOp.const(dtypes.index, 59999)).where(UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 1)).reduce(c7, c8, arg=Ops.ADD)+UOp.const(dtypes.int, -1))).where(UOp.const(dtypes.uchar, 0), c10).reduce(c6, arg=Ops.ADD)
c12 = c0.index((((c1*UOp.const(dtypes.index, 7840))+(c2*UOp.const(dtypes.index, 10)))+c3).valid(UOp.const(dtypes.bool, True))).store(c11).end(c1, c2, c3)
ast = c12.sink(arg=KernelInfo(name='test', axis_types=(), dont_use_locals=False, applied_opts=(Opt(op=OptOps.GROUP, axis=1, arg=16),), opts_to_apply=None))
_ = to_program(ast, Device["METAL"].renderer)
+1 -1
View File
@@ -89,7 +89,7 @@ class TestLocalAmax(unittest.TestCase):
x = Tensor.arange(16).reshape(4, 4).cast(dtypes.float).clone(devices[0]).realize().shard(devices, axis=0).realize()
GlobalCounters.reset()
out = (x * local_abs_max(x)).clone().realize()
self.assertEqual(GlobalCounters.kernel_count, 4)
self.assertEqual(GlobalCounters.kernel_count, 2)
self.assertEqual(out.tolist(), [[0., 7., 14., 21.], [28., 35., 42., 49.], [120., 135., 150., 165.], [180., 195., 210., 225.]])
if __name__ == '__main__':
+9
View File
@@ -127,6 +127,15 @@ class TestMultiTensor(unittest.TestCase):
fn = f(n)
np.testing.assert_allclose(fX.numpy(), fn, rtol=1e-6, atol=1e-6)
def test_stack(self):
X = Tensor.rand(4, 4).shard_(devices_2, 0)
Y = Tensor.rand(4, 4).shard_(devices_2, 0)
Z = Tensor.rand(4, 4).shard_(devices_2, 1) # mismatched shard axis gets resharded
for dim in (0, 1):
np.testing.assert_allclose(Tensor.stack(X, Y, Z, dim=dim).numpy(), np.stack([X.numpy(), Y.numpy(), Z.numpy()], axis=dim))
grad = Tensor.stack(X, Y).sum().gradient(X)[0]
np.testing.assert_allclose(grad.numpy(), 1)
def test_allreduce_naive(self):
with Context(RING=0):
a,b = _test_allreduce(Tensor.rand(256, 256))
+7
View File
@@ -1449,6 +1449,7 @@ class TestOps(unittest.TestCase):
def test_small_gemm_eye(self):
helper_test_op(None, lambda x,y: x.matmul(y), lambda x,y: x@y, vals=[np.eye(8).astype(np.float32), np.eye(8).astype(np.float32)])
@unittest.skipUnless(dtypes.half in Device[Device.DEFAULT].renderer.supported_dtypes(), "not precise enough when emulating")
@unittest.skipIf(IMAGE>0, "image does math in float32")
def test_gemm_fp16(self):
helper_test_op([(64,64), (64,64)], lambda x,y: x.half().matmul(y.half()), atol=5e-3, rtol=5e-3, grad_atol=5e-3, grad_rtol=5e-3)
def test_gemm(self):
@@ -1601,6 +1602,12 @@ class TestOps(unittest.TestCase):
helper_test_op(None, lambda x,y: x.isclose(y), vals=[[a], [b]], forward_only=True)
helper_test_op(None, lambda x,y: x.isclose(y, equal_nan=True), vals=[[a], [b]], forward_only=True)
def test_isclose_scalar(self):
# torch needs a tensor
helper_test_op([(3, 4, 5, 6)], lambda x: x.isclose(torch.tensor(1.0)), lambda x: x.isclose(1.0), forward_only=True)
helper_test_op(None, lambda x: x.isclose(torch.tensor(1.0)), lambda x: x.isclose(1.0),
vals=[[1.0, 1.0 + 1e-7, 2.0, math.inf, -math.inf, math.nan]], forward_only=True)
def test_mean(self):
helper_test_op([(3,4,5,6)], lambda x: x.mean())
helper_test_op([()], lambda x: x.mean())
+34 -19
View File
@@ -22,31 +22,31 @@ def _test_uop_result(inputs:list[Tensor], sink:UOp, local_size=None):
def _setup_and_test_alu(alu_op:Ops, input_val:ConstType, *alu_src_uops:UOp):
dtype = alu_src_uops[0].dtype
a = UOp.param(0, dtype.ptr(1))
b = UOp.param(1, dtype.ptr(1))
a = UOp.param(0, dtype, (1,))
b = UOp.param(1, dtype, (1,))
idx = UOp.const(dtypes.int, 0)
ld = b.index(idx, ptr=True).load()
ld = b.index(idx).load()
alu = ld.alu(alu_op, *alu_src_uops)
store = UOp.store(a.index(idx, ptr=True), alu)
return _test_uop_result([Tensor([input_val])], UOp(Ops.SINK, dtypes.void, (store,), arg=KernelInfo()))[0]
store = UOp.store(a.index(idx), alu)
return _test_uop_result([Tensor([input_val])], UOp(Ops.SINK, src=(store,), arg=KernelInfo()))[0]
class TestRendererFailures(unittest.TestCase):
@unittest.skipIf(not isinstance(Device[Device.DEFAULT].renderer, (PTXRenderer, PythonRenderer)), "test is for ptx or python renderer")
def test_gated_store_with_alu(self):
a = UOp.param(0, dtypes.int.ptr(4))
gate_alu = (lidx0:=UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 4),), 'lidx0')).ne(0)
gated_alu_store = UOp(Ops.STORE, dtypes.void, (a.index(lidx0.valid(gate_alu), ptr=True), UOp.const(dtypes.int, 1)))
sink = UOp(Ops.SINK, dtypes.void, (gated_alu_store,), arg=KernelInfo())
a = UOp.param(0, dtypes.int, (4,))
gate_alu = (lidx0:=UOp.special(4, 'lidx0')).ne(0)
gated_alu_store = UOp(Ops.STORE, src=(a.index(lidx0.valid(gate_alu)), UOp.const(dtypes.int, 1)))
sink = UOp(Ops.SINK, src=(gated_alu_store,), arg=KernelInfo())
ret = _test_uop_result([], sink, local_size=[4, 1, 1])[0]
np.testing.assert_equal(ret, [0, 1, 1, 1])
@unittest.skipIf(not isinstance(Device[Device.DEFAULT].renderer, (PTXRenderer, PythonRenderer)), "test is for ptx or python renderer")
def test_gated_store_with_alu_2d(self):
a = UOp.param(0, dtypes.int.ptr(8))
gate_alu_0 = (lidx0:=UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 4),), 'lidx0')).ne(0)
gate_alu_1 = (lidx1:=UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 2),), 'lidx1')).ne(0)
gated_alu_store = UOp(Ops.STORE, dtypes.void, (a.index((lidx0+lidx1*4).valid(gate_alu_0&gate_alu_1), ptr=True), UOp.const(dtypes.int, 1)))
sink = UOp(Ops.SINK, dtypes.void, (gated_alu_store,), arg=KernelInfo())
a = UOp.param(0, dtypes.int, (8,))
gate_alu_0 = (lidx0:=UOp.special(4, 'lidx0')).ne(0)
gate_alu_1 = (lidx1:=UOp.special(2, 'lidx1')).ne(0)
gated_alu_store = UOp(Ops.STORE, src=(a.index((lidx0+lidx1*4).valid(gate_alu_0&gate_alu_1)), UOp.const(dtypes.int, 1)))
sink = UOp(Ops.SINK, src=(gated_alu_store,), arg=KernelInfo())
ret = _test_uop_result([], sink, local_size=[4, 2, 1])[0]
np.testing.assert_equal(ret, [0, 0, 0, 0, 0, 1, 1, 1])
@@ -83,16 +83,31 @@ class TestWGSLFailures(unittest.TestCase):
ret = _setup_and_test_alu(Ops.MUL, 5.0, UOp.const(dtypes.float32, float("inf")))
self.assertEqual(ret[0], float("inf"))
# WGSL has a specific select(alt, val, gate) ternary operator instead of gate?val:alt
def test_gated_load(self):
a = UOp.param(0, dtypes.int, (4,))
b = UOp.param(1, dtypes.int, (4,))
c = UOp.param(2, dtypes.int, (4,))
lidx0 = UOp.special(4, "lidx0")
gate = lidx0.ne(0)
alt = c.index(lidx0).load()
ld = UOp.load(b.index(lidx0.valid(gate)))
alt_load = gate.where(ld, alt)
store = UOp.store(a.index(lidx0), alt_load)
sink = UOp(Ops.SINK, src=(store,), arg=KernelInfo())
ret = _test_uop_result([Tensor([0,1,2,3], dtype=dtypes.int), Tensor([4,5,6,7], dtype=dtypes.int)], sink, local_size=[4])[0]
np.testing.assert_equal(ret, [4,1,2,3])
@unittest.skipIf(not isinstance(Device[Device.DEFAULT].renderer, PTXRenderer), "tests for ptx renderer")
class TestPTXFailures(unittest.TestCase):
@unittest.skip("INDEX can only have a gate ALU parent, not an IF")
def test_gated_store_with_if(self):
a = UOp.param(0, dtypes.int.ptr())
gate_alu = (lidx0:=UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 4),), 'lidx0')).ne(0)
a = UOp.param(0, dtypes.int, (4,))
gate_alu = (lidx0:=UOp.special(4, 'lidx0')).ne(0)
val = UOp.const(dtypes.int, 1)
if_uop = UOp(Ops.IF, dtypes.void, (gate_alu,))
gated_alu_store = UOp(Ops.STORE, dtypes.void, (a.index(lidx0, if_uop), val))
sink = UOp(Ops.SINK, dtypes.void, (gated_alu_store,), arg=KernelInfo())
if_uop = UOp(Ops.IF, src=(gate_alu,))
gated_alu_store = UOp(Ops.STORE, src=(a.index(lidx0, if_uop), val))
sink = UOp(Ops.SINK, src=(gated_alu_store,), arg=KernelInfo())
ret = _test_uop_result([], sink, local_size=[4, 1, 1])[0]
np.testing.assert_equal(ret, [0, 1, 1, 1])
+46 -1
View File
@@ -1,6 +1,7 @@
import unittest
import numpy as np
from tinygrad import Tensor, Variable
from tinygrad import Tensor, Variable, dtypes
from tinygrad.helpers import CHECK_OOB
class TestTensorVariable(unittest.TestCase):
def test_add_tvar(self):
@@ -8,6 +9,50 @@ class TestTensorVariable(unittest.TestCase):
ret = (Tensor(vv) + 3).item()
assert ret == 4
def test_variable_mul_tensor(self):
vv = Variable("a", 1, 10).bind(2)
t = Tensor.ones(3, dtype=dtypes.int8)
self.assertListEqual((t * vv).tolist(), [2, 2, 2])
# TODO: fix
try:
self.assertListEqual((vv * t).tolist(), [2, 2, 2])
except RuntimeError: pass
def test_large_range_variable(self):
vv = Variable("b", 0, 2**40).bind(2**35)
# TODO: pm_lower_index_dtype lowers ALU PARAM to int32 unconditionally
try:
self.assertEqual(Tensor(vv).item(), 2**35)
except AssertionError:
pass
def test_variable_tensor_dtype_arg(self):
vv = Variable("a", 1, 10).bind(2)
t = Tensor(vv, dtype=dtypes.float32)
self.assertEqual(t.dtype, dtypes.float32)
self.assertEqual(t.item(), 2.0)
def test_unbound_variable_tensor(self):
# an unbound variable schedules fine, but can't execute
with self.assertRaisesRegex(RuntimeError, "unbound"): Tensor(Variable("u", 1, 10)).item()
with self.assertRaisesRegex(RuntimeError, "unbound"): (Tensor(Variable("u", 1, 10)) + 1).item()
# bound variables in an expression are fine
self.assertEqual(Tensor(Variable("u", 1, 10).bind(2) + 1).item(), 3)
def test_shrink_beyond_buffer_variable(self):
# TODO: shrink by a variable whose vmax exceeds the dim should fail at build, today only CHECK_OOB=1 rejects it
t = Tensor.ones(3).contiguous()[:Variable("a", 1, 10).bind(5)]
if CHECK_OOB: self.assertRaises(RuntimeError, t.sum().item)
else: t.sum().item() # silent OOB: reads 2 elements past the buffer, result depends on the allocator
def test_symbolic_shape_mul_variable_tensor(self):
# NOTE: the buffer dim must cover the variable's vmax
vv = Variable("a", 1, 10).bind(2)
self.assertEqual((Tensor.ones(10).contiguous()[:vv] * Tensor(vv)).sum().item(), 4.0)
# a vmin=0 symbolic dim broadcasts too
v0 = Variable("z", 0, 10).bind(2)
self.assertEqual((Tensor.ones(10).contiguous()[:v0] * Tensor(v0)).sum().item(), 4.0)
def test_inner_tvar_node(self):
vv = Variable("w", 0, 10).bind(2)
ret = Tensor(vv * 4).item()
+14 -13
View File
@@ -20,18 +20,18 @@ def run_uops(uops_list:list[UOp], bufs:list[Buffer]):
def uop(uops:list[UOp], op:Ops, dtype:Optional[DType], src:tuple[UOp, ...], arg:Any=None) -> UOp:
if op is Ops.CONST: uops.append(UOp.const(dtype, arg))
elif op is Ops.PARAM: uops.append(UOp.param(arg, dtype).replace(src=()))
elif op is Ops.PARAM: uops.append(UOp.param(arg, dtype, shape=(1,)))
else: uops.append(UOp(op, dtype, tuple(src), arg))
return uops[-1]
def _test_single_value(vals, op, dts):
uops = []
output_dtype = dtypes.bool if op in (Ops.CMPLT, Ops.CMPNE) else dts[-1]
buf_store = uop(uops, Ops.PARAM, output_dtype.ptr(1), (), 0)
buf_loads = [uop(uops, Ops.PARAM, dtype.ptr(1), (), i+1) for i,dtype in enumerate(dts)]
buf_store = uop(uops, Ops.PARAM, output_dtype, (), 0)
buf_loads = [uop(uops, Ops.PARAM, dtype, (), i+1) for i,dtype in enumerate(dts)]
loads = (buf_loads[i].index(uop(uops, Ops.CONST, dtypes.int32, (), 0)) for i, dtype in enumerate(dts))
alu = uop(uops, op, output_dtype, loads)
out = uop(uops, Ops.STORE, dtypes.void, (buf_store.index(uop(uops, Ops.CONST, dtypes.int32, (), 0), ptr=True), alu))
out = uop(uops, Ops.STORE, dtypes.void, (buf_store.index(uop(uops, Ops.CONST, dtypes.int32, (), 0)), alu))
buf = Buffer(Device.DEFAULT, 1, output_dtype).allocate()
buf2 = [Buffer(Device.DEFAULT, 1, dtype).allocate().copyin(np.array([a], dtype=_to_np_dtype(dtype)).data) for a,dtype in zip(vals, dts)]
run_uops([out], [buf]+buf2)
@@ -42,7 +42,7 @@ def _test_single_value(vals, op, dts):
def _test_single_value_const(vals, op, dts):
uops = []
output_dtype = dtypes.bool if op in (Ops.CMPLT, Ops.CMPNE) else dts[-1]
buf_store = uop(uops, Ops.PARAM, output_dtype.ptr(1), (), 0)
buf_store = uop(uops, Ops.PARAM, output_dtype, (), 0)
loads = (uop(uops, Ops.CONST, dtype, [], a) for a,dtype in zip(vals, dts))
alu = uop(uops, op, output_dtype, loads)
out = buf_store[UOp.const(dtypes.int32, 0)].store(alu)
@@ -54,7 +54,7 @@ def _test_single_value_const(vals, op, dts):
def _test_uops_result(output_dtype, uops, res):
# uops = []
buf_store = uop(uops, Ops.PARAM, output_dtype.ptr(1), (), 0)
buf_store = uop(uops, Ops.PARAM, output_dtype, (), 0)
# res = output_fn(uops)
out = uop(uops, Ops.STORE, dtypes.void, (buf_store.index(uop(uops, Ops.CONST, dtypes.int32, (), 0)), res))
buf = Buffer(Device.DEFAULT, 1, output_dtype).allocate()
@@ -181,7 +181,7 @@ class TestLocalAccess(unittest.TestCase):
uops.append(smem)
st = uop(uops, Ops.STORE, dtypes.void, (smem.index(uop(uops, Ops.CONST, dtypes.int32, (), 0)), uop(uops, Ops.CONST, dtypes.float32, (), 42.0)))
barr = uop(uops, Ops.BARRIER, dtypes.void, (st,))
sres = uop(uops, Ops.LOAD, dtypes.float32, (smem.after(barr).index(uop(uops, Ops.CONST, dtypes.int32, (), 0), ptr=True),))
sres = uop(uops, Ops.LOAD, dtypes.float32, (smem.after(barr).index(uop(uops, Ops.CONST, dtypes.int32, (), 0)),))
self.assertEqual(_test_uops_result(dtypes.float32, uops, sres), 42)
# NOTE: webgpu specific, since only webgpu performs bitpacking
@@ -225,13 +225,13 @@ class TestLocalAccess(unittest.TestCase):
@unittest.skipUnless(isinstance(Device[Device.DEFAULT].renderer, PTXRenderer), "This only tests assembly backends")
class TestAssembly(unittest.TestCase):
def test_bitshift_left(self):
g1 = UOp.param(0, dtypes.int32.ptr(3))
out = UOp.param(1, dtypes.int32.ptr(2))
g1 = UOp.param(0, dtypes.int32, shape=(3,))
out = UOp.param(1, dtypes.int32, shape=(2,))
c1 = UOp.const(dtypes.int, 2)
c2 = UOp.const(dtypes.int, 3)
l1 = g1.index(c1)
a1 = UOp(Ops.MUL, dtypes.int, (l1, c1))
a2 = UOp(Ops.MUL, dtypes.int, (l1, c2))
a1 = UOp(Ops.MUL, src=(l1, c1))
a2 = UOp(Ops.MUL, src=(l1, c2))
uops = to_uops_list([out.index(UOp.const(dtypes.int, 0)).store(a1), out.index(UOp.const(dtypes.int, 1)).store(a2)],
ren=Device[Device.DEFAULT].renderer)
Device[Device.DEFAULT].renderer.render(uops)
@@ -239,6 +239,7 @@ class TestAssembly(unittest.TestCase):
self.assertIn(Ops.SHL, ops)
self.assertIn(Ops.MUL, ops)
@unittest.skip("this is a questionable microoptimization i won't enforce")
def test_mulacc_unrolled(self):
# test that acc = acc + a0*b0 + a1*b1 + a2*b2 + a3*b3
# is not acc = acc + (a0*b0 + a1*b1 + a2*b2 + a3*b3)
@@ -253,7 +254,7 @@ class TestAssembly(unittest.TestCase):
self.assertGreaterEqual(len([x.op for x in uops if x.op is Ops.MULACC]), 4)
def test_mulacc_shl(self):
g1 = UOp.param(0, dtypes.int32.ptr(2))
g1 = UOp.param(0, dtypes.int32, shape=(2,))
c1 = UOp.const(dtypes.int, 0)
c2 = UOp.const(dtypes.int, 1)
expr = g1.index(c1) * UOp.const(dtypes.int, 4096) + g1.index(c2)
@@ -262,7 +263,7 @@ class TestAssembly(unittest.TestCase):
self.assertIn(Ops.MULACC, [x.op for x in uops])
def test_use_cmpeq(self):
g = UOp.param(0, dtypes.uint32.ptr(8))
g = UOp.param(0, dtypes.uint32, shape=(8,))
c = UOp.const(dtypes.uint, 7)
comp = g.index(c).ne(c).ne(True)
uops = to_uops_list([comp], ren=Device[Device.DEFAULT].renderer)
+19 -19
View File
@@ -7,12 +7,12 @@ from tinygrad.engine.realize import get_runtime
from tinygrad.codegen import to_program
from tinygrad.helpers import dedup, getenv
from tinygrad.device import Buffer
from tinygrad.dtype import ImageDType, Invalid
from tinygrad.dtype import Invalid
# PYTHONPATH="." DEV=QCOM FLOAT16=1 IMAGE=2 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/720392c9a5b986981fdbed1bb8c47a6c5573a50e/selfdrive/modeld/models/driving_vision.onnx
def vision_conv_143():
c0 = UOp.param(0, dtypes.imageh((16, 1024, 4)))
c0 = UOp.param(0, dtypes.half, shape=(16, 1024, 4))
c2 = UOp.range(32, 3, AxisType.LOOP)
c5 = UOp.range(128, 4, AxisType.LOOP)
c8 = UOp.range(16, 2, AxisType.LOOP)
@@ -22,15 +22,15 @@ def vision_conv_143():
c26 = UOp.range(7, 1, AxisType.REDUCE)
c27 = c2*2+c26
c32 = ((c27<3)!=True)&(c27<67)
c34 = UOp.param(1, dtypes.imageh((32, 1024, 4)))
c34 = UOp.param(1, dtypes.half, shape=(32, 1024, 4))
c38 = c5//2
c45 = (c32&c24).where((c27*64+c38+c17*4096+-12480), UOp.const(dtypes.weakint, Invalid))
c45 = (c32&c24).where((c27*64+c38+c17*4096+-12480), UOp.const(dtypes.index, Invalid))
c48 = (c24&c32).where(c34.index(c45), UOp.const(dtypes.float, 0.0))
c49 = UOp.param(2, dtypes.imageh((64, 49, 4)))
c49 = UOp.param(2, dtypes.half, shape=(64, 49, 4))
c61 = c48*c49.index((c26*4+c5%2+c16*28+c38*196))
c63 = UOp.param(3, dtypes.float.ptr(128))
c63 = UOp.param(3, dtypes.float, (128,))
c65 = c61.reduce(c16, c26, arg=Ops.ADD)+c63.index(c5)
c67 = c0.index((c2*128+c5+c8*4096), ptr=True).store(c65).end(c8, c2, c5)
c67 = c0.index((c2*128+c5+c8*4096)).store(c65).end(c8, c2, c5)
opts = None
# JITBEAM=2
@@ -38,7 +38,7 @@ def vision_conv_143():
return c67.sink(arg=KernelInfo(name="conv", opts_to_apply=opts))
def vision_conv_153():
c0 = UOp.param(0, dtypes.imageh((8, 1024, 4)))
c0 = UOp.param(0, dtypes.half, shape=(8, 1024, 4))
c2 = UOp.range(16, 3, AxisType.LOOP)
c5 = UOp.range(256, 4, AxisType.LOOP)
c8 = UOp.range(8, 2, AxisType.LOOP)
@@ -48,15 +48,15 @@ def vision_conv_153():
c26 = UOp.range(7, 1, AxisType.REDUCE)
c27 = c2*2+c26
c32 = ((c27<3)!=True)&(c27<35)
c34 = UOp.param(1, dtypes.imageh((16, 1024, 4)))
c34 = UOp.param(1, dtypes.half, shape=(16, 1024, 4))
c38 = c5//2
c45 = (c32&c24).where((c27*128+c38+c17*4096+-12672), UOp.const(dtypes.weakint, Invalid))
c45 = (c32&c24).where((c27*128+c38+c17*4096+-12672), UOp.const(dtypes.index, Invalid))
c48 = (c24&c32).where(c34.index(c45), UOp.const(dtypes.float, 0.0))
c49 = UOp.param(2, dtypes.imageh((128, 49, 4)))
c49 = UOp.param(2, dtypes.half, shape=(128, 49, 4))
c61 = c48*c49.index((c26*4+c5%2+c16*28+c38*196))
c63 = UOp.param(3, dtypes.float.ptr(256))
c63 = UOp.param(3, dtypes.float, (256,))
c65 = c61.reduce(c16, c26, arg=Ops.ADD)+c63.index(c5)
c67 = c0.index((c2*256+c5+c8*4096), ptr=True).store(c65).end(c8, c2, c5)
c67 = c0.index((c2*256+c5+c8*4096)).store(c65).end(c8, c2, c5)
opts = None
# JITBEAM=2
@@ -64,20 +64,20 @@ def vision_conv_153():
return c67.sink(arg=KernelInfo(name="conv", opts_to_apply=opts))
def dm_conv_172():
c0 = UOp.param(0, dtypes.imageh((1, 240, 4)))
c0 = UOp.param(0, dtypes.half, shape=(1, 240, 4))
c2 = UOp.range(960, 4, AxisType.LOOP)
c5 = UOp.param(1, dtypes.imageh((8, 384, 4)))
c5 = UOp.param(1, dtypes.half, shape=(8, 384, 4))
c7 = UOp.range(32, 0, AxisType.REDUCE)
c10 = UOp.range(4, 1, AxisType.REDUCE)
c13 = UOp.range(12, 3, AxisType.REDUCE)
c18 = UOp.range(8, 2, AxisType.REDUCE)
c23 = UOp.param(2, dtypes.imageh((240, 128, 4)))
c23 = UOp.param(2, dtypes.half, shape=(240, 128, 4))
c35 = c5.index((c7*4+c10+c13*128+c18*1536))*c23.index((c10*4+c2%4+c7*16+c2//4*512))
c37 = UOp.param(3, dtypes.float.ptr(960))
c37 = UOp.param(3, dtypes.float, (960,))
c39 = c35.reduce(c7, c10, arg=Ops.ADD)+c37.index(c2)
c50 = (1.0+((c39+0.044708251953125*(c39*(c39*c39)))*-2.3021129851685216).exp2()).reciprocal()*c39
c53 = c50.reduce(c18, c13, arg=Ops.ADD)*0.010416666666666666
c55 = c0.index(c2, ptr=True).store(c53).end(c2)
c55 = c0.index(c2).store(c53).end(c2)
opts = None
# JITBEAM=2
@@ -95,7 +95,7 @@ rt = get_runtime(Device.DEFAULT, ps)
gs = sorted(dedup([u for u in ast.toposort() if u.op is Ops.PARAM]), key=lambda u: u.arg)
# print(len(gs))
# print([g.dtype for g in gs])
bufs = [Buffer(ps.arg.device, g.size, g.dtype if isinstance(g.dtype, ImageDType) else g.dtype._base).ensure_allocated() for g in gs]
bufs = [Buffer(ps.arg.device, g.max_numel(), g.dtype).ensure_allocated() for g in gs]
gsize, lsize = ps.arg.launch_dims({})
t = rt(*[b._buf for b in bufs], global_size=gsize, local_size=lsize, vals=ps.arg.vals({}), wait=True)
+1 -1
View File
@@ -67,7 +67,7 @@ class TestYOLOv8(unittest.TestCase):
# currently rtol is 0.025 because there is a 1-2% difference in our predictions
# because of the zero padding in SPPF module (line 280) maxpooling layers rather than the -infinity in torch.
# This difference does not make a difference "visually".
np.testing.assert_allclose(onnx_output, tiny_output, atol=5e-4, rtol=0.025)
np.testing.assert_allclose(onnx_output, tiny_output, atol=5e-4, rtol=0.01)
if __name__ == '__main__':
unittest.main()
+1 -1
View File
@@ -3,7 +3,7 @@ from tinygrad import Tensor, UOp, Device, nn
from tinygrad.schedule import schedule_cache
from tinygrad.codegen import to_program, to_program_cache
from tinygrad.schedule.indexing import apply_movement_op, _apply_reshape
from tinygrad.codegen.decomp.divandmod import fold_divmod_general
from tinygrad.uop.divandmod import fold_divmod_general
from test.test_tiny import TestTiny
def uops_allocated(): return sum([isinstance(x, UOp) for x in gc.get_objects()])
+3 -3
View File
@@ -40,7 +40,7 @@ def random_int_expr(depth=10):
def random_bool_expr(depth=10, expr1=None):
if depth == 0: return True
if expr1 is None: expr1 = random_int_expr(depth-1)
expr2 = random.choice([random_or_sub_expression_int(depth-1, expr1), UOp.const(dtypes.int, random.randint(-10, 10))])
expr2 = random.choice([random_or_sub_expression_int(depth-1, expr1), UOp.const(dtypes.index, random.randint(-10, 10))])
return random.choice(comp_ops)(expr1, expr2)
@@ -82,8 +82,8 @@ if __name__ == "__main__":
f"v2=Variable(\"{u2.arg[0]}\", {u2.arg[1]}, {u2.arg[2]})\n" +\
f"v3=Variable(\"{u3.arg[0]}\", {u3.arg[1]}, {u3.arg[2]})\n" +\
f"expr = {expr}\n" +\
f"v1_val, v2_val, v3_val = UOp.const(dtypes.int, {n1.as_long()}), UOp.const(dtypes.int, {n2.as_long()})," +\
f"UOp.const(dtypes.int, {n3.as_long()})\n" +\
f"v1_val, v2_val, v3_val = UOp.const(dtypes.index, {n1.as_long()}), UOp.const(dtypes.index, {n2.as_long()})," +\
f"UOp.const(dtypes.index, {n3.as_long()})\n" +\
"num = expr.simplify().substitute({v1:v1_val, v2:v2_val, v3:v3_val}).ssimplify()\n" +\
"rn = expr.substitute({v1:v1_val, v2:v2_val, v3:v3_val}).ssimplify()\n" +\
"assert num==rn, f\"{num} != {rn}\"\n"
+2 -2
View File
@@ -82,8 +82,8 @@ def eval_uop(uop:UOp, inputs:list[tuple[DType, list[Any]]]|None=None, vals:tuple
for buf_dt, data in inputs or []:
bufs.append(buf:=allocator.alloc(len(data) * buf_dt.itemsize))
allocator._copyin(buf, memoryview(struct.pack(str(len(data)) + (buf_dt.fmt or ""), *data)))
g = UOp.param(0, uop.dtype.ptr(1))
prg = to_program(UOp.store(g.index(UOp.const(dtypes.int, 0), ptr=True), uop).sink(arg=KernelInfo()), PythonRenderer(Target("PYTHON")))
g = UOp.param(0, uop.dtype, (1,))
prg = to_program(UOp.store(g.index(UOp.const(dtypes.int, 0)), uop).sink(arg=KernelInfo()), PythonRenderer(Target("PYTHON")))
prog = PythonProgram("run", PythonCompiler().compile(prg.src[2].arg))
prog(out_buf:=allocator.alloc(uop.dtype.itemsize), *bufs, vals=vals)
return out_buf.cast(uop.dtype.fmt or "").tolist()[0]
+15 -15
View File
@@ -423,10 +423,10 @@ def _collect_data_slices(assigns: list[tuple[str, UOp]], data_prefix: str, pcode
class _Ctx:
"""Context for instruction compilation - holds buffers and helpers."""
__slots__ = ('inst_size', 'dyn_fields', '_axis_id', 'wave_size', 'vgpr', 'accvgpr')
sgpr = UOp.param(0, dtypes.uint32.ptr(SGPR_COUNT))
vmem = UOp.param(2, dtypes.uint32.ptr(1 << 46))
lds = UOp.param(3, dtypes.uint32.ptr(16384))
scratch = UOp.param(4, dtypes.uint8.ptr(1 << 30))
sgpr = UOp.param(0, dtypes.uint32, (SGPR_COUNT,))
vmem = UOp.param(2, dtypes.uint32, (1 << 46,))
lds = UOp.param(3, dtypes.uint32, (16384,))
scratch = UOp.param(4, dtypes.uint8, (1 << 30,))
# Cache PARAM UOps by wave_size so all _Ctx instances with same wave_size share identical UOp references
_vgpr_cache: dict[int, UOp] = {}
_accvgpr_cache: dict[int, UOp] = {}
@@ -434,10 +434,10 @@ class _Ctx:
def __init__(self, inst_size: int, wave_size: int = 32):
self.inst_size, self._axis_id, self.wave_size = inst_size, 0, wave_size
self.dyn_fields: list[tuple[int, int]] = [] # (lo, hi) of fields read dynamically
if wave_size not in _Ctx._vgpr_cache: _Ctx._vgpr_cache[wave_size] = UOp.param(1, dtypes.uint32.ptr(256 * wave_size))
if wave_size not in _Ctx._vgpr_cache: _Ctx._vgpr_cache[wave_size] = UOp.param(1, dtypes.uint32, (256 * wave_size,))
self.vgpr = _Ctx._vgpr_cache[wave_size]
if wave_size == 64:
if wave_size not in _Ctx._accvgpr_cache: _Ctx._accvgpr_cache[wave_size] = UOp.param(5, dtypes.uint32.ptr(256 * wave_size))
if wave_size not in _Ctx._accvgpr_cache: _Ctx._accvgpr_cache[wave_size] = UOp.param(5, dtypes.uint32, (256 * wave_size,))
self.accvgpr = _Ctx._accvgpr_cache[wave_size]
else:
self.accvgpr = self.vgpr
@@ -463,7 +463,7 @@ class _Ctx:
"""Read instruction dword from vmem at PC + dword_idx*4."""
pc = self.rpc()
addr = pc if dword_idx == 0 else pc + UOp.const(dtypes.uint64, dword_idx * 4)
return self.vmem.index(addr >> UOp.const(dtypes.uint64, 2), ptr=True).load()
return self.vmem.index(addr >> UOp.const(dtypes.uint64, 2)).load()
def inst_field(self, field) -> UOp:
"""Extract field bits from instruction encoding. Tracks field for canonical key computation."""
@@ -516,14 +516,14 @@ class _Ctx:
# Dynamic register access (takes UOp index instead of int)
def rsgpr_dyn(self, reg: UOp, valid: UOp | None = None) -> UOp:
"""Read SGPR with dynamic register index."""
if valid is not None: return self.sgpr.index(reg.valid(valid), ptr=True).load()
return self.sgpr.index(reg, ptr=True).load()
if valid is not None: return self.sgpr.index(reg.valid(valid)).load()
return self.sgpr.index(reg).load()
def wsgpr_dyn(self, reg: UOp, val: UOp) -> UOp:
"""Write SGPR with dynamic register index. On RDNA, index 124 = NULL (writes discarded). On CDNA, index 124 = M0 (read/write)."""
# RDNA: NULL (124) discards writes. CDNA: M0 (124) is writable.
valid = None if self.wave_size == 64 else reg.ne(_c(124))
return self.sgpr.index(reg.valid(valid) if valid is not None else reg, ptr=True).store(val.cast(dtypes.uint32))
return self.sgpr.index(reg.valid(valid) if valid is not None else reg).store(val.cast(dtypes.uint32))
def wmask(self, reg: UOp, val: UOp) -> list[UOp]:
"""Write a lane mask (VCC/EXEC). Splits into lo/hi for wave64."""
@@ -540,7 +540,7 @@ class _Ctx:
def rvgpr_dyn(self, reg: UOp, lane: UOp, valid: UOp | None = None) -> UOp:
"""Read VGPR with dynamic register index."""
idx = reg.cast(dtypes.int) * _c(self.wave_size, dtypes.int) + lane.cast(dtypes.int)
return self.vgpr.index(idx.valid(valid), ptr=True).load() if valid is not None else self.vgpr.index(idx, ptr=True).load()
return self.vgpr.index(idx.valid(valid)).load() if valid is not None else self.vgpr.index(idx).load()
def wvgpr_dyn(self, reg: UOp, lane: UOp, val: UOp, exec_mask: UOp, after: UOp | None = None) -> UOp:
"""Write VGPR with dynamic register index."""
@@ -551,7 +551,7 @@ class _Ctx:
def raccvgpr_dyn(self, reg: UOp, lane: UOp, valid: UOp | None = None) -> UOp:
"""Read ACCVGPR with dynamic register index (CDNA only)."""
idx = reg.cast(dtypes.int) * _c(self.wave_size, dtypes.int) + lane.cast(dtypes.int)
return self.accvgpr.index(idx.valid(valid), ptr=True).load() if valid is not None else self.accvgpr.index(idx, ptr=True).load()
return self.accvgpr.index(idx.valid(valid)).load() if valid is not None else self.accvgpr.index(idx).load()
def waccvgpr_dyn(self, reg: UOp, lane: UOp, val: UOp, exec_mask: UOp, after: UOp | None = None) -> UOp:
"""Write ACCVGPR with dynamic register index (CDNA only)."""
@@ -711,7 +711,7 @@ class _Ctx:
# VGPR bit-slice: (vgpr_idx, rhs_val, hi_bit, lo_bit) - hi/lo are UOp constants
hi_bit, lo_bit = int(val[2].arg), int(val[3].arg)
width = hi_bit - lo_bit + 1
old = self.vgpr.index(val[0], ptr=True).load()
old = self.vgpr.index(val[0]).load()
new_val = _set_bits(old, _val_to_bits(val[1]), width, lo_bit).cast(dtypes.uint32)
active = _lane_active(exec_mask, lane)
if len(val) > 4: active = active & _to_bool(val[4])
@@ -2010,7 +2010,7 @@ def _compile_mubuf(inst: irc.MUBUF, ctx: _Ctx) -> UOp:
lds_addr = lds_base + lane.cast(dtypes.uint32) * _c(n_dwords * 4)
for i in range(n_dwords):
word_addr = (addr + UOp.const(dtypes.uint64, i * 4)) >> UOp.const(dtypes.uint64, 2)
val = in_bounds.where(mem.index(word_addr.cast(dtypes.int64), ptr=True).load(), _c(0))
val = in_bounds.where(mem.index(word_addr.cast(dtypes.int64)).load(), _c(0))
lds_idx = (lds_addr + _c(i * 4)) >> _c(2)
lds_slot = ctx.lds.index(lds_idx.valid(active))
stores.append(lds_slot.store(active.where(val, lds_slot)))
@@ -2023,7 +2023,7 @@ def _compile_mubuf(inst: irc.MUBUF, ctx: _Ctx) -> UOp:
else:
for i in range(n_dwords):
word_addr = (addr + UOp.const(dtypes.uint64, i * 4)) >> UOp.const(dtypes.uint64, 2)
val = in_bounds.where(mem.index(word_addr.cast(dtypes.int64).valid(in_bounds), ptr=True).load(), _c(0))
val = in_bounds.where(mem.index(word_addr.cast(dtypes.int64).valid(in_bounds)).load(), _c(0))
stores.append((ctx.waccvgpr_dyn if use_acc else ctx.wvgpr_dyn)(vdata + _c(i), lane, val, exec_mask))
return UOp.sink(UOp.group(*stores).end(lane), *ctx.inc_pc())
+32 -29
View File
@@ -77,7 +77,7 @@ def _val_to_bits(val):
return val if val.dtype == dtypes.uint32 else val.cast(dtypes.uint32)
def _floor(x):
t = UOp(Ops.TRUNC, x.dtype, (x,))
t = UOp(Ops.TRUNC, src=(x,))
return ((x < _const(x.dtype, 0)) & x.ne(t)).where(t - _const(x.dtype, 1), t)
def _f16_extract(v): return (v & _u32(0xFFFF)).cast(dtypes.uint16).bitcast(dtypes.half) if v.dtype == dtypes.uint32 else v
@@ -156,10 +156,10 @@ def _trig_reduce(x, phase=0.0):
turns, two_pi = match
if phase: turns = turns + _const(turns.dtype, phase)
n = _floor(turns + _const(turns.dtype, 0.5))
return UOp(Ops.SIN, turns.dtype, ((turns - n) * _const(turns.dtype, two_pi),))
return UOp(Ops.SIN, src=((turns - n) * _const(turns.dtype, two_pi),))
if phase: x = x + _const(x.dtype, phase * 6.283185307179586)
n = _floor(x * _const(x.dtype, 0.15915494309189535) + _const(x.dtype, 0.5))
return UOp(Ops.SIN, x.dtype, (x - n * _const(x.dtype, 6.283185307179586),))
return UOp(Ops.SIN, src=(x - n * _const(x.dtype, 6.283185307179586),))
def _signext(val: UOp) -> UOp:
for bits, mask, ext in [(4, 0xF, 0xFFFFFFF0), (8, 0xFF, 0xFFFFFF00), (16, 0xFFFF, 0xFFFF0000)]:
@@ -184,7 +184,7 @@ def _abs(val: UOp) -> UOp:
def _f_to_u(f, dt):
clamped = (f < _const(f.dtype, 0.0)).where(_const(f.dtype, 0.0), f)
truncated = UOp(Ops.TRUNC, f.dtype, (clamped,))
truncated = UOp(Ops.TRUNC, src=(clamped,))
return (truncated >= _const(f.dtype, 2**(dt.itemsize*8))).where(_const(dt, dt.max), truncated.cast(dt))
def _cvt_quiet(val: UOp) -> UOp:
@@ -230,7 +230,7 @@ def _ldexp(val: UOp, exp: UOp) -> UOp:
if val.dtype == dtypes.uint32: val = val.bitcast(dtypes.float32)
elif val.dtype == dtypes.uint64: val = val.bitcast(dtypes.float64)
if exp.dtype in (dtypes.uint32, dtypes.uint64): exp = exp.cast(dtypes.int if exp.dtype == dtypes.uint32 else dtypes.int64)
return val * UOp(Ops.EXP2, val.dtype, (exp.cast(val.dtype),))
return val * UOp(Ops.EXP2, src=(exp.cast(val.dtype),))
def _frexp_mant(val: UOp) -> UOp:
val = val.bitcast(dtypes.float32) if val.dtype == dtypes.uint32 else val.bitcast(dtypes.float64) if val.dtype == dtypes.uint64 else val
@@ -288,20 +288,20 @@ def _sad_u8(a: UOp, b: UOp, acc: UOp, masked: bool = False) -> UOp:
return result
_FUNCS: dict[str, Callable[..., UOp]] = {
'sqrt': lambda a: UOp(Ops.SQRT, a.dtype, (a,)), 'trunc': lambda a: UOp(Ops.TRUNC, a.dtype, (a,)),
'log2': lambda a: UOp(Ops.LOG2, a.dtype, (a,)), 'sin': lambda a: _trig_reduce(a),
'sqrt': lambda a: UOp(Ops.SQRT, src=(a,)), 'trunc': lambda a: UOp(Ops.TRUNC, src=(a,)),
'log2': lambda a: UOp(Ops.LOG2, src=(a,)), 'sin': lambda a: _trig_reduce(a),
'cos': lambda a: _trig_reduce(a, 0.25), 'floor': _floor, 'fract': lambda a: a - _floor(a),
'signext': _signext, 'abs': _abs,
'isEven': lambda a: (UOp(Ops.TRUNC, a.dtype, (a,)).cast(dtypes.int) & _const(dtypes.int, 1)).eq(_const(dtypes.int, 0)),
'max': lambda a, b: UOp(Ops.MAX, a.dtype, (a, b)),
'min': lambda a, b: UOp(Ops.MAX, a.dtype, (a.neg(), b.neg())).neg(),
'pow': lambda a, b: UOp(Ops.EXP2, dtypes.float32, (b.bitcast(dtypes.float32),)),
'isEven': lambda a: (UOp(Ops.TRUNC, src=(a,)).cast(dtypes.int) & _const(dtypes.int, 1)).eq(_const(dtypes.int, 0)),
'max': lambda a, b: UOp(Ops.MAX, src=(a, b)),
'min': lambda a, b: UOp(Ops.MAX, src=(a.neg(), b.neg())).neg(),
'pow': lambda a, b: UOp(Ops.EXP2, src=(b.bitcast(dtypes.float32),)),
'fma': lambda a, b, c: a * b + c,
'i32_to_f32': lambda a: a.cast(dtypes.int).cast(dtypes.float32),
'u32_to_f32': lambda a: a.cast(dtypes.uint32).cast(dtypes.float32),
'f32_to_i32': lambda a: UOp(Ops.TRUNC, dtypes.float32, (a.bitcast(dtypes.float32),)).cast(dtypes.int),
'f32_to_i32': lambda a: UOp(Ops.TRUNC, src=(a.bitcast(dtypes.float32),)).cast(dtypes.int),
'f32_to_u32': lambda a: _f_to_u(a.bitcast(dtypes.float32), dtypes.uint32),
'f64_to_i32': lambda a: UOp(Ops.TRUNC, dtypes.float64, (a.bitcast(dtypes.float64),)).cast(dtypes.int),
'f64_to_i32': lambda a: UOp(Ops.TRUNC, src=(a.bitcast(dtypes.float64),)).cast(dtypes.int),
'f64_to_u32': lambda a: _f_to_u(a.bitcast(dtypes.float64), dtypes.uint32),
'f16_to_f32': lambda a: _f16_extract(a).cast(dtypes.float32),
'f32_to_f16': lambda a: a.cast(dtypes.half),
@@ -309,8 +309,8 @@ _FUNCS: dict[str, Callable[..., UOp]] = {
'f64_to_f32': lambda a: a.bitcast(dtypes.float64).cast(dtypes.float32),
'i32_to_f64': lambda a: a.cast(dtypes.int).cast(dtypes.float64),
'u32_to_f64': lambda a: a.cast(dtypes.uint32).cast(dtypes.float64),
'f16_to_i16': lambda a: UOp(Ops.TRUNC, dtypes.half, (_f16_extract(a),)).cast(dtypes.int16),
'f16_to_u16': lambda a: UOp(Ops.TRUNC, dtypes.half, (_f16_extract(a),)).cast(dtypes.uint16),
'f16_to_i16': lambda a: UOp(Ops.TRUNC, src=(_f16_extract(a),)).cast(dtypes.int16),
'f16_to_u16': lambda a: UOp(Ops.TRUNC, src=(_f16_extract(a),)).cast(dtypes.uint16),
'i16_to_f16': lambda a: a.cast(dtypes.int16).cast(dtypes.half),
'u16_to_f16': lambda a: a.cast(dtypes.uint16).cast(dtypes.half),
'bf16_to_f32': lambda a: (((a.cast(dtypes.uint32) if a.dtype != dtypes.uint32 else a) & _u32(0xFFFF)) << _u32(16)).bitcast(dtypes.float32),
@@ -343,7 +343,7 @@ _FUNCS: dict[str, Callable[..., UOp]] = {
'i8_to_i32': lambda a: _signext(a.cast(dtypes.uint32) & _u32(0xFF)),
'i4_to_i32': lambda a: _signext_4bit(a.cast(dtypes.uint32) & _u32(0xF)),
# Float to int16 conversions
'v_cvt_i16_f32': lambda a: UOp(Ops.TRUNC, dtypes.float32, (a.bitcast(dtypes.float32),)).cast(dtypes.int16),
'v_cvt_i16_f32': lambda a: UOp(Ops.TRUNC, src=(a.bitcast(dtypes.float32),)).cast(dtypes.int16),
'v_cvt_u16_f32': lambda a: _f_to_u(a.bitcast(dtypes.float32), dtypes.uint16),
# SAD (Sum of Absolute Differences) - sum |a_i - b_i| for 4 bytes + accumulator
'v_sad_u8': lambda a, b, c: _sad_u8(a, b, c),
@@ -492,7 +492,10 @@ class Parser:
case '>=' | '<=' | '>' | '<' | '<>':
ops = {'>=':(lambda a,b:a>=b),'<=':(lambda a,b:a<=b),'>':(lambda a,b:a>b),'<':(lambda a,b:a<b),'<>':(lambda a,b:a.ne(b))}
return self._cmp_nan(left, right, ops[op])
case '>>' | '<<': return (left >> right) if op == '>>' else (left << right)
case '>>' | '<<':
if not dtypes.is_int(left.dtype): left = left.cast(dtypes.uint32)
if not dtypes.is_int(right.dtype): right = right.cast(dtypes.uint32)
return (left >> right) if op == '>>' else (left << right)
case '+' | '-':
if op == '-' and left.op == Ops.CONST and right.op == Ops.CONST: return _const(left.dtype, left.arg - right.arg)
return (left + right) if op == '+' else (left - right)
@@ -504,7 +507,7 @@ class Parser:
left, right = left.cast(pdt), right.cast(pdt)
if op == '*': return left * right
return (left // right) if dtypes.is_int(left.dtype) else (left / right)
case '**': return UOp(Ops.EXP2, left.dtype, (right.cast(left.dtype),)) if left.op == Ops.CONST and left.arg == 2.0 else left
case '**': return UOp(Ops.EXP2, src=(right.cast(left.dtype),)) if left.op == Ops.CONST and left.arg == 2.0 else left
_PREC = [('||',), ('&&',), ('|',), ('^',), ('&',), ('==', '!=', '<>'), ('>=', '<=', '>', '<'), ('>>', '<<'), ('+', '-'), ('*', '/'), ('**',)]
@@ -584,7 +587,7 @@ class Parser:
vgpr = self.vars.get('_vgpr')
if vgpr is None: return _u32(0)
ws = self.vars.get('_wave_size', 32)
return vgpr.index(_to_u32(reg) * _u32(ws) + _to_u32(lane), ptr=True).load()
return vgpr.index(_to_u32(reg) * _u32(ws) + _to_u32(lane)).load()
if self.try_eat('LPAREN'):
args = self._parse_args()
self.eat('RPAREN')
@@ -610,7 +613,7 @@ class Parser:
vgpr = self.vars.get('_vgpr')
if vgpr is None: return _u32(0)
ws = self.vars.get('_wave_size', 32)
return vgpr.index(_to_u32(reg) * _u32(ws) + _u32(int(idx)), ptr=True).load()
return vgpr.index(_to_u32(reg) * _u32(ws) + _u32(int(idx))).load()
elem = self.vars.get(f'{name}@{idx}', self.vars.get(f'{name}{idx}'))
if elem is None:
# Extract bit idx from base variable (like var[idx])
@@ -828,22 +831,22 @@ class Parser:
assert mem is not None, "memory load requires _vmem or _lds"
adt = dtypes.uint64 if addr.dtype == dtypes.uint64 else dtypes.uint32
active = self.vars.get('_active')
def mindex(idx:UOp, ptr=False): return mem.index(idx.valid(active) if active is not None else idx, ptr=ptr)
byte_mem = mem.dtype.base == dtypes.uint8
def mindex(idx:UOp): return mem.index(idx.valid(active) if active is not None else idx)
byte_mem = mem.dtype == dtypes.uint8
if byte_mem:
idx = addr
if dt in (dtypes.uint64, dtypes.int64, dtypes.float64):
val = _u32(0).cast(dtypes.uint64)
for i in range(8): val = val | (mindex(idx + _const(dtypes.int, i), ptr=True).load().cast(dtypes.uint64) << _u64(i * 8))
for i in range(8): val = val | (mindex(idx + _const(dtypes.int, i)).load().cast(dtypes.uint64) << _u64(i * 8))
elif dt in (dtypes.uint8, dtypes.int8):
val = mindex(idx, ptr=True).load().cast(dt)
val = mindex(idx).load().cast(dt)
elif dt in (dtypes.uint16, dtypes.int16, dtypes.short):
lo = mindex(idx, ptr=True).load().cast(dtypes.uint32)
hi = mindex(idx + _const(dtypes.int, 1), ptr=True).load().cast(dtypes.uint32)
lo = mindex(idx).load().cast(dtypes.uint32)
hi = mindex(idx + _const(dtypes.int, 1)).load().cast(dtypes.uint32)
val = (lo | (hi << _u32(8))).cast(dt)
else:
val = _u32(0)
for i in range(4): val = val | (mindex(idx + _const(dtypes.int, i), ptr=True).load().cast(dtypes.uint32) << _u32(i * 8))
for i in range(4): val = val | (mindex(idx + _const(dtypes.int, i)).load().cast(dtypes.uint32) << _u32(i * 8))
else:
idx = addr >> _const(addr.dtype, 2)
val = mindex(idx)
@@ -1304,7 +1307,7 @@ def parse_block(lines: list[str], start: int, env: dict[str, VarVal], funcs: dic
result = else_branch[0]
for c, rv in reversed(conditions):
if isinstance(rv, UOp) and isinstance(result, UOp):
if rv.dtype != result.dtype and rv.dtype.itemsize == result.dtype.itemsize: result = result.cast(rv.dtype)
if rv.dtype != result.dtype: result = result.cast(rv.dtype)
result = c.where(rv, result)
return i, block_assigns, result
# If statically true, use that branch directly; otherwise merge with WHERE
@@ -1325,7 +1328,7 @@ def parse_block(lines: list[str], start: int, env: dict[str, VarVal], funcs: dic
if isinstance(ba, dict) and var in ba:
tv = ba[var]
if isinstance(tv, UOp) and isinstance(res, UOp):
res = cond.where(tv, res.cast(tv.dtype) if tv.dtype != res.dtype and tv.dtype.itemsize == res.dtype.itemsize else res)
res = cond.where(tv, res.cast(tv.dtype) if tv.dtype != res.dtype else res)
block_assigns[var] = env[var] = res
# Merge side effects from branches with conditions
if assigns is not None:
+1 -1
View File
@@ -127,7 +127,7 @@ class TestBitcastConstFolding(unittest.TestCase):
def test_vec_bitcast(self):
with Context(SPEC=0):
srcs = full_rewrite(UOp.const(dtypes.int32.vec(3), (-1, -2**31, 75)).bitcast(dtypes.uint32.vec(3)).sink()).src
srcs = full_rewrite(UOp.const(dtypes.int32, (-1, -2**31, 75)).bitcast(dtypes.uint32).sink()).src
self.assertTrue(all(r.op is Ops.CONST and r.dtype == dtypes.uint32 for r in srcs))
self.assertEqual(tuple(x.arg for x in srcs), (2**32-1, 2**31, 75))
+2 -68
View File
@@ -1,76 +1,10 @@
import unittest, pickle
from tinygrad.tensor import Tensor
from tinygrad.dtype import dtypes, DType, ImageDType, PtrDType, to_dtype, Invalid, InvalidType
class TestImageDType(unittest.TestCase):
def test_image_scalar(self):
assert dtypes.imagef((10,10)).base.scalar() == dtypes.float32
assert dtypes.imageh((10,10)).base.scalar() == dtypes.float32
def test_image_vec(self):
assert dtypes.imagef((10,10)).base.vec(4) == dtypes.float32.vec(4)
assert dtypes.imageh((10,10)).base.vec(4) == dtypes.float32.vec(4)
class TestPtrDType(unittest.TestCase):
def test_vec_double(self):
dt1 = dtypes.float.vec(4).ptr().vec(4)
dt2 = dtypes.float.vec(4).ptr().vec(4)
self.assertEqual(dt1, dt2)
self.assertEqual(str(dt1), str(dt2))
def test_scalar(self):
dt = dtypes.float.vec(4).ptr().scalar()
self.assertEqual(dt.base, dtypes.float.vec(4))
dt = dtypes.float.vec(4).ptr().vec(4).scalar()
self.assertEqual(dt.base, dtypes.float.vec(4))
dt = dtypes.float.vec(4).scalar()
self.assertEqual(dt, dtypes.float)
def test_serialize(self):
dt = dtypes.float.vec(4).ptr().vec(4)
self.assertEqual(dt, eval(str(dt)))
def test_vec_ptr_sz(self):
dt = dtypes.float.ptr(1024).vec(4)
self.assertEqual(dt, eval(str(dt)))
self.assertEqual(str(dt), "dtypes.float.ptr(1024).vec(4)")
def test_vcount(self):
dt = dtypes.float.ptr().vec(4)
self.assertEqual(dt.vcount, 4)
self.assertEqual(dt.v, 4)
self.assertEqual(dt.count, 1)
dt = dtypes.float.vec(4).ptr()
self.assertEqual(dt.vcount, 1)
self.assertEqual(dt.v, 1)
self.assertEqual(dt.count, 4)
dt = dtypes.float.vec(4).ptr().vec(4)
self.assertEqual(dt.vcount, 4)
self.assertEqual(dt.v, 4)
self.assertEqual(dt.count, 4)
from tinygrad.dtype import dtypes, DType, to_dtype, Invalid, InvalidType
class TestEqStrDType(unittest.TestCase):
def test_image_ne(self):
if ImageDType is None: raise unittest.SkipTest("no ImageDType support")
assert dtypes.float == dtypes.float32, "float doesn't match?"
assert dtypes.imagef((1,2,4)) != dtypes.imageh((1,2,4)), "different image dtype doesn't match"
assert dtypes.imageh((1,2,4)) != dtypes.imageh((1,4,2)), "different shape doesn't match"
assert dtypes.imageh((1,2,4)) == dtypes.imageh((1,2,4)), "same shape matches"
assert isinstance(dtypes.imageh((1,2,4)), ImageDType)
def test_ptr_eq(self):
assert dtypes.float32.ptr() == dtypes.float32.ptr()
assert not (dtypes.float32.ptr() != dtypes.float32.ptr())
def test_ptr_nbytes(self):
assert dtypes.float16.ptr(32).nbytes() == 32 * dtypes.float16.itemsize
def test_ptr_nbytes_unlimited(self):
self.assertRaises(RuntimeError, lambda: dtypes.float32.ptr().nbytes())
def test_strs(self):
if PtrDType is None: raise unittest.SkipTest("no PtrDType support")
self.assertEqual(str(dtypes.imagef((1,2,4))), "dtypes.imagef((1, 2, 4))")
self.assertEqual(str(dtypes.float32.ptr(16)), "dtypes.float.ptr(16)")
self.assertEqual(str(dtypes.float32), "dtypes.float")
class TestToDtype(unittest.TestCase):
def test_dtype_to_dtype(self):
+16
View File
@@ -239,6 +239,22 @@ class TestTypePromotion(unittest.TestCase):
assert least_upper_dtype(dtypes.weakint, dtypes.float32) == dtypes.float32
assert least_upper_dtype(dtypes.weakint, dtypes.float64) == dtypes.float64
def test_weakfloat_promo(self):
# weakfloat is a float, but like weakint it is not one of dtypes.floats
assert dtypes.is_float(dtypes.weakfloat) and dtypes.weakfloat not in dtypes.floats
# weakfloat with itself is weakfloat
assert least_upper_dtype(dtypes.weakfloat, dtypes.weakfloat) == dtypes.weakfloat
# weakfloat is above bool, weakint and any concrete int (they defer up to it)
assert least_upper_dtype(dtypes.weakfloat, dtypes.bool) == dtypes.weakfloat
assert least_upper_dtype(dtypes.weakfloat, dtypes.weakint) == dtypes.weakfloat
assert least_upper_dtype(dtypes.weakfloat, dtypes.int32) == dtypes.weakfloat
assert least_upper_dtype(dtypes.weakfloat, dtypes.uint64) == dtypes.weakfloat
# weakfloat defers to any concrete float type
assert least_upper_dtype(dtypes.weakfloat, dtypes.fp8e4m3) == dtypes.fp8e4m3
assert least_upper_dtype(dtypes.weakfloat, dtypes.float16) == dtypes.float16
assert least_upper_dtype(dtypes.weakfloat, dtypes.float32) == dtypes.float32
assert least_upper_dtype(dtypes.weakfloat, dtypes.float64) == dtypes.float64
class TestTypeSpec(unittest.TestCase):
def setUp(self):
self.old_default_int, self.old_default_float = dtypes.default_int, dtypes.default_float
+14 -3
View File
@@ -24,10 +24,10 @@ class TestGroupedDims(unittest.TestCase):
total = math.prod(dims)
specials = sorted(dedup(flatten([[y for y in x.toposort() if y.op is Ops.SPECIAL] for x in idxs])), key=lambda u: u.arg)
# build flat index and primed flat (same expression with renamed SPECIALs)
flat = UOp.const(dtypes.weakint, 0)
flat = UOp.const(dtypes.index, 0)
for i, idx in enumerate(idxs):
flat = flat + idx * int(math.prod(dims[i+1:]))
flat_p = flat.substitute({s: UOp(Ops.SPECIAL, s.dtype, s.src, s.arg+"_p") for s in specials})
flat_p = flat.substitute({s: UOp(Ops.SPECIAL, src=s.src, arg=s.arg+"_p") for s in specials})
solver = z3.Solver()
[z3_flat, z3_flat_p] = uops_to_z3(solver, flat, flat_p)
# bounds
@@ -94,9 +94,20 @@ class TestGroupedDims(unittest.TestCase):
assert idxs[2].op is Ops.SPECIAL, f"expected SPECIAL for direct-mapped dim, got {idxs[2].op}"
assert idxs[3].op is Ops.SPECIAL, f"expected SPECIAL for direct-mapped dim, got {idxs[3].op}"
def test_grouped_dims_high_rank(self):
# 4D collapsed onto 2 axes
self._check_grouped_dims("gidx", (4,4,4,4), (16,16), False, [16,16])
# 4D untouched
self._check_grouped_dims("gidx", (2,3,4,5), None, False, [2,3,4,5])
idxs = get_grouped_dims("gidx", (2,3,4,5), None, False)
assert all(u.op is Ops.SPECIAL for u in idxs), f"expected all-SPECIAL when untouched, got {[u.op for u in idxs]}"
# 5D and 6D collapsed onto 3 axes
self._check_grouped_dims("gidx", (2,2,2,2,2), (4,4,4), False, [4,4,2])
self._check_grouped_dims("gidx", (2,2,2,2,2,2), (8,8,8), False, [8,4,2])
def test_global_prod_max(self):
g, l = UOp.range(256, 0, AxisType.GLOBAL), UOp.range(256, 1, AxisType.LOCAL)
sink = UOp.param(0, dtypes.float.ptr()).index(g + l).store(UOp.const(dtypes.float, 1.0)).end(g, l).sink(arg=KernelInfo())
sink = UOp.param(0, dtypes.float, (512,)).index(g + l).store(UOp.const(dtypes.float, 1.0)).end(g, l).sink(arg=KernelInfo())
class R(Renderer): global_max, local_max, global_prod_max = (256, 256, 256), (128, 128, 128), (128, 128, 128)
specials = [u for u in add_gpudims(R(Target()), sink).toposort() if u.op is Ops.SPECIAL]
self.assertGreater(len([s for s in specials if "lidx" in s.arg]), 1)
+18 -24
View File
@@ -107,30 +107,30 @@ class TestFoldingAndReduction(unittest.TestCase):
class TestModuloAndDivisionFolding(unittest.TestCase):
def test_full_graph_rewrite_modulo_folding_with_define_var(self):
# index dtype because div-mod rules only work on index
x_var_uop = UOp.variable('x', 0, 100).cast(dtypes.weakint)
x_var_uop = UOp.variable('x', 0, 100).cast(dtypes.index)
optimized_mod_uop = apply_rewrite(((x_var_uop * 4) + 2) % 4)
self.assertEqual(optimized_mod_uop.op, Ops.CONST)
self.assertEqual(optimized_mod_uop.arg, 2)
def test_full_graph_rewrite_division_folding_with_define_var(self):
# index dtype because div-mod rules only work on index
n_var_uop = UOp.variable('n', 1, 1000).cast(dtypes.weakint)
n_var_uop = UOp.variable('n', 1, 1000).cast(dtypes.index)
optimized_div_uop = apply_rewrite((n_var_uop * 6) // 3)
self.assertEqual(optimized_div_uop.op, Ops.MUL)
self.assertEqual(optimized_div_uop.src[1].arg, 2)
def test_full_graph_rewrite_complex_mod_div_folding(self):
# index dtype because div-mod rules only work on index
k_var_uop = UOp.variable('k', 0, 50).cast(dtypes.weakint)
k_var_uop = UOp.variable('k', 0, 50).cast(dtypes.index)
optimized_div_uop = apply_rewrite(((k_var_uop * 12 + 8) % 6) // 2)
self.assertEqual(optimized_div_uop.op, Ops.CONST)
self.assertEqual(optimized_div_uop.arg, 1)
def test_graph_rewrite_div_folding_bug(self):
lhs = UOp(Ops.ADD, dtypes.int.vec(4), src=(
UOp(Ops.STACK, dtypes.int.vec(4), arg=None, src=(UOp(Ops.SPECIAL, dtypes.int, arg='lidx0', src=(UOp.const(dtypes.int, 32),)),)*4),
UOp.const(dtypes.int.vec(4), (0, 256, 512, 768))))
rhs = UOp.const(dtypes.int.vec(4), 2)
lhs = UOp(Ops.ADD, src=(
UOp(Ops.STACK, arg=None, src=(UOp(Ops.SPECIAL, src=(UOp.const(dtypes.int, 32),), arg='lidx0'),)*4),
UOp.const(dtypes.int, (0, 256, 512, 768))))
rhs = UOp.const(dtypes.int, (2,)*4)
unopt = lhs<rhs
opt = apply_rewrite(unopt)
print(unopt)
@@ -140,7 +140,7 @@ class TestModuloAndDivisionFolding(unittest.TestCase):
def test_full_graph_rewrite_modulo_large_divisor(self):
# index dtype because div-mod rules only work on index
x_var_uop = UOp.variable('x', 1, 5)
self.assertIs(apply_rewrite(x_var_uop.cast(dtypes.weakint) % 10).render(simplify=False), x_var_uop.render(simplify=False))
self.assertIs(apply_rewrite(x_var_uop.cast(dtypes.index) % 10).render(simplify=False), x_var_uop.render(simplify=False))
def test_full_graph_rewrite_division_with_remainder(self):
x_var_uop = UOp.variable('x', 7, 9)
@@ -182,34 +182,28 @@ class TestEdgeCasesAndSpecialOperations(unittest.TestCase):
class TestGEPAndVectorizeRewrite(unittest.TestCase):
def test_gep_single_element_extraction(self):
# GEP on a vector dtype to extract a single element
base_vector = UOp.const(dtypes.float32.vec(4), (1.0, 2.0, 3.0, 4.0))
self.assertEqual(apply_rewrite(base_vector.gep(2)).arg, 3.0)
base_vector = UOp.const(dtypes.float32, (1.0, 2.0, 3.0, 4.0))
self.assertEqual(apply_rewrite(base_vector.index(2)).arg, 3.0)
def test_gep_tuple_extraction(self):
# GEP on a vector dtype to extract multiple elements as a vector
base_vector = UOp.const(dtypes.float32.vec(4), (1.0, 2.0, 3.0, 4.0))
self.assertEqual(list(apply_rewrite_values(base_vector.gep((2, 3)))), [3.0, 4.0])
base_vector = UOp.const(dtypes.float32, (1.0, 2.0, 3.0, 4.0))
self.assertEqual(list(apply_rewrite_values(UOp.stack(*[base_vector.index(i) for i in (2, 3)]))), [3.0, 4.0])
def test_gep_on_const_stack(self):
# GEP on a const STACK to extract a single element
const_stack = UOp.const(dtypes.float32.vec(4), (1.0, 2.0, 3.0, 4.0))
self.assertEqual(apply_rewrite(const_stack.gep(2)).arg, 3.0)
const_stack = UOp.const(dtypes.float32, (1.0, 2.0, 3.0, 4.0))
self.assertEqual(apply_rewrite(const_stack.index(2)).arg, 3.0)
def test_gep_tuple_on_const_stack(self):
# GEP on a const STACK using a tuple to extract multiple elements
const_stack = UOp.const(dtypes.float32.vec(4), (7.0, 8.0, 9.0, 10.0))
self.assertEqual(list(apply_rewrite_values(const_stack.gep((1, 3)))), [8.0, 10.0])
def test_gep_gep_simplification(self):
# Nested GEP simplification on a vector dtype
base_vector = UOp.const(dtypes.float32.vec(4), (10.0, 20.0, 30.0, 40.0))
gep_inner = base_vector.gep(1) # Extract 2nd element (20.0)
self.assertEqual(apply_rewrite(gep_inner.gep(0)).arg, 20.0)
const_stack = UOp.const(dtypes.float32, (7.0, 8.0, 9.0, 10.0))
self.assertEqual(list(apply_rewrite_values(UOp.stack(*[const_stack.index(i) for i in (1, 3)]))), [8.0, 10.0])
def test_vectorize_multiple_elements(self):
# Vectorizing multiple elements using GEP
base_vector = UOp.const(dtypes.float32.vec(4), (5.0, 10.0, 15.0, 20.0))
vectorized_uop = UOp(Ops.STACK, dtypes.float32.vec(4), src=(base_vector.gep(0), base_vector.gep(1), base_vector.gep(2), base_vector.gep(3)))
base_vector = UOp.const(dtypes.float32, (5.0, 10.0, 15.0, 20.0))
vectorized_uop = UOp(Ops.STACK, src=tuple(base_vector.index(i) for i in range(4)))
self.assertEqual(list(apply_rewrite_values(vectorized_uop)), [5.0, 10.0, 15.0, 20.0])
+1 -70
View File
@@ -1,6 +1,6 @@
import ctypes, gzip, unittest, timeit, pickle
from tinygrad import Variable
from tinygrad.helpers import Context, ContextVar, argfix, colored, word_wrap, is_numpy_ndarray, mv_address, get_contraction, count, all_same
from tinygrad.helpers import Context, ContextVar, argfix, colored, word_wrap, is_numpy_ndarray, mv_address, count, all_same
from tinygrad.helpers import merge_dicts, strip_parens, prod, round_up, fetch, fully_flatten, from_mv, to_mv, polyN, time_to_str, cdiv, cmod, getbits
from tinygrad.helpers import ceildiv, ansistrip, get_shape
from tinygrad.tensor import Tensor
@@ -273,75 +273,6 @@ class TestMemoryview(unittest.TestCase):
mva_us = timeit.timeit(lambda: mv_address(x), number=iters) * 1e6 / iters
print(f"from_mv vs mv_address: {fmv_us:8.3f} µs vs {mva_us:8.3f} µs")
class TestGetContraction(unittest.TestCase):
def test_contraction(self):
r = get_contraction((1,2,3,4), (2,3,4))
self.assertEqual(r, [[0, 1], [2], [3]])
r = get_contraction((2,1,3,4), (2,3,4))
self.assertEqual(r, [[0], [1, 2], [3]])
r = get_contraction((1,2,3,1,4), (1,2,3,4))
self.assertEqual(r, [[], [0, 1], [2], [3, 4]])
r = get_contraction((1,2,3,1,4,1,1), (2,3,4))
self.assertEqual(r, [[0, 1], [2], [3, 4, 5, 6]])
r = get_contraction((1,2,3,4), (1,2,3*4))
self.assertEqual(r, [[], [0, 1], [2, 3]])
r = get_contraction((1,2,3,4), (2,1,3,4))
self.assertEqual(r, [[0, 1], [], [2], [3]])
r = get_contraction((1,2,3,4), (1,1,2*3*4,1))
self.assertEqual(r, [[], [], [0,1,2,3], []])
r = get_contraction((2,1,3,4), (1,2,3,4))
self.assertEqual(r, [[], [0], [1, 2], [3]])
r = get_contraction((1,2,3,4), (2*3*4,1,1,1))
self.assertEqual(r, [[0, 1, 2, 3], [], [], []])
r = get_contraction((4,4,4,4), (16,1,16))
self.assertEqual(r, [[0, 1], [], [2, 3]])
r = get_contraction((1,2,3,4,1,1,1), (2,3,4))
self.assertEqual(r, [[0, 1], [2], [3, 4, 5, 6]])
r = get_contraction((1,2,3,4), (1,2,3,4,1))
self.assertEqual(r, [[], [0, 1], [2], [3], []])
r = get_contraction((14,1,384,14,1,1,1,1), (1,14,384,14))
self.assertEqual(r, [[], [0], [1,2], [3,4,5,6,7]])
r = get_contraction((14,1,384,1,14,1,1,1,1), (1,14,384,14))
self.assertEqual(r, [[], [0], [1,2], [3,4,5,6,7,8]])
r = get_contraction((512, 512), (1, 1, 512, 1, 1, 1, 1, 512))
self.assertEqual(r, [[], [], [0], [], [], [], [], [1]])
r = get_contraction((1,2,3,4), (1,2,6,2))
self.assertEqual(r, None)
def test_contraction_ones(self):
r = get_contraction((1,), (1,1,1))
self.assertEqual(r, [[], [], [0]])
r = get_contraction((1,1), (1,1,1))
self.assertEqual(r, [[], [], [0, 1]])
r = get_contraction((1,1,1,1), (1,))
self.assertEqual(r, [[0,1,2,3]])
r = get_contraction((1,1,1,1), (1,1))
self.assertEqual(r, [[], [0,1,2,3]])
r = get_contraction((1,1,1,1), (1,1,1))
self.assertEqual(r, [[], [], [0,1,2,3]])
r = get_contraction((1,1,1,1), (1,1,1,1))
self.assertEqual(r, [[], [], [], [0,1,2,3]])
class TestGetShape(unittest.TestCase):
def test_get_shape(self):
assert get_shape(2) == ()
+8 -8
View File
@@ -7,14 +7,14 @@ from tinygrad.codegen import to_program
class TestLinearizerFailures(unittest.TestCase):
def test_fail_1(self):
c0 = UOp.param(0, dtypes.float.ptr(64))
c1 = UOp.range(UOp.const(dtypes.weakint, 2), 1, AxisType.LOOP)
c2 = UOp.range(UOp.const(dtypes.weakint, 32), 2, AxisType.LOOP)
c3 = ((c1*UOp.const(dtypes.weakint, 32))+c2)
c4 = UOp.param(1, dtypes.float.ptr(163840))
c5 = UOp.range(UOp.const(dtypes.weakint, 2560), 0, AxisType.REDUCE)
c6 = c4.index(((((((c5//UOp.const(dtypes.weakint, 8))%UOp.const(dtypes.weakint, 8))*UOp.const(dtypes.weakint, 8))+(c5%UOp.const(dtypes.weakint, 8)))+(((c2*UOp.const(dtypes.weakint, 40))+(c5//UOp.const(dtypes.weakint, 64)))*UOp.const(dtypes.weakint, 64)))+(c1*UOp.const(dtypes.weakint, 81920))))
c7 = UOp.param(2, dtypes.float.ptr(64))
c0 = UOp.param(0, dtypes.float, (64,))
c1 = UOp.range(UOp.const(dtypes.index, 2), 1, AxisType.LOOP)
c2 = UOp.range(UOp.const(dtypes.index, 32), 2, AxisType.LOOP)
c3 = ((c1*UOp.const(dtypes.index, 32))+c2)
c4 = UOp.param(1, dtypes.float, (163840,))
c5 = UOp.range(UOp.const(dtypes.index, 2560), 0, AxisType.REDUCE)
c6 = c4.index(((((((c5//UOp.const(dtypes.index, 8))%UOp.const(dtypes.index, 8))*UOp.const(dtypes.index, 8))+(c5%UOp.const(dtypes.index, 8)))+(((c2*UOp.const(dtypes.index, 40))+(c5//UOp.const(dtypes.index, 64)))*UOp.const(dtypes.index, 64)))+(c1*UOp.const(dtypes.index, 81920))))
c7 = UOp.param(2, dtypes.float, (64,))
c8 = c7.index(c3)
c9 = ((((c6+(c8*UOp.const(dtypes.float, -1.0)))*(c6+(c8*UOp.const(dtypes.float, -1.0)))).reduce(c5, arg=Ops.ADD)*UOp.const(dtypes.float, 0.000390625))+UOp.const(dtypes.float, 1e-05)).sqrt().reciprocal()
c10 = c0.index(c3).store(c9).end(c1, c2)
+5 -1
View File
@@ -20,7 +20,11 @@ def _make_linear(buffer_lists, copies=None):
calls = []
for bufs in buffer_lists:
is_copy = len(bufs) == 2 and frozenset((id(bufs[0]), id(bufs[1]))) in copy_pairs
calls.append(UOp(Ops.CALL, dtypes.void, (UOp(Ops.COPY if is_copy else Ops.SINK), *bufs)))
if is_copy:
src0 = bufs[0].copy_to_device(bufs[1].device)
else:
src0 = UOp(Ops.SINK, src=tuple(bufs))
calls.append(UOp(Ops.CALL, src=(src0, *bufs)))
return UOp(Ops.LINEAR, src=tuple(calls))
def _get_arena(buf, linear, result):
+25 -25
View File
@@ -50,7 +50,7 @@ class TestPatternMatcher(unittest.TestCase):
def fxn(ctx, x):
ctx.append(True)
assert len(x.src) == 0
return x.replace(src=(UOp(Ops.DEVICE, arg="blah"),))
return x.replace(src=(UOp(Ops.NOOP),))
matcher = PatternMatcher([(UPat(Ops.CONST, src=(), name="x"), fxn)])
c1 = UOp.const(dtypes.float, 1.0)
# second rewrite shouldn't match anything
@@ -62,16 +62,16 @@ class TestPatternMatcher(unittest.TestCase):
def test_uop(self):
matcher = PatternMatcher([(UPat(Ops.CONST, name="x"), lambda x: x.rtag())])
c1 = UOp.const(dtypes.float, 1.0)
c2 = UOp(Ops.ADD, dtypes.float, (c1, c1))
c2 = UOp(Ops.ADD, src=(c1, c1))
self.assertEqual(matcher.rewrite(c1), c1.rtag())
self.assertEqual(matcher.rewrite(c2), None)
def test_uop_set(self):
matcher = PatternMatcher([(UPat((Ops.CONST, Ops.CAST), name="x"), lambda x: x.rtag())])
c1 = UOp.const(dtypes.bool, False)
c2 = UOp(Ops.CAST, dtypes.int, (c1,))
c2 = UOp(Ops.CAST, arg=dtypes.int, src=(c1,))
c3 = UOp.const(dtypes.float, 1.0)
c4 = UOp(Ops.ADD, dtypes.float, (c3, c3))
c4 = UOp(Ops.ADD, src=(c3, c3))
self.assertEqual(matcher.rewrite(c1), c1.rtag())
self.assertEqual(matcher.rewrite(c2), c2.rtag())
self.assertEqual(matcher.rewrite(c4), None)
@@ -84,8 +84,8 @@ class TestPatternMatcher(unittest.TestCase):
])
c1 = UOp.const(dtypes.float, 0.0)
c2 = UOp.const(dtypes.bool, False)
c3 = UOp(Ops.MAX, dtypes.float, (c1, c1))
c4 = UOp(Ops.MUL, dtypes.float, (c1, c1))
c3 = UOp(Ops.MAX, src=(c1, c1))
c4 = UOp(Ops.MUL, src=(c1, c1))
c5 = UOp.const(dtypes.int, -1)
self.assertEqual(matcher.rewrite(c1), c1.rtag())
self.assertEqual(matcher.rewrite(c2), c2.rtag())
@@ -101,11 +101,11 @@ class TestPatternMatcher(unittest.TestCase):
y1 = UOp.const(dtypes.int, 1)
y2 = UOp.const(dtypes.int, 2)
y3 = UOp.const(dtypes.int, -1)
c1 = UOp(Ops.MUL, dtypes.int, (y1, y2))
c2 = UOp(Ops.MUL, dtypes.int, (y2, y2))
c3 = UOp(Ops.MUL, dtypes.int, (y3, y2))
c4 = UOp(Ops.MUL, dtypes.int, (y2, y1))
c5 = UOp(Ops.MUL, dtypes.int, (y2, y3))
c1 = UOp(Ops.MUL, src=(y1, y2))
c2 = UOp(Ops.MUL, src=(y2, y2))
c3 = UOp(Ops.MUL, src=(y3, y2))
c4 = UOp(Ops.MUL, src=(y2, y1))
c5 = UOp(Ops.MUL, src=(y2, y3))
self.assertEqual(matcher.rewrite(c1), c1.rtag())
self.assertEqual(matcher.rewrite(c2), None)
self.assertEqual(matcher.rewrite(c3), c3.rtag())
@@ -116,8 +116,8 @@ class TestPatternMatcher(unittest.TestCase):
matcher = PatternMatcher([(UPat(GroupOp.ALU, name="x", src=(UPat(Ops.CONST, name="y"), UPat(Ops.CONST, name="y"))), lambda x, y: x.rtag())])
y1 = UOp.const(dtypes.float, 1.0)
y2 = UOp.const(dtypes.float, 1.0)
c1 = UOp(Ops.ADD, dtypes.float, (y1, y1))
c2 = UOp(Ops.ADD, dtypes.float, (y1, y2))
c1 = UOp(Ops.ADD, src=(y1, y1))
c2 = UOp(Ops.ADD, src=(y1, y2))
self.assertEqual(matcher.rewrite(c1), c1.rtag())
self.assertEqual(matcher.rewrite(c2), c1.rtag())
@@ -143,14 +143,14 @@ class TestPatternMatcher(unittest.TestCase):
matcher = PatternMatcher([(UPat(GroupOp.ALU, name="x", src=(UPat(Ops.CONST), UPat(Ops.CONST))), lambda x: x.rtag())])
c1 = UOp.const(dtypes.float, 1.0)
c2 = UOp.const(dtypes.float, 2.0)
c3 = UOp(Ops.ADD, dtypes.float, (c1,c2))
c3 = UOp(Ops.ADD, src=(c1,c2))
self.assertEqual(matcher.rewrite(c3), c3.rtag())
self.assertEqual(matcher.rewrite(c2), None)
# that CONST/ALU -> ALU/CONST rewrite is now instant
"""
matcher = PatternMatcher([(UPat(GroupOp.ALU, name="x", src=(UPat(Ops.CONST), UPat(GroupOp.ALU))), lambda x: x)])
c4 = UOp(Ops.ADD, dtypes.float, (c1,c3))
c5 = UOp(Ops.ADD, dtypes.float, (c3,c1))
c4 = UOp(Ops.ADD, src=(c1,c3))
c5 = UOp(Ops.ADD, src=(c3,c1))
self.assertEqual(matcher.rewrite(c3), None)
self.assertEqual(matcher.rewrite(c4), c4)
self.assertEqual(matcher.rewrite(c5), None)
@@ -160,10 +160,10 @@ class TestPatternMatcher(unittest.TestCase):
matcher = PatternMatcher([(UPat(GroupOp.ALU, name="x", src=[UPat(Ops.CONST), UPat(GroupOp.ALU)]), lambda x: x.rtag())])
c1 = UOp.const(dtypes.float, 1.0)
c2 = UOp.const(dtypes.float, 2.0)
c3 = UOp(Ops.ADD, dtypes.float, (c1,c2))
c4 = UOp(Ops.ADD, dtypes.float, (c3,c2))
c5 = UOp(Ops.ADD, dtypes.float, (c2,c3))
c6 = UOp(Ops.ADD, dtypes.float, (c3,c4))
c3 = UOp(Ops.ADD, src=(c1,c2))
c4 = UOp(Ops.ADD, src=(c3,c2))
c5 = UOp(Ops.ADD, src=(c2,c3))
c6 = UOp(Ops.ADD, src=(c3,c4))
self.assertEqual(matcher.rewrite(c3), None)
self.assertEqual(matcher.rewrite(c4), c4.rtag())
self.assertEqual(matcher.rewrite(c5), c5.rtag())
@@ -173,8 +173,8 @@ class TestPatternMatcher(unittest.TestCase):
matcher = PatternMatcher([(UPat(GroupOp.ALU, name="x", src=UPat(Ops.CONST)), lambda x: x.rtag())])
c1 = UOp.const(dtypes.float, 1.0)
c2 = UOp.const(dtypes.float, 2.0)
c3 = UOp(Ops.ADD, dtypes.float, (c1,c2))
c4 = UOp(Ops.ADD, dtypes.float, (c2,c3))
c3 = UOp(Ops.ADD, src=(c1,c2))
c4 = UOp(Ops.ADD, src=(c2,c3))
self.assertEqual(matcher.rewrite(c3), c3.rtag())
self.assertEqual(matcher.rewrite(c4), None)
@@ -183,9 +183,9 @@ class TestPatternMatcher(unittest.TestCase):
c1 = UOp.const(dtypes.float, 1.0)
c2 = UOp.const(dtypes.float, 2.0)
c3 = UOp.const(dtypes.float, 3.0)
c4 = UOp(Ops.EXP2, dtypes.float, (c1,))
c5 = UOp(Ops.ADD, dtypes.float, (c1,c2))
c6 = UOp(Ops.MULACC, dtypes.float, (c1,c2,c3))
c4 = UOp(Ops.EXP2, src=(c1,))
c5 = UOp(Ops.ADD, src=(c1,c2))
c6 = UOp(Ops.MULACC, src=(c1,c2,c3))
self.assertEqual(matcher.rewrite(c4), None)
self.assertEqual(matcher.rewrite(c5), None)
self.assertEqual(matcher.rewrite(c6), c6.rtag())
+5 -6
View File
@@ -266,7 +266,7 @@ class TestSchedule(unittest.TestCase):
x = Tensor.empty(big_enough).realize()
with Context(SPLIT_REDUCEOP=1):
out = (x - x.max(keepdim=True)).max()
check_schedule(out, 4)
check_schedule(out, 3)
def test_example_matmul_contig(self):
x = Tensor.eye(64).clone().realize()
@@ -355,8 +355,7 @@ class TestSchedule(unittest.TestCase):
b = Tensor.empty((1, 16)).realize()
out0 = a.sum() + 2
out1 = a.sum() + b
# check_schedule([out0, out1], 2)
check_schedule([out0, out1], 3)
check_schedule([out0, out1], 2)
def test_scaled_dot_product_attention_multireduce_fusion(self):
q = Tensor.empty(32,8,16,8).realize()
@@ -546,8 +545,7 @@ class TestSchedule(unittest.TestCase):
a = Tensor.empty(3, 4, 5).abs().realize()
b = Tensor.empty(3, 4, 5).abs().realize()
out = (a.log2().pad(((0, 1), (0, 1), (0, 1)), value=1.0).sum()+b).abs().log2().pad(((0, 1), (0, 1), (0, 1)), value=1.0).sum().contiguous()
# check_schedule(out, 1)
check_schedule(out, 2)
check_schedule(out, 1)
def test_shrink_pad_safe(self):
a = Tensor.ones((3, )).contiguous().realize()
@@ -601,7 +599,8 @@ class TestSchedule(unittest.TestCase):
p = P[0]
p = p.pad(((1, 0), ))
p = p.repeat([2])
check_schedule(p, 3)
# TODO: this should be 3 if fix store hazard worked correctly
check_schedule(p, 4)
def test_conv2d(self, allowed=4, dtype=dtypes.float):
old_default_float, dtypes.default_float = dtypes.default_float, dtype
+23 -23
View File
@@ -1,6 +1,6 @@
import unittest, itertools
from tinygrad.codegen.late.devectorizer import indexing_simplify
from tinygrad.codegen.late.coalese import indexing_simplify
from tinygrad.dtype import dtypes
from tinygrad.uop.ops import UOp, Ops, graph_rewrite
from tinygrad.uop.symbolic import simplify_valid, sym, pm_move_where_on_load
@@ -14,16 +14,16 @@ def simplify_valid_idx(sink: UOp) -> UOp: return graph_rewrite(sink, sym+pm_move
def simplify_image_idx(sink: UOp) -> UOp: return graph_rewrite(sink, sym+pm_move_where_on_load+indexing_simplify, name="simplify_image_idx")
def get_gated_load_uop(valid:UOp, idx:UOp):
return UOp(Ops.LOAD, dtypes.float, (
UOp.param(0, dtypes.float.ptr()).index(idx.valid(valid), ptr=True),
return UOp(Ops.LOAD, src=(
UOp.param(0, dtypes.float, (1024,)).index(idx.valid(valid)),
))
def get_load_image_uop(image_shape:tuple[int, ...], valid:UOp, idx:tuple[UOp, UOp]):
return UOp(Ops.LOAD, dtypes.float.vec(4), (
UOp.param(0, dtypes.imagef(image_shape)).index(idx[1].valid(valid), idx[0].valid(valid), ptr=True),
return UOp(Ops.LOAD, src=(
UOp.param(0, dtypes.float, image_shape).index(idx[1].valid(valid), idx[0].valid(valid)),
))
def Special(expr, nmax): return UOp(Ops.SPECIAL, dtypes.weakint, (UOp.const(dtypes.weakint, nmax),), expr)
def Special(expr, nmax): return UOp(Ops.SPECIAL, src=(UOp.const(dtypes.index, nmax),), arg=expr)
def Variable(expr, nmin, nmax): return UOp.variable(expr, nmin, nmax)
def Range(n, nmax): return UOp.range(nmax, n)
@@ -455,7 +455,7 @@ class TestImageSimplification(unittest.TestCase):
A1 = lidx0*32 + r0*32 + lidx1*4 - 99
valid = ((lidx1 < 1).ne(True)) & ((lidx0 + r0) < 3).ne(True) & ((lidx0 + r0) < 19)
alu0 = gidx0 + (A1 % 32)*32 + (A1 // 32 % 16)*1024
load = get_load_image_uop((1, 16384, 4), valid, (alu0, UOp.const(dtypes.weakint, 0)))
load = get_load_image_uop((1, 16384, 4), valid, (alu0, UOp.const(dtypes.index, 0)))
try:
self.check(load, None, "(gidx0+lidx0*1024+r0*1024+lidx1*128+-3168)", "0")
except AssertionError:
@@ -474,7 +474,7 @@ class TestImageSimplification(unittest.TestCase):
A1 = lidx0*16 + r0*16 + lidx1*4 - 51
valid = ((lidx1 < 1).ne(True)) & ((lidx0 + r0) < 3).ne(True) & ((lidx0 + r0) < 11)
alu0 = lidx2 + gidx0*4 + (A1 % 16)*64 + (A1 // 16 % 8)*1024
load = get_load_image_uop((1, 8192, 4), valid, (alu0, UOp.const(dtypes.weakint, 0)))
load = get_load_image_uop((1, 8192, 4), valid, (alu0, UOp.const(dtypes.index, 0)))
try:
self.check(load, None, "(lidx2+gidx0*4+lidx0*1024+r0*1024+lidx1*256+-3264)", "0")
except AssertionError:
@@ -488,20 +488,20 @@ class TestImageSimplification(unittest.TestCase):
gidx0 = Special("gidx0", 1064)
r12 = Range(12, 3)
valid = ((gidx0 < 645).ne(True)) & (gidx0 < 653)
idx = (r12*4 + (gidx0+3)%4 + (gidx0+3)//4*24 - 3888, UOp.const(dtypes.weakint, 0))
idx = (r12*4 + (gidx0+3)%4 + (gidx0+3)//4*24 - 3888, UOp.const(dtypes.index, 0))
load = get_load_image_uop((1, 48, 4), valid, idx)
self.check(load, None, "(r12*4+(gidx0+3)%4+(gidx0+3)//4*24+-3888)", "0")
class TestDropTrueGate(unittest.TestCase):
def test_drop_true_gate_on_index(self):
# test that INDEX with a constant True valid gets simplified to drop the valid
from tinygrad.codegen.late.devectorizer import indexing_simplify
from tinygrad.codegen.late.coalese import indexing_simplify
from tinygrad.uop.ops import graph_rewrite
from tinygrad.uop.symbolic import sym
buf = UOp.param(0, dtypes.int.ptr())
idx = UOp.const(dtypes.weakint, 0)
buf = UOp.param(0, dtypes.int, (1,))
idx = UOp.const(dtypes.index, 0)
true_gate = UOp.const(dtypes.bool, True)
index_with_gate = UOp(Ops.INDEX, dtypes.int.ptr(), (buf, idx.valid(true_gate)))
index_with_gate = UOp(Ops.INDEX, src=(buf, idx.valid(true_gate)))
# apply the optimization
result = graph_rewrite(index_with_gate, sym+indexing_simplify)
# the True valid should be dropped (INDEX should only have 2 sources)
@@ -516,7 +516,7 @@ class TestRangeShrink(unittest.TestCase):
def test_range_shrink_single_guard(self):
# range 0..203 guarded by r < 4 everywhere -> shrink to 0..3
r = Range(0, 204)
load = get_gated_load_uop(r < UOp.const(dtypes.weakint, 4), r)
load = get_gated_load_uop(r < UOp.const(dtypes.index, 4), r)
ranges = self.get_ranges(load.sink())
self.assertEqual(len(ranges), 1)
self.assertEqual(ranges[0].src[0].arg, 4)
@@ -524,8 +524,8 @@ class TestRangeShrink(unittest.TestCase):
def test_range_shrink_picks_max_guard(self):
# two loads guard the same range with r < 4 and r < 8 -> shrink to max(4, 8) = 8
r = Range(0, 204)
load1 = get_gated_load_uop(r < UOp.const(dtypes.weakint, 4), r)
load2 = get_gated_load_uop(r < UOp.const(dtypes.weakint, 8), r)
load1 = get_gated_load_uop(r < UOp.const(dtypes.index, 4), r)
load2 = get_gated_load_uop(r < UOp.const(dtypes.index, 8), r)
ranges = self.get_ranges(UOp.sink(load1, load2))
self.assertEqual(len(ranges), 1)
self.assertEqual(ranges[0].src[0].arg, 8)
@@ -533,7 +533,7 @@ class TestRangeShrink(unittest.TestCase):
def test_range_no_shrink_guard_ge_max(self):
# guard r < 300 with range max 204 -> no shrink (guard doesn't constrain)
r = Range(0, 204)
load = get_gated_load_uop(r < UOp.const(dtypes.weakint, 300), r)
load = get_gated_load_uop(r < UOp.const(dtypes.index, 300), r)
ranges = self.get_ranges(load.sink())
self.assertEqual(len(ranges), 1)
self.assertEqual(ranges[0].src[0].arg, 204)
@@ -541,8 +541,8 @@ class TestRangeShrink(unittest.TestCase):
def test_range_no_shrink_when_unguarded_elsewhere(self):
# one load guards r < 4, but another load uses r without a gate -> no shrink
r = Range(0, 204)
load1 = get_gated_load_uop(r < UOp.const(dtypes.weakint, 4), r)
load2 = UOp(Ops.LOAD, dtypes.float, (UOp.param(1, dtypes.float.ptr()).index(r, ptr=True),))
load1 = get_gated_load_uop(r < UOp.const(dtypes.index, 4), r)
load2 = UOp(Ops.LOAD, src=(UOp.param(1, dtypes.float, (204,)).index(r),))
ranges = self.get_ranges(UOp.sink(load1, load2))
self.assertEqual(len(ranges), 1)
self.assertEqual(ranges[0].src[0].arg, 204)
@@ -550,7 +550,7 @@ class TestRangeShrink(unittest.TestCase):
def test_range_no_shrink_when_used_in_reduce(self):
# range used in both a gated load AND directly in the reduce expression -> no shrink
r = Range(0, 204)
gated_load = get_gated_load_uop(r < UOp.const(dtypes.weakint, 4), r)
gated_load = get_gated_load_uop(r < UOp.const(dtypes.index, 4), r)
red = (r.cast(dtypes.float) + gated_load).reduce(r, arg=Ops.ADD)
ranges = self.get_ranges(red.sink())
self.assertEqual(len(ranges), 1)
@@ -559,7 +559,7 @@ class TestRangeShrink(unittest.TestCase):
def test_range_shrink_to_single_iteration(self):
# guard r < 1 shrinks range to 1 -> single iteration, range eliminated entirely
r = Range(0, 204)
load = get_gated_load_uop(r < UOp.const(dtypes.weakint, 1), r)
load = get_gated_load_uop(r < UOp.const(dtypes.index, 1), r)
ranges = self.get_ranges(load.sink())
self.assertEqual(len(ranges), 0)
@@ -568,7 +568,7 @@ class TestRangeShrink(unittest.TestCase):
from tinygrad.dtype import Invalid
r = Range(0, 204)
x = (r < 4).where(UOp.const(dtypes.float, 1), Invalid)
ranges = self.get_ranges(UOp.param(0, dtypes.float.ptr()).index(r).store((r < 4).where(x, 0)).sink())
ranges = self.get_ranges(UOp.param(0, dtypes.float, (204,)).index(r).store((r < 4).where(x, 0)).sink())
self.assertEqual(len(ranges), 1)
self.assertEqual(ranges[0].src[0].arg, 4)
@@ -577,7 +577,7 @@ class TestRangeShrink(unittest.TestCase):
from tinygrad.dtype import Invalid
r = Range(0, 204)
x = (r < 4).where(UOp.const(dtypes.float, 1), Invalid)
ranges = self.get_ranges(UOp.param(0, dtypes.float.ptr()).index(r).store((r < 4).where(0, x)).sink())
ranges = self.get_ranges(UOp.param(0, dtypes.float, (204,)).index(r).store((r < 4).where(0, x)).sink())
self.assertEqual(len(ranges), 1)
self.assertEqual(ranges[0].src[0].arg, 4)
+17
View File
@@ -1,6 +1,23 @@
import unittest
from tinygrad import Variable
from tinygrad.tensor import Tensor
from tinygrad.uop.ops import _broadcast_shape
class TestBroadcastShape(unittest.TestCase):
def test_symbolic(self):
v = Variable("v", 1, 10)
self.assertEqual(_broadcast_shape((v,), (1,)), (v,))
self.assertEqual(_broadcast_shape((v,), ()), (v,))
self.assertEqual(_broadcast_shape((v,), (v,)), (v,))
with self.assertRaises(IndexError): _broadcast_shape((v,), (5,))
def test_symbolic_vmin_zero(self):
# a symbolic dim that may be 0 still broadcasts against 1 to itself
v0 = Variable("v0", 0, 10)
self.assertEqual(_broadcast_shape((v0,), (1,)), (v0,))
self.assertEqual(_broadcast_shape((v0,), ()), (v0,))
self.assertEqual(_broadcast_shape((3, v0), (3, 1)), (3, v0))
with self.assertRaises(IndexError): _broadcast_shape((v0,), (5,))
class TestSymbolic(unittest.TestCase):
def assert_tuple_equal(self, x, y):
+1 -1
View File
@@ -69,7 +69,7 @@ class TestIdxUpcast(unittest.TestCase):
if not isinstance(Device[Device.DEFAULT].renderer, (PTXRenderer, NIRRenderer)):
assert idx.op is Ops.INDEX
idx_val = idx.src[1]
self.assertFalse(idx_val.overflows(idx_val.dtype.base.scalar()))
self.assertFalse(idx_val.overflows(idx_val.dtype.scalar()))
# use expand to generate kernel that uses large idx
def do_op_then_assert(self, dtype: DType, dim1, dim2, dim3):
+18 -3
View File
@@ -222,6 +222,10 @@ class TestTensorUOpBitcast(unittest.TestCase):
t = _t(4)
self.assertIs(t.bitcast("uint32").uop, t.uop.bitcast("uint32"))
self.assertIs(t.uop.bitcast("uint32").dtype, dtypes.uint32)
def test_bitcast_same_and_diff_size(self):
_check(self, _t(4).float(), lambda x: x.bitcast(dtypes.uint32)) # same size
_check(self, _t(4).cast(dtypes.uint8), lambda x: x.bitcast(dtypes.uint16)) # widen: uint8[4] -> uint16[2]
_check(self, _t(4).cast(dtypes.uint16), lambda x: x.bitcast(dtypes.uint8)) # narrow: uint16[4] -> uint8[8]
class TestTensorUOpRand(unittest.TestCase):
def test_random_bits(self):
@@ -372,6 +376,13 @@ class TestTensorUOpStack(unittest.TestCase):
def test_stack_dim1(self): _check(self, _t(2, 3), lambda x: x.stack(x, dim=1))
def test_stack_3tensors(self): _check(self, _t(2, 3), lambda x: x.stack(x, x, dim=0))
def test_stack_new_last(self): _check(self, _t(2, 3), lambda x: x.stack(x, dim=-1))
def test_stack_mixed_dtype(self):
w = _t(2, 3).float()
_check(self, _t(2, 3), lambda x: x.stack(w if isinstance(x, Tensor) else w.uop))
self.assertIs(_t(2, 3).uop.stack(w.uop).dtype, dtypes.float32)
def test_stack_index_dtype(self):
# index is outside the promotion lattice, equal dtypes bypass promotion
self.assertEqual(UOp.const(dtypes.index, 1).stack(UOp.const(dtypes.index, 2)).shape, (2,))
class TestTensorUOpConv2d(unittest.TestCase):
def test_conv2d_basic(self):
@@ -420,6 +431,10 @@ class TestTensorUOpConv2d(unittest.TestCase):
w = _t(1, 1, 2, 2).float()
_check(self, _t(1, 1, 3, 3).float(), lambda x: x.conv_transpose2d(w if isinstance(x, Tensor) else w.uop, stride=2))
class TestTensorUOpHashing(unittest.TestCase):
def test_keccak_sha3_256(self): _check(self, _t(8).cast(dtypes.uint8), lambda x: x.keccak())
def test_keccak_shake_128(self): _check(self, _t(8).cast(dtypes.uint8), lambda x: x.keccak("shake_128"))
class TestTensorUOpEinsum(unittest.TestCase):
def test_einsum_dot(self): _check(self, _t(2, 3), lambda x: type(x).einsum("ij,ij->", x, x))
def test_einsum_transpose(self): _check(self, _t(2, 3), lambda x: type(x).einsum("ij->ji", x))
@@ -456,7 +471,6 @@ class TestTensorUOpSVD(unittest.TestCase):
def test_svd_batched(self): self._check(_t(2, 2, 2).float())
def test_svd_nonfull(self): self._check(_t(3, 2).float(), full_matrices=False)
# UOp.empty / UOp.empty_like are the canonical buffer allocators; Tensor.empty / Tensor.empty_like just forward.
class TestUOpEmpty(unittest.TestCase):
def test_empty_dtype_string(self):
self.assertEqual(UOp.empty((3, 4), dtype="float32").dtype, dtypes.float32)
@@ -475,11 +489,12 @@ class TestUOpEmpty(unittest.TestCase):
self.assertTrue(u.has_buffer_identity())
def test_empty_direct_singleton_tuple_device(self):
# regression: direct UOp.empty with a singleton-tuple device + axis must not trip .multi()'s tuple assert
u = UOp.empty((4,), dtype=dtypes.float32, device=("NULL:0",), axis=0)
u = UOp.empty((4,), dtype=dtypes.float32, device=("NULL:0",))
self.assertEqual((u.shape, u.device, u.axis), ((4,), "NULL", None))
class TestTensorUOpCreation(unittest.TestCase):
def test_empty(self):
self.assertIs(_strip_unique(Tensor.empty(2, 3).uop), _strip_unique(UOp.empty(2, 3)))
def test_full(self):
self.assertIs(_strip_unique(Tensor.full((2, 3), 42).uop), _strip_unique(UOp.full((2, 3), 42)))
def test_full_kwargs(self):
-26
View File
@@ -1,26 +0,0 @@
import unittest
from tinygrad import Tensor
class TestLoadStore(unittest.TestCase):
def test_load_shape(self):
t = Tensor(bytes(16)).fs_load(1024)
assert t.shape == (1024,), t.shape
t.schedule_linear()
def test_store_shape(self):
t = Tensor.zeros(1024).fs_store()
assert t.shape == (16,), t.shape
t.schedule_linear()
def test_load_large_shape(self):
t = Tensor(bytes(16)).fs_load(10_000_000)
assert t.shape == (10_000_000,), t.shape
t.schedule_linear()
def test_store_large_shape(self):
t = Tensor.zeros(10_000_000).fs_store()
assert t.shape == (16,), t.shape
t.schedule_linear()
if __name__ == "__main__":
unittest.main()
+2 -2
View File
@@ -9,8 +9,8 @@ class TestTranscendentalFunctions(unittest.TestCase):
def test_payne_hanek_reduction(self):
# TODO: Test constant input when constant folding is fixed (or maybe test both variants)
# Load input value from a buffer to prevent constant folding
input_buf = UOp.param(1, dtypes.double.ptr(1))
loaded_value = input_buf.index(UOp.const(dtypes.int, 0), ptr=True).load()
input_buf = UOp.param(1, dtypes.double, (1,))
loaded_value = input_buf.index(UOp.const(dtypes.int, 0)).load()
def eval_payne_hanek_reduction(v:float) -> tuple[float, int]:
return tuple(eval_uop(u, [(dtypes.float64, [v])]) for u in payne_hanek_reduction(loaded_value))
+80 -243
View File
@@ -2,9 +2,8 @@ import unittest, pytest
from tinygrad import dtypes, Variable
from tinygrad.dtype import AddrSpace
from tinygrad.helpers import DEBUG, Context
from tinygrad.uop.ops import Ops, UOp, UPat, PatternMatcher, track_rewrites, graph_rewrite, GroupOp, AxisType
from tinygrad.uop.ops import Ops, UOp, UPat, PatternMatcher, graph_rewrite, GroupOp, AxisType
from tinygrad.uop.symbolic import sym
from tinygrad.codegen.late.expander import expander
from test.helpers import to_uops_list
simple_pm = PatternMatcher([
@@ -21,19 +20,12 @@ def const_values(u:UOp):
class TestGraphRewriteConst(unittest.TestCase):
def test_gep_const(self):
v1 = UOp.const(dtypes.int.vec(3), (0,1,2))
v2 = v1.gep(1)
v1 = UOp.const(dtypes.int, (0,1,2))
v2 = v1.index(1)
ret = graph_rewrite(v2, sym)
self.assertEqual(ret.dtype, dtypes.int)
self.assertEqual(ret.arg, 1)
def test_gep_const_single(self):
v1 = UOp.const(dtypes.int.vec(3), 4)
v2 = v1.gep(1)
ret = graph_rewrite(v2, sym)
self.assertEqual(ret.dtype, dtypes.int)
self.assertEqual(ret.arg, 4)
def test_add_const(self):
v1 = UOp.const(dtypes.int, (0,1,2))
v2 = UOp.const(dtypes.int, (5,6,7))
@@ -210,7 +202,7 @@ class TestUOpGraph(unittest.TestCase):
def test_where_same_fold(self):
v = UOp.variable('tmp', 0, 1)
c0 = UOp.const(dtypes.weakint, 0)
c0 = UOp.const(dtypes.index, 0)
vc = v != c0
c1 = UOp.const(dtypes.float, 1.0)
out = vc.where(c1, c1)
@@ -258,21 +250,21 @@ class TestUOpGraph(unittest.TestCase):
@unittest.skip("this test isn't valid uops")
def test_noop_vectorize_fold(self):
d0 = UOp.param(0, dtypes.float.ptr())
d0 = UOp.param(0, dtypes.float, (1,))
idx = UOp.const(dtypes.int, 0)
ld = d0.load(idx, dtype=dtypes.float.vec(2))
vec = UOp(Ops.STACK, dtypes.float.vec(2), (ld,))
x = UOp(Ops.GEP, dtypes.float, (vec, ), arg=0)
alu = UOp(Ops.SQRT, dtypes.float, (x, ))
out = UOp(Ops.STORE, dtypes.void, (d0, idx, alu))
x = vec.index(0)
alu = UOp(Ops.SQRT, src=(x, ))
out = UOp(Ops.STORE, src=(d0, idx, alu))
uops = to_uops_list([out])
self.assertEqual(len([x for x in uops if x.op is Ops.STACK]), 0)
@unittest.skip("this test isn't valid uops")
def test_gep_vec_fold(self):
d0 = UOp.param(0, dtypes.float.ptr())
d1 = UOp.param(1, dtypes.float.ptr())
d2 = UOp.param(2, dtypes.float.ptr())
d0 = UOp.param(0, dtypes.float, (1,))
d1 = UOp.param(1, dtypes.float, (1,))
d2 = UOp.param(2, dtypes.float, (1,))
idx = UOp.const(dtypes.int, 0)
def _test_vec(geps, count=4):
vec = UOp(Ops.STACK, dtypes.float.vec(count), geps)
@@ -285,35 +277,35 @@ class TestUOpGraph(unittest.TestCase):
# possible
val = d1.index(idx).load(dtype=dtypes.float.vec(4))
xyzw = tuple(val.gep(i) for i in range(4))
xyzw = tuple(val.index(i) for i in range(4))
self.assertIs(_test_vec(xyzw).op, Ops.LOAD)
# unaligned
val = d1.index(idx).load(dtype=dtypes.float.vec(4))
wzyx = tuple(val.gep(i) for i in reversed(range(4)))
wzyx = tuple(val.index(i) for i in reversed(range(4)))
self.assertIs(_test_vec(wzyx).op, Ops.STACK)
# different_size
val = d1.index(idx).load(dtype=dtypes.float.vec(2))
xy = tuple(val.gep(i) for i in range(2))
xy = tuple(val.index(i) for i in range(2))
self.assertIs(_test_vec(xy+xy).op, Ops.STACK)
val = d1.index(idx).load(dtype=dtypes.float.vec(4))
xy = tuple(val.gep(i) for i in range(2))
xy = tuple(val.index(i) for i in range(2))
self.assertIs(_test_vec(xy, count=2).op, Ops.STACK)
# different vals
val1 = d1.index(idx).load(dtype=dtypes.float.vec(2))
val2 = d2.index(idx).load(dtype=dtypes.float.vec(2))
xy1 = tuple(val1.gep(i) for i in range(2))
xy2 = tuple(val2.gep(i) for i in range(2))
xy1 = tuple(val1.index(i) for i in range(2))
xy2 = tuple(val2.index(i) for i in range(2))
self.assertIs(_test_vec(xy1+xy2).op, Ops.STACK)
def test_gep_vec_const_fold(self):
for vec_size in [2, 4, 8]:
consts = [UOp.const(dtypes.float, float(i)) for i in range(vec_size)]
vec = UOp(Ops.STACK, dtypes.float.vec(vec_size), tuple(consts))
vec = UOp(Ops.STACK, src=tuple(consts))
with Context(SPEC=0):
uops = to_uops_list([UOp(Ops.GEP, dtypes.float, (vec,), (i,)) for i in range(vec_size)])
uops = to_uops_list([vec.index(i) for i in range(vec_size)])
for uop, const in zip(uops, consts):
self.assertEqual(uop, const)
@@ -323,7 +315,7 @@ class TestUOpGraph(unittest.TestCase):
vec = UOp(Ops.STACK, dtypes.half.vec(i), tuple(UOp.const(dtypes.half, 0.0) for _ in range(i)))
var = UOp.variable("var", 0, 1, dtypes.half.vec(i))
acc = UOp.variable('acc', 0, 1, dtypes.half.vec(i))
wmma = UOp(Ops.WMMA, dtypes.half.vec(i), (vec, var, acc))
wmma = UOp(Ops.WMMA, src=(vec, var, acc))
uops = to_uops_list([wmma])
self.assertEqual(uops[0], acc)
self.assertEqual(len(uops), 2) # +1 for SINK
@@ -332,7 +324,7 @@ class TestUOpGraph(unittest.TestCase):
var = UOp.variable("var", 0, 1, dtypes.half.vec(i))
vec = UOp(Ops.STACK, dtypes.half.vec(i), tuple(UOp.const(dtypes.half, 0.0) for _ in range(i)))
acc = UOp.variable('acc', 0, 1, dtypes.half.vec(i))
wmma = UOp(Ops.WMMA, dtypes.half.vec(i), (var, vec, acc))
wmma = UOp(Ops.WMMA, src=(var, vec, acc))
uops = to_uops_list([wmma])
self.assertEqual(uops[0], acc)
self.assertEqual(len(uops), 2) # +1 for SINK
@@ -345,7 +337,7 @@ class TestUOpGraph(unittest.TestCase):
tuple(UOp.variable(f'tmp{j}', 0, 1, dtypes.half) for j in range(i//2)))
var = UOp.variable(f'tmp{i}', 0, 1, dtypes.half.vec(i))
acc = UOp.variable('acc', 0, 1, dtypes.half.vec(i))
wmma = UOp(Ops.WMMA, dtypes.half.vec(i), (vec, var, acc))
wmma = UOp(Ops.WMMA, src=(vec, var, acc))
uops = to_uops_list([wmma])
self.assertEqual(uops[-2], wmma) # -2 to skip SINK
@@ -355,7 +347,7 @@ class TestUOpGraph(unittest.TestCase):
tuple(UOp.const(dtypes.half, 0.0) for _ in range(i//2)) +
tuple(UOp.variable(f'tmp{j}', 0, 1, dtypes.half) for j in range(i//2)))
acc = UOp.variable('acc', 0, 1, dtypes.half.vec(i))
wmma = UOp(Ops.WMMA, dtypes.half.vec(i), (var, vec, acc))
wmma = UOp(Ops.WMMA, src=(var, vec, acc))
uops = to_uops_list([wmma])
self.assertEqual(uops[-2], wmma) # -2 to skip SINK
@@ -364,7 +356,7 @@ class TestUOpGraph(unittest.TestCase):
tuple(UOp.const(dtypes.half, 1.0 if j == 0 else 0.0) for j in range(i)))
var = UOp.variable(f'tmp{i}', 0, 1, dtypes.half.vec(i))
acc = UOp.variable('acc', 0, 1, dtypes.half.vec(i))
wmma = UOp(Ops.WMMA, dtypes.half.vec(i), (vec, var, acc))
wmma = UOp(Ops.WMMA, src=(vec, var, acc))
uops = to_uops_list([wmma])
self.assertEqual(uops[-2], wmma) # -2 to skip SINK
@@ -373,27 +365,27 @@ class TestUOpGraph(unittest.TestCase):
vec = UOp(Ops.STACK, dtypes.half.vec(i),
tuple(UOp.const(dtypes.half, 1.0 if j == 0 else 0.0) for j in range(i)))
acc = UOp.variable('acc', 0, 1, dtypes.half.vec(i))
wmma = UOp(Ops.WMMA, dtypes.half.vec(i), (var, vec, acc))
wmma = UOp(Ops.WMMA, src=(var, vec, acc))
uops = to_uops_list([wmma])
self.assertEqual(uops[-2], wmma) # -2 to skip SINK
def test_cast_alu_fold(self):
d0 = UOp.param(0, dtypes.bool.ptr(1))
d1 = UOp.param(1, dtypes.int.ptr(1))
d0 = UOp.param(0, dtypes.bool, (1,))
d1 = UOp.param(1, dtypes.int, (1,))
idx = UOp.const(dtypes.int, 0)
ld = d1.index(idx)
alu = (ld<1).cast(dtypes.bool)
out = d0.index(idx, ptr=True).store(alu)
out = d0.index(idx).store(alu)
uops = to_uops_list([out])
self.assertEqual(len([x for x in uops if x.op is Ops.CAST]), 0)
def test_double_cast_fold(self):
d0 = UOp.param(0, dtypes.float.ptr(1))
d1 = UOp.param(1, dtypes.int.ptr(1))
d0 = UOp.param(0, dtypes.float, (1,))
d1 = UOp.param(1, dtypes.int, (1,))
idx = UOp.const(dtypes.int, 0)
ld = d1.index(idx)
alu = ld.cast(dtypes.float).cast(dtypes.float)
out = d0.index(idx, ptr=True).store(alu)
out = d0.index(idx).store(alu)
uops = to_uops_list([out])
self.assertEqual(len([x for x in uops if x.op is Ops.CAST]), 1)
@@ -412,7 +404,7 @@ class TestUOpGraph(unittest.TestCase):
def test_bitcast_to_same_dtype_fold(self):
for dt in dtypes.ints + dtypes.floats + (dtypes.bool,):
d0 = UOp.param(0, dt.ptr(1))
d0 = UOp.param(0, dt, (1,))
v = d0.index(UOp.const(dtypes.int, 0))
uops = to_uops_list([v.bitcast(dt)])
self.assertEqual(len([x for x in uops if x.op is Ops.BITCAST and x.dtype is dt]), 0, f"dtype = {dt}")
@@ -425,18 +417,18 @@ class TestUOpGraph(unittest.TestCase):
def test_where_on_gated_load_fold(self):
ridx0 = UOp.range(100, 0)
d0 = UOp.param(0, dtypes.long.ptr(100))
d0 = UOp.param(0, dtypes.long, (100,))
ld = d0.index(ridx0.valid(ridx0<50))
w = (ridx0<50).where(ld, 5)
out = UOp.param(1, dtypes.long.ptr(100))
uops = to_uops_list([out.index(ridx0, ptr=True).store(w)])
out = UOp.param(1, dtypes.long, (100,))
uops = to_uops_list([out.index(ridx0).store(w)])
for u in uops:
assert u.op is not Ops.WHERE
if u.op is Ops.LOAD and u.src[0].src[0].op is Ops.PARAM: assert u.src[1].arg==5
def test_where_on_gated_load_folds_swapped_branches(self):
ridx0 = UOp.range(100, 0)
d0 = UOp.param(0, dtypes.long.ptr(100))
d0 = UOp.param(0, dtypes.long, (100,))
ld = d0.index(ridx0.valid((ridx0<50).logical_not()))
w = (ridx0<50).where(5, ld)
uops = to_uops_list([w])
@@ -446,40 +438,40 @@ class TestUOpGraph(unittest.TestCase):
def test_where_on_gated_load_with_cast(self):
ridx0 = UOp.range(100, 0)
d0 = UOp.param(0, dtypes.int.ptr(100))
d0 = UOp.param(0, dtypes.int, (100,))
gate_idx = ridx0.valid((ridx0<50))
ld = d0.index(gate_idx).cast(dtypes.float)
w = (ridx0<50).where(ld, 5.0)
out = UOp.param(1, dtypes.float.ptr(100))
uops = to_uops_list([out.index(ridx0, ptr=True).store(w)])
out = UOp.param(1, dtypes.float, (100,))
uops = to_uops_list([out.index(ridx0).store(w)])
for u in uops:
assert u.op is not Ops.WHERE
if u.op is Ops.LOAD and u.src[0].src[0].op is Ops.PARAM: assert u.src[1].arg == 5
def test_where_on_casted_gated_load_extra_cond(self):
ridx0 = UOp.range(100, 0)
d0 = UOp.param(0, dtypes.float.ptr(100))
d0 = UOp.param(0, dtypes.float, (100,))
ld = d0.index(ridx0.valid(ridx0<50))
w = ((ridx0<50) & (ridx0>30)).where(ld, UOp.const(dtypes.float, 0)).cast(dtypes.half)
out = UOp.param(1, dtypes.half.ptr(100))
uops = to_uops_list([out.index(ridx0, ptr=True).store(w)])
out = UOp.param(1, dtypes.half, (100,))
uops = to_uops_list([out.index(ridx0).store(w)])
for u in uops:
assert u.op is not Ops.WHERE
def test_where_on_casted_gated_load_extra_cond_swapped(self):
ridx0 = UOp.range(100, 0)
d0 = UOp.param(0, dtypes.float.ptr(100))
d0 = UOp.param(0, dtypes.float, (100,))
ld = d0.index(ridx0.valid(ridx0<50))
w = ((ridx0<50) & (ridx0>30)).where(UOp.const(dtypes.float, 0), ld).cast(dtypes.half)
out = UOp.param(1, dtypes.half.ptr(100))
uops = to_uops_list([out.index(ridx0, ptr=True).store(w)])
out = UOp.param(1, dtypes.half, (100,))
uops = to_uops_list([out.index(ridx0).store(w)])
for u in uops:
assert u.op is not Ops.WHERE
def test_where_in_store_becomes_gate(self):
ridx0 = UOp.range(100, 0)
d0 = UOp.param(0, dtypes.long.ptr(100))
idx = d0.index(ridx0, ptr=True)
d0 = UOp.param(0, dtypes.long, (100,))
idx = d0.index(ridx0)
ld = idx.load()
val = (ridx0<50).where(5, ld)
st = idx.store(val).end(ridx0)
@@ -491,81 +483,81 @@ class TestUOpGraph(unittest.TestCase):
def test_load_idx_becomes_int(self):
# mnist indexing with split reduceop
# Make sure we are not doign math on the loaded index, which would promote it to long
c0 = UOp.param(0, dtypes.uchar.ptr(128000))
c1 = UOp.range(UOp.const(dtypes.weakint, 512), 1, AxisType.LOOP)
c2 = UOp.range(UOp.const(dtypes.weakint, 250), 2, AxisType.LOOP)
c3 = UOp.param(1, dtypes.int.ptr(512))
c0 = UOp.param(0, dtypes.uchar, (128000,))
c1 = UOp.range(UOp.const(dtypes.index, 512), 1, AxisType.LOOP)
c2 = UOp.range(UOp.const(dtypes.index, 250), 2, AxisType.LOOP)
c3 = UOp.param(1, dtypes.int, (512,))
c4 = c3.index(c1)
c5 = UOp.range(UOp.const(dtypes.weakint, 240), 0, AxisType.REDUCE)
c6 = ((c2*UOp.const(dtypes.weakint, 240))+c5)
c7 = UOp.param(2, dtypes.uchar.ptr(60000))
c5 = UOp.range(UOp.const(dtypes.index, 240), 0, AxisType.REDUCE)
c6 = ((c2*UOp.const(dtypes.index, 240))+c5)
c7 = UOp.param(2, dtypes.uchar, (60000,))
c8 = c7.index(c6)
c9 = ((c4<0).where((c4+60000), c4)!=c6.cast(dtypes.int)).where(0, c8.cast(dtypes.uint).cast(dtypes.uchar)).reduce(c5, arg=Ops.ADD)
c10 = c0.index(((c1*UOp.const(dtypes.weakint, 250))+c2)).store(c9).end(c1, c2)
c10 = c0.index(((c1*UOp.const(dtypes.index, 250))+c2)).store(c9).end(c1, c2)
uops = to_uops_list([c10])
for u in uops:
self.assertNotEqual(u.dtype, dtypes.long)
def test_load_idx_no_math_on_loaded(self):
# test the (x+y)<c pattern where x has loads - we shouldn't do math on loaded indices
c0 = UOp.param(0, dtypes.uchar.ptr(128000))
c1 = UOp.range(UOp.const(dtypes.weakint, 512), 1, AxisType.LOOP)
c2 = UOp.range(UOp.const(dtypes.weakint, 250), 2, AxisType.LOOP)
c3 = UOp.param(1, dtypes.int.ptr(512))
c0 = UOp.param(0, dtypes.uchar, (128000,))
c1 = UOp.range(UOp.const(dtypes.index, 512), 1, AxisType.LOOP)
c2 = UOp.range(UOp.const(dtypes.index, 250), 2, AxisType.LOOP)
c3 = UOp.param(1, dtypes.int, (512,))
c4 = c3.index(c1) # c4 is a load
c5 = UOp.range(UOp.const(dtypes.weakint, 240), 0, AxisType.REDUCE)
c6 = ((c2*UOp.const(dtypes.weakint, 240))+c5)
c7 = UOp.param(2, dtypes.uchar.ptr(60000))
c5 = UOp.range(UOp.const(dtypes.index, 240), 0, AxisType.REDUCE)
c6 = ((c2*UOp.const(dtypes.index, 240))+c5)
c7 = UOp.param(2, dtypes.uchar, (60000,))
c8 = c7.index(c6)
# (loaded + range) < const pattern - loaded value shouldn't be promoted to long
loaded_idx = c4.cast(dtypes.weakint)
comparison = (loaded_idx + c5) < UOp.const(dtypes.weakint, 60000)
loaded_idx = c4.cast(dtypes.index)
comparison = (loaded_idx + c5) < UOp.const(dtypes.index, 60000)
c9 = comparison.where(c8.cast(dtypes.uint).cast(dtypes.uchar), 0).reduce(c5, arg=Ops.ADD)
c10 = c0.index(((c1*UOp.const(dtypes.weakint, 250))+c2)).store(c9).end(c1, c2)
c10 = c0.index(((c1*UOp.const(dtypes.index, 250))+c2)).store(c9).end(c1, c2)
uops = to_uops_list([c10])
for u in uops:
self.assertNotEqual(u.dtype, dtypes.long)
def test_fold_gated_load(self):
glbl0 = UOp.param(0, dtypes.int.ptr(1))
glbl1 = UOp.param(1, dtypes.int.ptr(1))
glbl2 = UOp.param(2, dtypes.int.ptr(1))
glbl0 = UOp.param(0, dtypes.int, (1,))
glbl1 = UOp.param(1, dtypes.int, (1,))
glbl2 = UOp.param(2, dtypes.int, (1,))
idx = UOp.const(dtypes.int, 0)
ld0 = glbl1.index(UOp.invalid())
ld1 = glbl2.index(idx.valid(UOp.const(dtypes.bool, True)))
uops = to_uops_list([glbl0.index(idx, ptr=True).store(ld1+ld0)])
uops = to_uops_list([glbl0.index(idx).store(ld1+ld0)])
# the gate and invalid value are deleted from ld1
self.assertEqual(len([u for u in uops if u.op is Ops.LOAD]), 1)
def test_fold_gated_load_local(self):
glbl0 = UOp.param(0, dtypes.int.ptr(16))
glbl0 = UOp.param(0, dtypes.int, (16,))
smem = UOp.placeholder((18,), dtypes.int, slot=0, addrspace=AddrSpace.LOCAL)
lidx = UOp.special(16, "lidx0", dtypes.int)
st = smem.index(lidx, ptr=True).store(glbl0.index(lidx, ptr=True).load())
lidx = UOp.special(16, "lidx0")
st = smem.index(lidx).store(glbl0.index(lidx).load())
barrier = st.barrier()
ld0 = smem.after(barrier).index(UOp.invalid())
ld1 = smem.after(barrier).index((lidx+2).valid(UOp.const(dtypes.bool, True)))
uops = to_uops_list([glbl0.index(lidx, ptr=True).store(ld1+ld0)])
uops = to_uops_list([glbl0.index(lidx).store(ld1+ld0)])
# the gate and invalid value are deleted from ld1
self.assertEqual(len([u for u in uops if u.op is Ops.LOAD]), 2)
def test_fold_gated_store(self):
glbl = UOp.param(0, dtypes.int.ptr(1))
glbl = UOp.param(0, dtypes.int, (1,))
idx0 = UOp.const(dtypes.int, 0)
val = UOp.const(dtypes.int, 42)
st0 = glbl.index(UOp.invalid(), ptr=True).store(val)
st1 = glbl.index(idx0.valid(UOp.const(dtypes.bool, True)), ptr=True).store(val)
st0 = glbl.index(UOp.invalid()).store(val)
st1 = glbl.index(idx0.valid(UOp.const(dtypes.bool, True))).store(val)
uops = to_uops_list([st0, st1])
# only the second store happens
self.assertEqual(len([u for u in uops if u.op is Ops.STORE]), 1)
@unittest.skip("this is a uop type error")
def test_asserts_bad_gate(self):
glbl0 = UOp.param(0, dtypes.int.ptr())
glbl0 = UOp.param(0, dtypes.int, (1,))
idx = UOp.const(dtypes.int, 0)
bad_gate = UOp.const(dtypes.int, 1)
with self.assertRaises(AssertionError): to_uops_list([UOp(Ops.STORE, dtypes.void, (glbl0, idx, UOp.const(dtypes.int, 42), bad_gate))])
with self.assertRaises(AssertionError): to_uops_list([UOp(Ops.STORE, src=(glbl0, idx, UOp.const(dtypes.int, 42), bad_gate))])
def test_after_end(self):
r = UOp.range(10, 0)
@@ -579,161 +571,6 @@ class TestUOpGraph(unittest.TestCase):
a = c.after(e)
self.assertNotIn(r, a.ranges)
@track_rewrites()
def expander_rewrite(sink): return graph_rewrite(sink, sym + expander)
class TestExpander(unittest.TestCase):
def test_expand_add_broadcast(self):
e1 = UOp(Ops.UNROLL, dtypes.int, (UOp.const(dtypes.int.vec(4), tuple(x for x in range(4))),), ((1,4),))
sink = expander_rewrite(e1+3)
assert sink.op is Ops.UNROLL and len(const_values(sink.src[0])) == 4
self.assertTupleEqual(const_values(sink.src[0]), (3,4,5,6))
def test_contract_simple(self):
e1 = UOp(Ops.UNROLL, dtypes.int, (UOp.const(dtypes.int.vec(4), tuple(x for x in range(4))),), ((1,4),))
con = UOp(Ops.CONTRACT, dtypes.int.vec(4), (e1,), ((1,4),))
sink = expander_rewrite(con)
self.assertEqual(sink.op, Ops.STACK)
self.assertTupleEqual(const_values(sink), (0,1,2,3))
def test_contract_axis_1(self):
e1 = UOp(Ops.UNROLL, dtypes.int, (UOp.const(dtypes.int.vec(16), tuple(x for x in range(16))),), ((1,4),(2,4)))
con = UOp(Ops.CONTRACT, dtypes.int.vec(4), (e1,), ((1,4),))
sink = expander_rewrite(con)
vals = const_values(sink.src[0])
assert sink.op is Ops.UNROLL and len(vals) == 16 and sink.arg == ((2,4),)
assert sink.src[0].op is Ops.STACK
self.assertTupleEqual(vals[0:4], (0,4,8,12))
self.assertTupleEqual(vals[12:], (3,7,11,15))
def test_contract_axis_2(self):
e1 = UOp(Ops.UNROLL, dtypes.int, (UOp.const(dtypes.int.vec(16), tuple(x for x in range(16))),), ((1,4),(2,4)))
con = UOp(Ops.CONTRACT, dtypes.int.vec(4), (e1,), ((2,4),))
sink = expander_rewrite(con)
vals = const_values(sink.src[0])
assert sink.op is Ops.UNROLL and len(vals) == 16 and sink.arg == ((1,4),)
assert sink.src[0].op is Ops.STACK
self.assertTupleEqual(vals[0:4], (0,1,2,3))
self.assertTupleEqual(vals[12:], (12,13,14,15))
def test_contract_axis_2_big(self):
e1 = UOp(Ops.UNROLL, dtypes.int, (UOp.const(dtypes.int.vec(16), tuple(x for x in range(16))),), ((1,2),(2,2),(3,2),(4,2)))
con = UOp(Ops.CONTRACT, dtypes.int.vec(2), (e1,), ((2,2),))
sink = expander_rewrite(con)
assert sink.op is Ops.UNROLL and sink.arg == ((1, 2), (3, 2), (4, 2))
vals = const_values(sink.src[0])
self.assertTupleEqual(vals[0:2], (0,4))
self.assertTupleEqual(vals[12:14], (10,14))
def test_contract_multi_axis(self):
e1 = UOp(Ops.UNROLL, dtypes.int, (UOp.const(dtypes.int.vec(16), tuple(x for x in range(16))),), ((1,2),(2,2),(3,2),(4,2)))
sink = expander_rewrite(UOp(Ops.CONTRACT, dtypes.int.vec(4), (e1,), ((3, 2), (2, 2))))
assert sink.op is Ops.UNROLL and sink.arg == ((1, 2), (4, 2))
self.assertTupleEqual(const_values(sink.src[0])[0:4], (0, 4, 2, 6))
sink = expander_rewrite(UOp(Ops.CONTRACT, dtypes.int.vec(4), (e1,), ((2, 2), (3, 2))))
assert sink.op is Ops.UNROLL and sink.arg == ((1, 2), (4, 2))
self.assertTupleEqual(const_values(sink.src[0])[0:4], (0, 2, 4, 6))
def test_contract_mid(self):
e1 = UOp(Ops.UNROLL, dtypes.int, (UOp.const(dtypes.int.vec(8), tuple(x for x in range(8))),), ((1,2),(2,2),(3,2)))
con = UOp(Ops.CONTRACT, dtypes.int.vec(2), (e1,), ((2,2),))
sink = expander_rewrite(con)
assert sink.op is Ops.UNROLL and sink.arg == ((1,2),(3,2))
assert sink.src[0].op is Ops.STACK and len(const_values(sink.src[0])) == 8
self.assertTupleEqual(const_values(sink.src[0]), (0,2,1,3,4,6,5,7))
def test_contract_no_expand(self):
e1 = UOp.variable("i", 0, 10, dtype=dtypes.int)
con = UOp(Ops.CONTRACT, dtypes.int.vec(2), (e1,), ((2,2),))
sink = expander_rewrite(con)
assert sink.op is Ops.STACK and len(sink.src) == 2
assert sink.src[0] == sink.src[1]
def test_contract_half_expand(self):
e1 = UOp(Ops.UNROLL, dtypes.int, (UOp.const(dtypes.int.vec(4), tuple(x for x in range(4))),), ((1,4),))
con = UOp(Ops.CONTRACT, dtypes.int.vec(8), (e1,), ((1,4), (2,2)))
sink = expander_rewrite(con)
vals = const_values(sink)
assert sink.op is Ops.STACK and len(vals) == 8
assert vals[0] == vals[1]
assert vals[0] != vals[2]
assert vals[6] == vals[7]
def test_expand_same_axis(self):
e1 = UOp(Ops.UNROLL, dtypes.int, (UOp.const(dtypes.int.vec(4), tuple(x for x in range(4))),), ((1,4),))
e2 = UOp(Ops.UNROLL, dtypes.int, (UOp.const(dtypes.int.vec(4), tuple(4*x for x in range(4))),), ((1,4),))
sink = expander_rewrite(e1+e2)
self.assertEqual(sink.op, Ops.UNROLL)
self.assertEqual(sink.src[0].op, Ops.STACK)
self.assertTupleEqual(const_values(sink.src[0]), (0,5,10,15))
def test_expand_different_axis(self, flip=False):
e1 = UOp(Ops.UNROLL, dtypes.int, (UOp.const(dtypes.int.vec(4), tuple(4*x for x in range(4))),), ((1,4),))
e2 = UOp(Ops.UNROLL, dtypes.int, (UOp.const(dtypes.int.vec(4), tuple(x for x in range(4))),), ((2,4),))
sink = expander_rewrite((e2+e1) if flip else (e1+e2))
vals = const_values(sink.src[0])
assert sink.op is Ops.UNROLL and len(vals) == 16
assert sink.arg == ((1, 4), (2, 4))
self.assertTupleEqual(vals, (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15))
def test_expand_different_axis_flip(self): self.test_expand_different_axis(True)
@unittest.skip("no longer supported")
def test_reduce_known_axis(self):
e1 = UOp(Ops.UNROLL, dtypes.int, tuple(UOp.const(dtypes.int, x) for x in range(4)), ((1,4),))
sink = (3*e1).reduce(e1, arg=Ops.ADD)
sink = expander_rewrite(sink)
assert sink.op is Ops.CONST
self.assertEqual(sink.arg, 3*(0+1+2+3))
@unittest.skip("no longer supported")
def test_reduce_const(self):
e1 = UOp(Ops.UNROLL, dtypes.int, tuple(UOp.const(dtypes.int, x) for x in range(4)), ((1,4),))
sink = UOp.const(dtypes.int, 3).reduce(e1, arg=Ops.ADD)
sink = expander_rewrite(sink)
assert sink.op is Ops.CONST
self.assertEqual(sink.arg, 3*4)
@unittest.skip("no longer supported")
def test_double_expand(self):
e1 = UOp(Ops.UNROLL, dtypes.int, tuple(UOp.const(dtypes.int, x) for x in range(4)), ((2,4),))
e2 = UOp(Ops.UNROLL, dtypes.int, tuple(UOp.const(dtypes.int, 4+x) for x in range(4)), ((2,4),))
e = UOp(Ops.UNROLL, dtypes.int, (e1, e2), ((1,2),))
sink = expander_rewrite(e)
assert sink.op is Ops.UNROLL and len(sink.src) == 8
assert sink.arg == ((1, 2), (2, 4))
self.assertListEqual([x.arg for x in sink.src], [0,1,2,3,4,5,6,7])
@unittest.skip("no longer supported")
def test_double_expand_reverse(self):
e1 = UOp(Ops.UNROLL, dtypes.int, tuple(UOp.const(dtypes.int, x) for x in range(4)), ((1,4),))
e2 = UOp(Ops.UNROLL, dtypes.int, tuple(UOp.const(dtypes.int, 4+x) for x in range(4)), ((1,4),))
e = UOp(Ops.UNROLL, dtypes.int, (e1, e2), ((2,2),))
sink = expander_rewrite(e)
assert sink.op is Ops.UNROLL and len(sink.src) == 8
assert sink.arg == ((1, 4), (2, 2))
self.assertListEqual([x.arg for x in sink.src], [0, 4, 1, 5, 2, 6, 3, 7])
@unittest.skip("no longer supported")
def test_double_expand_middle(self):
e1 = UOp(Ops.UNROLL, dtypes.int, tuple(UOp.const(dtypes.int, x) for x in range(4)), ((1,2),(3,2)))
e2 = UOp(Ops.UNROLL, dtypes.int, tuple(UOp.const(dtypes.int, 4+x) for x in range(4)), ((1,2),(3,2)))
e = UOp(Ops.UNROLL, dtypes.int, (e1, e2), ((2,2),))
sink = expander_rewrite(e)
assert sink.op is Ops.UNROLL and len(sink.src) == 8
assert sink.arg == ((1, 2), (2, 2), (3, 2))
self.assertListEqual([x.arg for x in sink.src], [0, 1, 4, 5, 2, 3, 6, 7])
# does this need to work?
@unittest.expectedFailure
@unittest.skip
def test_reduce_different_axis(self):
e1 = UOp(Ops.UNROLL, dtypes.int, tuple(UOp.const(dtypes.int, x) for x in range(4)), ((1,4),))
e2 = UOp(Ops.UNROLL, dtypes.int, tuple(UOp.const(dtypes.int, x) for x in range(4)), ((2,4),))
sink = e1.reduce(e2, arg=Ops.ADD)
sink = expander_rewrite(sink)
print(sink)
class TestReduceCollapse(unittest.TestCase):
def test_multi_range_reduce_add(self):
"""Test that (x + y).reduce(r1, r2) distributes over multiple ranges"""
@@ -780,7 +617,7 @@ class TestConstBufferize(unittest.TestCase):
from tinygrad.schedule.rangeify import pm_const_buffer_folding, BufferizeOpts
c = UOp.const(dtypes.float, 42.0)
r1 = UOp.range(3, 0)
bufferize_with_range = UOp(Ops.STAGE, dtypes.float, (c, r1), arg=BufferizeOpts(device="CPU"))
bufferize_with_range = UOp(Ops.STAGE, src=(c, r1), arg=BufferizeOpts(device="CPU"))
self.assertEqual(len(bufferize_with_range.src), 2) # const + 1 range
result = graph_rewrite(bufferize_with_range, pm_const_buffer_folding, name='test')
@@ -795,7 +632,7 @@ class TestConstBufferize(unittest.TestCase):
c = UOp.const(dtypes.float, 3.14)
r1 = UOp.range(3, 0)
r2 = UOp.range(4, 1)
bufferize_with_ranges = UOp(Ops.STAGE, dtypes.float, (c, r1, r2), arg=BufferizeOpts(device="CPU"))
bufferize_with_ranges = UOp(Ops.STAGE, src=(c, r1, r2), arg=BufferizeOpts(device="CPU"))
self.assertEqual(len(bufferize_with_ranges.src), 3) # const + 2 ranges
result = graph_rewrite(bufferize_with_ranges, pm_const_buffer_folding, name='test')
+44 -24
View File
@@ -11,13 +11,13 @@ from tinygrad.uop.validate import uops_to_z3
def check_uop_against_string(self, v:UOp, s:str):
sym_vars = {v.render():v for v in v.toposort() if v.op in (Ops.RANGE, Ops.SPECIAL, Ops.PARAM)}
s_eval = eval(s, sym_vars)
if isinstance(s_eval, int) and v.dtype==dtypes.weakint: s_eval = UOp.const(dtypes.weakint, s_eval)
if isinstance(s_eval, int) and v.dtype==dtypes.index: s_eval = UOp.const(dtypes.index, s_eval)
elif isinstance(s_eval, (bool, int, float)): s_eval = UOp.const(dtypes.from_py(s_eval), s_eval)
s_eval = graph_rewrite(s_eval, commutative, name="cannonicalize eval")
self.assertIs(s_eval, v, f"eval did not match simplified: {s_eval} != {v.render()} for {s}")
def Variable(name: str, min_val: ConstType, max_val: ConstType, dtype: DType=dtypes.weakint): return UOp.variable(name,min_val,max_val,dtype)
def uconst(val): return UOp.const(dtypes.weakint, val)
def Variable(name: str, min_val: ConstType, max_val: ConstType, dtype: DType=dtypes.index): return UOp.variable(name,min_val,max_val,dtype)
def uconst(val): return UOp.const(dtypes.index, val)
def usum(ops): return functools.reduce(lambda x,y: x+y, ops)
def uand(ops): return functools.reduce(lambda x,y: x*y, ops)
@@ -247,12 +247,12 @@ class TestSymbolic(unittest.TestCase):
self.assertEqual((Variable("x", -10, 0)%Variable("y", 1, 10))._min_max, (0, 9))
def test_range_div_its_symbolic_bound(self):
a = Variable("a", 1, 10, dtypes.weakint)
a = Variable("a", 1, 10, dtypes.index)
ridx0 = UOp.range(a+2, 0)
self.helper_test_variable(ridx0//(a+2), 0, 0, "0")
def test_range_mod_its_symbolic_bound(self):
a = Variable("a", 1, 10, dtypes.weakint)
a = Variable("a", 1, 10, dtypes.index)
ridx = UOp.range(a+2, 0)
self.helper_test_variable(ridx%(a+2), 0, 11, "r0")
@@ -825,6 +825,26 @@ class TestSymbolic(unittest.TestCase):
self.helper_test_variable((x//10)*10 + x%10, 0, 119, "(a*10+(a+b//5)//2*10+(b+a*5)%10)")
self.helper_test_variable((x//10)*2 + (x//5)%2, 0, 23, "(a*3+b//5)")
def test_div_mod_recombine_merged_quotient(self):
# recombine finds the quotient base//div even when stored merged as base0//(d0*div), including with an offset
x = Variable("x", 0, 199)
self.helper_test_variable(((x//3)%4)*2 + ((x//12)%5)*8, 0, 38, "x//3%20*2") # nested-merged quotient
self.helper_test_variable(((x//3 + 1)%4) + ((x+3)//12)*4, 1, 67, "x//3+1") # offset-merged quotient
def test_div_mod_recombine_negative_div(self):
# partial recombine only needs d>0, div can be negative: (x%div) + ((x//div)%d)*div -> x%(div*d)
x = Variable("x", 0, 199)
self.helper_test_variable(x%(-3) + ((x//(-3))%5)*(-3), -14, 0, "x%-15")
def test_div_mod_recombine_shifted_quotient(self):
# when vmin<0 blocks const reduction on the mod side, the quotient is stored const-shifted: (x-50)//3 -> (x+1)//3 - 17.
# recombine only needs a quotient of some b congruent to base mod div, so the shift folds into the result
x = Variable("x", 0, 100)
y = Variable("y", 0, 99)
self.helper_test_variable((x-50)%3 + ((x-50)//3)*3, -50, 50, "(x+-50)") # shifted literal quotient
self.helper_test_variable((x-50)%3 + (((x-50)//3)%5)*3, 0, 14, "((x+-50)%15)") # shift inside the partial's mod
self.helper_test_variable(((y-50)//5)%4 + ((y-50)//20)*4, -10, 9, "(y//5+-10)") # merged and shifted
def test_div_mod_recombine_in_additive_sum(self):
x = Variable("x", 0, 31)
y = Variable("y", 0, 5)
@@ -898,9 +918,9 @@ class TestSymbolic(unittest.TestCase):
# CAST(bool -> int) != c (c not in {0,1}) -> always True (CAST is 0 or 1)
self.helper_test_variable(cond.cast(dtypes.int).ne(2), 1, 1, "True")
self.helper_test_variable(cond.cast(dtypes.int).ne(-1), 1, 1, "True")
# CAST(bool -> weakint) folds too
self.helper_test_variable(cond.cast(dtypes.weakint).ne(0), 0, 1, "(a<2)")
self.helper_test_variable(cond.cast(dtypes.weakint).ne(1), 0, 1, "((a<2)!=True)")
# CAST(bool -> index) folds too
self.helper_test_variable(cond.cast(dtypes.index).ne(0), 0, 1, "(a<2)")
self.helper_test_variable(cond.cast(dtypes.index).ne(1), 0, 1, "((a<2)!=True)")
def test_where_removal(self):
cond = Variable("a", 0, 3) < 2
@@ -956,8 +976,8 @@ class TestSymbolic(unittest.TestCase):
expr = cond.where(a, b).cast(dtypes.half)
# TODO: copied from render, render does not support cast
glbl = UOp.param(0, dtypes.int.ptr(1))
uops = get_uops(UOp(Ops.STORE, dtypes.void, (glbl.index(UOp.const(dtypes.int, 0), ptr=True), expr)).sink())
glbl = UOp.param(0, dtypes.int, (1,))
uops = get_uops(UOp(Ops.STORE, src=(glbl.index(UOp.const(dtypes.int, 0)), expr)).sink())
rewritten_uop = [uop for uop in uops if uop.op is Ops.STORE][0].src[1]
# the vars are now scalar PARAMs
@@ -1001,7 +1021,7 @@ class TestSymbolic(unittest.TestCase):
self.helper_test_variable((numerator//denominator)<=0, 1, 1, "True")
def test_symbolic_range_doesnt_collapse(self):
r0 = UOp.range((Variable("a", 1, 10)<5).cast(dtypes.weakint), 0)
r0 = UOp.range((Variable("a", 1, 10)<5).cast(dtypes.index), 0)
self.helper_test_variable(r0, 0, 0, "r0")
def test_const_reciprocal(self):
@@ -1269,16 +1289,16 @@ class TestInvalidIndex(unittest.TestCase):
self.assertIs((UOp.invalid()<Variable("a",0,10)).simplify().dtype, dtypes.bool)
def test_alu_invalid_vconst(self):
c1 = UOp.const(dtypes.weakint.vec(4), (1, 1, Invalid, Invalid))
c2 = UOp.const(dtypes.weakint.vec(4), (1, Invalid, 1, 1))
self.assertIs((c1+c2).simplify(), UOp.const(dtypes.weakint.vec(4), (2, Invalid, Invalid, Invalid)))
c1 = UOp.const(dtypes.index, (1, 1, Invalid, Invalid))
c2 = UOp.const(dtypes.index, (1, Invalid, 1, 1))
self.assertIs((c1+c2).simplify(), UOp.const(dtypes.index, (2, Invalid, Invalid, Invalid)))
class TestStoreLoadFolding(unittest.TestCase):
"""Tests for store(index, load(index)) -> NOOP rule. This rule matches patterns that EMERGE during simplification."""
def test_store_load_folding(self):
# store(idx, load(idx)) -> NOOP, including emergent patterns like store(idx, load(idx) + 0)
buf = UOp.param(0, dtypes.int.ptr())
index = buf.index(UOp.const(dtypes.weakint, 0))
buf = UOp.param(0, dtypes.int, (1,))
index = buf.index(UOp.const(dtypes.index, 0))
# Direct: store(idx, load(idx)) -> NOOP
self.assertEqual(graph_rewrite(index.store(index.load()), sym).op, Ops.NOOP)
# Emergent: store(idx, load(idx) + 0) -> store(idx, load(idx)) -> NOOP
@@ -1290,14 +1310,14 @@ class TestStoreLoadFolding(unittest.TestCase):
class TestMoveWhereOnLoad(unittest.TestCase):
def test_bool_index_preserves_dtype(self):
buf = UOp.param(0, dtypes.bool.ptr(8))
buf = UOp.param(0, dtypes.bool, (8,))
a = Variable("a", 0, 7)
r = UOp.range(8, 0)
# cond has a range that the rewrite can move into the valid: gate (a<4) goes into load valid
cond = (a < 4) & (r < 2)
valid = (a < 2) # pre-existing valid on the load (to pass can_move check for the r-only clause)
idx = buf.index(a.valid(valid), ptr=True)
expr = cond.where(idx, 0)
idx = buf.index(a.valid(valid))
expr = cond.where(idx, idx.const_like(0))
out = graph_rewrite(expr, pm_move_where_on_load)
# any WHERE in the rewritten graph must have matched-dtype branches
for u in out.toposort():
@@ -1335,10 +1355,10 @@ class TestGatedUopGivenValid(unittest.TestCase):
idx0 = (r0 + uconst(-1)) // uconst(3)
idx1 = r0 % uconst(3)
idx:UOp = (r0 < 3).where(UOp(Ops.STACK, dtypes.weakint.vec(2), (idx0, idx1)), UOp.invalid())
idx:UOp = (r0 < 3).where(UOp(Ops.STACK, src=(idx0, idx1)), UOp.invalid())
idx = graph_rewrite(idx, pm_simplify_valid)
# independent simplification: (r0-1)//3 -> (r0+2)//3 - 1, and r0%3 -> r0 when r0 in [0,2]
expected_vec = UOp(Ops.STACK, dtypes.weakint.vec(2), ((r0 + uconst(2)) // uconst(3) + uconst(-1), r0))
expected_vec = UOp(Ops.STACK, src=((r0 + uconst(2)) // uconst(3) + uconst(-1), r0))
self.assertEqual(idx, (r0 < 3).where(expected_vec, UOp.invalid()))
class TestRangeSplitting(unittest.TestCase):
@@ -1347,10 +1367,10 @@ class TestRangeSplitting(unittest.TestCase):
from tinygrad.codegen.simplify import pm_split_ranges, pm_flatten_range
r0 = UOp.range(uconst(8), 0)
# create a simple expression using the range with mod: store range%2 to a buffer
buf = UOp.param(0, dtypes.int.ptr())
buf = UOp.param(0, dtypes.int, (1,))
val = (r0 % uconst(2)).cast(dtypes.int)
store = UOp(Ops.STORE, dtypes.void, (buf.index(uconst(0)), val))
sink = UOp(Ops.SINK, dtypes.void, (UOp(Ops.END, dtypes.void, (store, r0)),))
store = UOp(Ops.STORE, src=(buf.index(uconst(0)), val))
sink = UOp(Ops.SINK, src=(UOp(Ops.END, src=(store, r0)),))
# count RANGEs before
ranges_before = len([u for u in sink.toposort() if u.op is Ops.RANGE])
# apply the range splitting optimization
+12 -12
View File
@@ -75,14 +75,14 @@ class TestVminVmaxProperties(unittest.TestCase):
self.assertEqual(uop.vmax, 8)
def test_vmin_vmax_variable_inside_special(self):
uop = UOp(Ops.SPECIAL, dtypes.int, arg='gidx0', src=(UOp.variable('i', 1, 10, dtypes.int),))
uop = UOp(Ops.SPECIAL, arg='gidx0', src=(UOp.variable('i', 1, 10, dtypes.int),))
self.assertEqual(uop.vmin, 0)
self.assertEqual(uop.vmax, 9)
def test_vmin_vmax_multiplication_0_inf(self):
# vmin and vmax for multiplication with a variable
x = UOp.const(dtypes.float, 0.0)
y = UOp.load(UOp.param(0, dtypes.float.ptr(1)), UOp.const(dtypes.int, 0), dtype=dtypes.float)
y = UOp.load(UOp.param(0, dtypes.float, (1,)), UOp.const(dtypes.int, 0), dtype=dtypes.float)
uop = x * y
# TODO: these should be 0, but definitely should not be nan
self.assertEqual(uop.vmin, -math.inf)
@@ -160,7 +160,7 @@ class TestVminVmaxProperties(unittest.TestCase):
self.assertNotEqual(i.vmin, i.vmax)
def test_vmin_vmax_invalid_vconst(self):
x = UOp.const(dtypes.weakint.vec(4), (0, 4, Invalid, Invalid))
x = UOp.const(dtypes.index, (0, 4, Invalid, Invalid))
self.assertLess(x.vmin, 0)
self.assertGreater(x.vmax, 4)
@@ -280,46 +280,46 @@ class TestVminVmaxDivMod(unittest.TestCase):
class TestVminVmaxVConst(unittest.TestCase):
def test_vmin_vmax_vconst_single_element(self):
# vmin and vmax for a single-element vector constant
uop = UOp.const(dtypes.int32.vec(1), (42,))
uop = UOp.const(dtypes.int32, (42,))
self.assertEqual(uop.vmin, 42)
self.assertEqual(uop.vmax, 42)
def test_vmin_vmax_vconst_multiple_elements(self):
# vmin and vmax for a multi-element vector constant
uop = UOp.const(dtypes.int32.vec(4), (10, 20, -5, 7))
uop = UOp.const(dtypes.int32, (10, 20, -5, 7))
self.assertEqual(uop.vmin, -5)
self.assertEqual(uop.vmax, 20)
def test_vmin_vmax_vconst_all_equal(self):
# vmin and vmax for a vector where all elements are equal
uop = UOp.const(dtypes.int32.vec(3), (7, 7, 7))
uop = UOp.const(dtypes.int32, (7, 7, 7))
self.assertEqual(uop.vmin, 7)
self.assertEqual(uop.vmax, 7)
def test_vmin_vmax_vconst_with_negative_values(self):
# vmin and vmax for a vector constant containing negative values
uop = UOp.const(dtypes.int32.vec(4), (-10, -20, -5, -15))
uop = UOp.const(dtypes.int32, (-10, -20, -5, -15))
self.assertEqual(uop.vmin, -20)
self.assertEqual(uop.vmax, -5)
def test_vmin_vmax_vconst_with_floats(self):
# vmin and vmax for a vector constant of float values
uop = UOp.const(dtypes.float32.vec(3), (1.5, -3.2, 0.0))
uop = UOp.const(dtypes.float32, (1.5, -3.2, 0.0))
self.assertEqual(uop.vmin, -3.2)
self.assertEqual(uop.vmax, 1.5)
def test_vmin_vmax_vconst_with_bools(self):
# vmin and vmax for a vector constant of bool values
uop = UOp.const(dtypes.bool.vec(3), (True, False, False))
uop = UOp.const(dtypes.bool, (True, False, False))
self.assertIs(uop.vmin, False)
self.assertIs(uop.vmax, True)
def test_vmin_vmax_vector_with_gep(self):
# vmin and vmax for a vector constant of bool values
d1 = UOp.param(1, dtypes.int.ptr())
d1 = UOp.param(1, dtypes.int, (1,))
idx = UOp.const(dtypes.int, 0)
val = UOp(Ops.LOAD, dtypes.int.vec(2), (d1.index(idx).cast(dtypes.int.vec(2).ptr()),))
uop = (val // 32).gep(0)
val = UOp(Ops.LOAD, src=(d1.index(idx),))
uop = (val // 32)
self.assertEqual(uop.vmin, -67108864)
self.assertEqual(uop.vmax, 67108863)
+41 -45
View File
@@ -110,12 +110,12 @@ class TestExecALU(unittest.TestCase):
class TestGatedStoreRewrite(unittest.TestCase):
def test_tiny_gate_store(self):
gmem = UOp.param(0, dtypes.float.ptr(8))
gidx0 = UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 4),), 'gidx0')
gate = gidx0<UOp.const(dtypes.int, 1)
idx = UOp(Ops.INDEX, dtypes.float.ptr(8), (gmem, (gidx0 * UOp.const(dtypes.int, 2)).valid(gate)))
gmem = UOp.param(0, dtypes.float, (8,))
gidx0 = UOp.special(4, 'gidx0')
gate = gidx0<UOp.const(dtypes.index, 1)
idx = UOp(Ops.INDEX, src=(gmem, (gidx0 * UOp.const(dtypes.index, 2)).valid(gate)))
val = UOp.const(dtypes.float, 42.0)
store = UOp(Ops.STORE, dtypes.void, (idx, val))
store = UOp(Ops.STORE, src=(idx, val))
uops = to_uops_list([store])
if_uop = next(u for u in uops if u.op is Ops.IF)
endif = next(u for u in uops if u.op is Ops.ENDIF)
@@ -126,12 +126,12 @@ class TestGatedStoreRewrite(unittest.TestCase):
self.assertEqual(len(gated_uops[-1].src), 2)
def test_gate_some_stores(self):
gmem0 = UOp.param(0, dtypes.float.ptr(8))
gmem1 = UOp.param(1, dtypes.float.ptr(8))
gidx0 = UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 4),), 'gidx0')
idx = gidx0 * UOp.const(dtypes.int, 2)
idx0 = UOp(Ops.INDEX, dtypes.float.ptr(8), (gmem0, idx.valid(gidx0<UOp.const(dtypes.int, 1))))
idx1 = UOp(Ops.INDEX, dtypes.float.ptr(8), (gmem1, idx))
gmem0 = UOp.param(0, dtypes.float, (8,))
gmem1 = UOp.param(1, dtypes.float, (8,))
gidx0 = UOp.special(4, 'gidx0')
idx = gidx0 * UOp.const(dtypes.index, 2)
idx0 = UOp(Ops.INDEX, src=(gmem0, idx.valid(gidx0<UOp.const(dtypes.index, 1))))
idx1 = UOp(Ops.INDEX, src=(gmem1, idx))
val = UOp.const(dtypes.float, 42.0)
stores = [UOp.store(idx0, val), UOp.store(idx1, val)]
uops = to_uops_list(stores)
@@ -146,13 +146,13 @@ class TestGatedStoreRewrite(unittest.TestCase):
# scaled down version of TestLinearizerDumb.test_unmerged_ifs
@unittest.skip("we don't merge ifs anymore")
def test_merge_ifs_alt(self):
gmem0 = UOp.param(0, dtypes.float.ptr(8))
gmem1 = UOp.param(1, dtypes.float.ptr(8))
gidx0 = UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 4),), 'gidx0')
idx = gidx0*UOp.const(dtypes.int, 2)
gate = gidx0<UOp.const(dtypes.int, 1)
idx0 = UOp(Ops.INDEX, dtypes.float.ptr(8), (gmem0, idx.valid(gate)))
idx1 = UOp(Ops.INDEX, dtypes.float.ptr(8), (gmem1, idx.valid(gate)))
gmem0 = UOp.param(0, dtypes.float, (8,))
gmem1 = UOp.param(1, dtypes.float, (8,))
gidx0 = UOp.special(4, 'gidx0')
idx = gidx0*UOp.const(dtypes.index, 2)
gate = gidx0<UOp.const(dtypes.index, 1)
idx0 = UOp(Ops.INDEX, src=(gmem0, idx.valid(gate)))
idx1 = UOp(Ops.INDEX, src=(gmem1, idx.valid(gate)))
val = UOp.const(dtypes.float, 42.0)
stores = [UOp.store(idx0, val), UOp.store(idx1, val)]
uops = to_uops_list(stores)
@@ -170,7 +170,7 @@ class TestGatedStoreRewrite(unittest.TestCase):
class TestFastIdiv(unittest.TestCase):
def test_division_power_of_two(self):
for dt in (dtypes.int32, dtypes.uint32):
g = UOp.param(0, dt.ptr(3))
g = UOp.param(0, dt, (3,))
c = UOp.const(dt, 2)
l = g.index(c)
a = UOp(Ops.CDIV, dt, (l, c))
@@ -183,7 +183,7 @@ class TestFastIdiv(unittest.TestCase):
def test_floormod_power_of_two(self):
# FLOORMOD by a power of two lowers to AND (correct floor mod for any sign in two's complement)
for dt in (dtypes.int32, dtypes.uint32):
g = UOp.param(0, dt.ptr(9))
g = UOp.param(0, dt, (9,))
c = UOp.const(dt, 8)
a = UOp(Ops.FLOORMOD, dt, (g.index(c), c))
uops = to_uops_list([a], ren=Device[Device.DEFAULT].renderer)
@@ -195,7 +195,7 @@ class TestFastIdiv(unittest.TestCase):
def test_floordiv_power_of_two_uint(self):
# uint FLOORDIV by a power of two lowers to a shift, leaving no IDIV/FLOORDIV in the kernel
for dt in (dtypes.uint32, dtypes.uint64):
g = UOp.param(0, dt.ptr(3))
g = UOp.param(0, dt, (3,))
c = UOp.const(dt, 2)
a = UOp(Ops.FLOORDIV, dt, (g.index(c), c))
uops = to_uops_list([a], ren=Device[Device.DEFAULT].renderer)
@@ -207,17 +207,17 @@ class TestFastIdiv(unittest.TestCase):
@Context(DISABLE_FAST_IDIV=0)
@unittest.skipIf(Device.DEFAULT == "WEBGPU", "WEBGPU doesn't support long")
def test_fast_idiv_and_mod(self):
g = UOp.param(0, dtypes.uint32.ptr(4))
g = UOp.param(0, dtypes.uint32, (4,))
c = UOp.const(dtypes.uint, 3)
l = g.index(c)
a = UOp(Ops.CDIV, dtypes.uint, (l, c))
a = UOp(Ops.CDIV, src=(l, c))
uops = to_uops_list([a], ren=Device[Device.DEFAULT].renderer)
Device[Device.DEFAULT].renderer.render(uops)
ops = [x.op for x in uops]
self.assertIn(Ops.SHR, ops)
self.assertNotIn(Ops.CDIV, ops)
b = UOp(Ops.CMOD, dtypes.uint, (l, c))
b = UOp(Ops.CMOD, src=(l, c))
uops = to_uops_list([b], ren=Device[Device.DEFAULT].renderer)
Device[Device.DEFAULT].renderer.render(uops)
ops = [x.op for x in uops]
@@ -242,10 +242,10 @@ class TestFastIdiv(unittest.TestCase):
@unittest.expectedFailure
def test_fast_idiv_overflow(self):
# This will be possible with a slightly different method for fast_idiv
g = UOp.param(0, dtypes.uint32.ptr(8))
g = UOp.param(0, dtypes.uint32, (8,))
c = UOp.const(dtypes.uint, 7)
l = UOp(Ops.LOAD, dtypes.uint, (g.index(c),))
a = UOp(Ops.CDIV, dtypes.uint, (l, c))
l = UOp(Ops.LOAD, src=(g.index(c),))
a = UOp(Ops.CDIV, src=(l, c))
uops = to_uops_list([a], ren=Device[Device.DEFAULT].renderer)
Device[Device.DEFAULT].renderer.render(uops)
ops = [x.op for x in uops]
@@ -253,10 +253,10 @@ class TestFastIdiv(unittest.TestCase):
self.assertNotIn(Ops.CDIV, ops)
def test_disable_fast_idiv(self):
g = UOp.param(0, dtypes.uint32.ptr(4))
g = UOp.param(0, dtypes.uint32, (4,))
c = UOp.const(dtypes.uint, 3)
l = g.index(c)
a = UOp(Ops.CDIV, dtypes.uint, (l, c))
a = UOp(Ops.CDIV, src=(l, c))
with Context(DISABLE_FAST_IDIV=1):
uops = to_uops_list([a], ren=Device[Device.DEFAULT].renderer)
ops = [x.op for x in uops]
@@ -269,8 +269,8 @@ class TestUOpMethod(unittest.TestCase):
a = UOp.const(dtypes.float, 2.0)
b = UOp.const(dtypes.float, 3.0)
add = UOp(Ops.ADD, dtypes.float, (a, b))
mul = UOp(Ops.MUL, dtypes.float, (a, b))
add = UOp(Ops.ADD, src=(a, b))
mul = UOp(Ops.MUL, src=(a, b))
assert (add < mul) or (mul < add), "add and mul with same src should have an order"
def test_uop_variables(self):
@@ -282,7 +282,7 @@ class TestUOpMethod(unittest.TestCase):
self.assertEqual(list(var_vals)[0], a.expr)
def test_const_factor(self):
gidx0 = UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 8),), 'gidx0')
gidx0 = UOp(Ops.SPECIAL, src=(UOp.const(dtypes.int, 8),), arg='gidx0')
self.assertEqual(UOp.const(dtypes.int, 17).const_factor(), 17)
self.assertEqual(gidx0.const_factor(), 1)
self.assertEqual((gidx0*3).const_factor(), 3)
@@ -290,8 +290,8 @@ class TestUOpMethod(unittest.TestCase):
self.assertEqual((gidx0*3+1).const_factor(), 1)
def test_replace(self):
x = UOp.param(0, dtypes.int.ptr())
self.assertEqual(x.replace(arg=UOp.param(1, dtypes.int.ptr()).arg).arg.slot, 1)
x = UOp.param(0, dtypes.int, (1,))
self.assertEqual(x.replace(arg=UOp.param(1, dtypes.int, (1,)).arg).arg.slot, 1)
with self.assertRaises(AssertionError): x.replace(field="a")
def test_const_zero_neg_zero_different(self):
@@ -315,13 +315,9 @@ class TestUOpStr(unittest.TestCase):
assert str(eval(str(a))) == str(a)
def test_vectorized_str(self):
vec = UOp(Ops.STACK, dtypes.int.vec(4), tuple(UOp.const(dtypes.int, x) for x in range(4)))
vec = UOp(Ops.STACK, src=tuple(UOp.const(dtypes.int, x) for x in range(4)))
assert str(eval(str(vec))) == str(vec)
def test_device_arg(self):
device = UOp(Ops.DEVICE, arg="CL")
assert str(eval(str(device))) == str(device)
def test_reduceop_arg(self):
sum_uop = Tensor.empty(32, 32).sum().uop
assert str(eval(str(sum_uop))) == str(sum_uop)
@@ -348,22 +344,22 @@ class TestUopsObject(unittest.TestCase):
class TestUOpRender(unittest.TestCase):
def test_render_vectorize_empty(self):
u = UOp(Ops.STACK, dtype=dtypes.int.vec(0), src=())
u = UOp(Ops.STACK, dtype=dtypes.void, src=())
self.assertEqual(u.render(simplify=False), "{}")
def test_render_vectorize_empty_simplified(self):
u = UOp(Ops.STACK, dtype=dtypes.int.vec(0), src=())
u = UOp(Ops.STACK, dtype=dtypes.void, src=())
self.assertEqual(u.render(), "{}")
def test_render_vectorize_same(self):
u = UOp(Ops.STACK, dtype=dtypes.int.vec(3), src=(UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 0)))
u = UOp(Ops.STACK, dtype=dtypes.int, src=(UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 0)))
self.assertEqual(u.render(simplify=False), "{0,0,0}")
def test_render_vectorize_different(self):
u = UOp(Ops.STACK, dtype=dtypes.int.vec(3), src=(UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 1), UOp.const(dtypes.int, 2)))
u = UOp(Ops.STACK, dtype=dtypes.int, src=(UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 1), UOp.const(dtypes.int, 2)))
self.assertEqual(u.render(simplify=False), "{0,1,2}")
def test_render_vectorize_same_simplified(self):
u = UOp(Ops.STACK, dtype=dtypes.int.vec(3), src=(UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 0)))
u = UOp(Ops.STACK, dtype=dtypes.int, src=(UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 0)))
self.assertEqual(u.render(), "{0,0,0}")
def test_render_vectorize_different_simplified(self):
u = UOp(Ops.STACK, dtype=dtypes.int.vec(3), src=(UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 1), UOp.const(dtypes.int, 2)))
u = UOp(Ops.STACK, dtype=dtypes.int, src=(UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 1), UOp.const(dtypes.int, 2)))
self.assertEqual(u.render(), "{0,1,2}")
if __name__ == '__main__':
+19 -9
View File
@@ -1,6 +1,6 @@
import unittest
from tinygrad import Tensor
from tinygrad.helpers import GlobalCounters, DEV
from tinygrad.helpers import GlobalCounters
from tinygrad.engine.realize import compile_linear, estimate_uop
from tinygrad.codegen import to_program
from tinygrad.renderer import Estimates
@@ -90,7 +90,6 @@ class TestUOpsStatsMatmulHalf(unittest.TestCase):
expected_ops = N ** 3 * 2
self.assertEqual(expected_ops, GlobalCounters.global_ops)
@unittest.skipIf(DEV.arch=="INTEL", "intel gets 524288 != 524352")
def test_bigger_matmul_half(self): self.test_simple_matmul_half(64)
def test_batched_matmul_half(self, N=16):
@@ -139,23 +138,23 @@ class TestUOpsStats(unittest.TestCase):
#MULACC should have the same stats as MUL + ADD
def test_mulacc(self):
globl = UOp.param(0, dtypes.int.ptr())
globl = UOp.param(0, dtypes.int, (3,))
o1 = UOp(Ops.CONST, dtypes.int, tuple(), 1)
o2 = UOp(Ops.CONST, dtypes.int, tuple(), 2)
u1 = globl.index(o1)
u2 = globl.index(o2)
u3 = UOp(Ops.CONST, dtypes.int, tuple(), 3)
u4 = UOp(Ops.MUL, dtypes.int, (u1,u2))
u5 = UOp(Ops.ADD, dtypes.int, (u4,u3))
u4 = UOp(Ops.MUL, src=(u1,u2))
u5 = UOp(Ops.ADD, src=(u4,u3))
uops = tuple(u5.toposort())
globl = UOp.param(0, dtypes.int.ptr())
globl = UOp.param(0, dtypes.int, (3,))
o1 = UOp(Ops.CONST, dtypes.int, tuple(), 1)
o2 = UOp(Ops.CONST, dtypes.int, tuple(), 2)
u1 = globl.index(o1)
u2 = globl.index(o2)
u3 = UOp(Ops.CONST, dtypes.int, tuple(), 3)
u4 = UOp(Ops.MULACC, dtypes.int, (u1,u2,u3))
u4 = UOp(Ops.MULACC, src=(u1,u2,u3))
uops_fma = tuple(u4.toposort())
self.assertEqual(flops_mem(uops), flops_mem(uops_fma))
@@ -166,19 +165,30 @@ class TestStatsOptimized(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.ast_gemm = (Tensor.empty(N, N) @ Tensor.empty(N, N)).schedule_linear().src[-1].src[0]
cls.ast_gemm_half = (Tensor.empty(N, N, dtype=dtypes.half) @ Tensor.empty(N, N, dtype=dtypes.half)).schedule_linear().src[-1].src[0]
cls.ast_reduce = (Tensor.empty(N*N).sum()).schedule_linear().src[-1].src[0]
def check_gemm(self, p:UOp, extra_flops=0):
def check_gemm(self, p:UOp, extra_flops=0, half=False):
est = p.src[0].arg.estimates
print(p.arg.name, est.ops, est.mem, est.lds)
self.assertEqual(est.ops, 2*N*N*N + extra_flops) # N**3 mulaccs
self.assertEqual(est.mem, 3*N*N*4) # 3 NxN mats with floats
self.assertEqual(est.mem, 3*N*N*(2 if half else 4)) # 3 NxN mats with floats
def test_gemm(self):
p = to_program(replace_opts(self.ast_gemm, []), renderer=Device[Device.DEFAULT].renderer)
self.check_gemm(p)
self.assertEqual(p.src[0].arg.estimates.lds, 2*N*N*N*4 + 4*N*N)
@unittest.skip("fails locally on AMD")
def test_gemm_tc_unroll_half(self):
try:
p = to_program(replace_opts(self.ast_gemm_half, [Opt(OptOps.TC, 0, (-1, 0, 1)), Opt(OptOps.UNROLL, 0, 2)]),
renderer=Device[Device.DEFAULT].renderer)
except KernelOptError:
raise unittest.SkipTest("no tensor cores")
print(p.src[2].arg)
self.check_gemm(p, half=True)
def test_gemm_tc_unroll(self):
try:
p = to_program(replace_opts(self.ast_gemm, [Opt(OptOps.TC, 0, (-1, 0, 1)), Opt(OptOps.UNROLL, 0, 2)]),
+63 -63
View File
@@ -11,168 +11,168 @@ class TestValidateOOB(unittest.TestCase):
# basic index patterns
def test_const_index(self):
with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16))
to_uops_list([buf.index(UOp.const(dtypes.int, 0), ptr=True).load(dtype=dtypes.int)]) # valid
to_uops_list([buf.index(UOp.const(dtypes.int, 15), ptr=True).load(dtype=dtypes.int)]) # valid (last element)
buf = UOp.param(0, dtypes.int, (16,))
to_uops_list([buf.index(UOp.const(dtypes.int, 0)).load(dtype=dtypes.int)]) # valid
to_uops_list([buf.index(UOp.const(dtypes.int, 15)).load(dtype=dtypes.int)]) # valid (last element)
with self.assertRaises(RuntimeError):
to_uops_list([buf.index(UOp.const(dtypes.int, 16), ptr=True).load(dtype=dtypes.int)]) # off by one
to_uops_list([buf.index(UOp.const(dtypes.int, 16)).load(dtype=dtypes.int)]) # off by one
with self.assertRaises(RuntimeError):
to_uops_list([buf.index(UOp.const(dtypes.int, 42), ptr=True).load(dtype=dtypes.int)]) # way out
to_uops_list([buf.index(UOp.const(dtypes.int, 42)).load(dtype=dtypes.int)]) # way out
def test_variable_index(self):
with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16))
to_uops_list([buf.index(Variable("i", 0, 15), ptr=True).load(dtype=dtypes.int)]) # valid
buf = UOp.param(0, dtypes.int, (16,))
to_uops_list([buf.index(Variable("i", 0, 15)).load(dtype=dtypes.int)]) # valid
with self.assertRaises(RuntimeError):
to_uops_list([buf.index(Variable("i", 0, 20), ptr=True).load(dtype=dtypes.int)]) # oob
to_uops_list([buf.index(Variable("i", 0, 20)).load(dtype=dtypes.int)]) # oob
with self.assertRaises(RuntimeError):
to_uops_list([buf.index(Variable("i", -5, 10), ptr=True).load(dtype=dtypes.int)]) # negative
to_uops_list([buf.index(Variable("i", -5, 10)).load(dtype=dtypes.int)]) # negative
def test_range_with_mask(self):
with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16))
buf = UOp.param(0, dtypes.int, (16,))
r = UOp.range(42, 0, AxisType.GLOBAL)
to_uops_list([buf.index(r.valid(r < 16), ptr=True).load(dtype=dtypes.int)]) # valid
to_uops_list([buf.index(r.valid(r < 16)).load(dtype=dtypes.int)]) # valid
with self.assertRaises(RuntimeError):
to_uops_list([buf.index(r.valid(r < 17), ptr=True).load(dtype=dtypes.int)]) # oob
to_uops_list([buf.index(r.valid(r < 17)).load(dtype=dtypes.int)]) # oob
def test_variable_with_mask(self):
with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16))
buf = UOp.param(0, dtypes.int, (16,))
v = Variable("v", -5, 80)
to_uops_list([buf.index(v.valid((v >= 0) & (v < 16)), ptr=True).load(dtype=dtypes.int)]) # valid
to_uops_list([buf.index(v.valid((v >= 0) & (v < 16))).load(dtype=dtypes.int)]) # valid
with self.assertRaises(RuntimeError):
to_uops_list([buf.index(v.valid(v < 20), ptr=True).load(dtype=dtypes.int)]) # negative not masked
to_uops_list([buf.index(v.valid(v < 20)).load(dtype=dtypes.int)]) # negative not masked
def test_gated_store(self):
with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16))
buf = UOp.param(0, dtypes.int, (16,))
v = Variable("v", 0, 20)
to_uops_list([buf.index(v.valid(v < 16), ptr=True).store(0)]) # valid
to_uops_list([buf.index(v.valid(v < 16)).store(0)]) # valid
with self.assertRaises(RuntimeError):
to_uops_list([buf.index(v.valid(v < 20), ptr=True).store(0)]) # oob
to_uops_list([buf.index(v.valid(v < 20)).store(0)]) # oob
# ALU ops in index
def test_floordiv(self):
with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16))
to_uops_list([buf.index(UOp.range(32, 0, AxisType.GLOBAL) // 2, ptr=True).load(dtype=dtypes.int)]) # 0..15 valid
buf = UOp.param(0, dtypes.int, (16,))
to_uops_list([buf.index(UOp.range(32, 0, AxisType.GLOBAL) // 2).load(dtype=dtypes.int)]) # 0..15 valid
with self.assertRaises(RuntimeError):
to_uops_list([buf.index(UOp.range(34, 0, AxisType.GLOBAL) // 2, ptr=True).load(dtype=dtypes.int)]) # 0..16 oob
to_uops_list([buf.index(UOp.range(34, 0, AxisType.GLOBAL) // 2).load(dtype=dtypes.int)]) # 0..16 oob
def test_mod(self):
with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16))
buf = UOp.param(0, dtypes.int, (16,))
r = UOp.range(100, 0, AxisType.GLOBAL)
to_uops_list([buf.index(r % 16, ptr=True).load(dtype=dtypes.int)]) # 0..15 valid
to_uops_list([buf.index(r % 16).load(dtype=dtypes.int)]) # 0..15 valid
with self.assertRaises(RuntimeError):
to_uops_list([buf.index(r % 20, ptr=True).load(dtype=dtypes.int)]) # 0..19 oob
to_uops_list([buf.index(r % 20).load(dtype=dtypes.int)]) # 0..19 oob
def test_shr(self):
with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16))
to_uops_list([buf.index(UOp.range(64, 0, AxisType.GLOBAL) >> 2, ptr=True).load(dtype=dtypes.int)]) # 0..15 valid
buf = UOp.param(0, dtypes.int, (16,))
to_uops_list([buf.index(UOp.range(64, 0, AxisType.GLOBAL) >> 2).load(dtype=dtypes.int)]) # 0..15 valid
with self.assertRaises(RuntimeError):
to_uops_list([buf.index(UOp.range(128, 0, AxisType.GLOBAL) >> 2, ptr=True).load(dtype=dtypes.int)]) # 0..31 oob
to_uops_list([buf.index(UOp.range(128, 0, AxisType.GLOBAL) >> 2).load(dtype=dtypes.int)]) # 0..31 oob
def test_shl(self):
with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(64))
buf = UOp.param(0, dtypes.int, (64,))
r = UOp.range(8, 0, AxisType.GLOBAL)
to_uops_list([buf.index(r << 2, ptr=True).load(dtype=dtypes.int)]) # 0..28 valid
to_uops_list([buf.index(r << 2).load(dtype=dtypes.int)]) # 0..28 valid
with self.assertRaises(RuntimeError):
to_uops_list([buf.index(r << 4, ptr=True).load(dtype=dtypes.int)]) # 0..112 oob
to_uops_list([buf.index(r << 4).load(dtype=dtypes.int)]) # 0..112 oob
def test_and(self):
with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16))
buf = UOp.param(0, dtypes.int, (16,))
r = UOp.range(100, 0, AxisType.GLOBAL)
to_uops_list([buf.index(r & 15, ptr=True).load(dtype=dtypes.int)]) # 0..15 valid
to_uops_list([buf.index(r & 15).load(dtype=dtypes.int)]) # 0..15 valid
with self.assertRaises(RuntimeError):
to_uops_list([buf.index(r & 31, ptr=True).load(dtype=dtypes.int)]) # 0..31 oob
to_uops_list([buf.index(r & 31).load(dtype=dtypes.int)]) # 0..31 oob
def test_max(self):
with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16))
to_uops_list([buf.index(Variable("v", -10, 15).maximum(0), ptr=True).load(dtype=dtypes.int)]) # 0..15 valid
buf = UOp.param(0, dtypes.int, (16,))
to_uops_list([buf.index(Variable("v", -10, 15).maximum(0)).load(dtype=dtypes.int)]) # 0..15 valid
with self.assertRaises(RuntimeError):
to_uops_list([buf.index(Variable("v2", -10, 20).maximum(0), ptr=True).load(dtype=dtypes.int)]) # 0..20 oob
to_uops_list([buf.index(Variable("v2", -10, 20).maximum(0)).load(dtype=dtypes.int)]) # 0..20 oob
def test_xor_in_mask(self):
with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16))
buf = UOp.param(0, dtypes.int, (16,))
r = UOp.range(32, 0, AxisType.GLOBAL)
to_uops_list([buf.index(r.valid((r < 8) ^ ((r >= 8) & (r < 16))), ptr=True).load(dtype=dtypes.int)]) # 0..15 valid
to_uops_list([buf.index(r.valid((r < 8) ^ ((r >= 8) & (r < 16)))).load(dtype=dtypes.int)]) # 0..15 valid
with self.assertRaises(RuntimeError):
to_uops_list([buf.index(r.valid((r < 10) ^ (r >= 20)), ptr=True).load(dtype=dtypes.int)]) # 0..9,20..31 oob
to_uops_list([buf.index(r.valid((r < 10) ^ (r >= 20))).load(dtype=dtypes.int)]) # 0..9,20..31 oob
# cast patterns
def test_float_cast_in_index(self):
with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(16))
buf = UOp.param(0, dtypes.int, (16,))
r = UOp.range(20, 0)
i = (r.cast(dtypes.float) * 0.68).trunc().cast(dtypes.int)
to_uops_list([buf.index(i.valid((i >= 0) & (i < 16)), ptr=True).load(dtype=dtypes.int)])
to_uops_list([buf.index(i.valid((i >= 0) & (i < 16))).load(dtype=dtypes.int)])
def test_bool_cast_in_mask(self):
with Context(CHECK_OOB=1, SPEC=2):
buf = UOp.param(0, dtypes.int.ptr(1))
buf = UOp.param(0, dtypes.int, (1,))
r = UOp.range(20, 0)
to_uops_list([buf.index(r.valid(r.cast(dtypes.bool).logical_not()), ptr=True).load(dtype=dtypes.int)]) # only r=0 valid
to_uops_list([buf.index(r.valid(r.cast(dtypes.bool).logical_not())).load(dtype=dtypes.int)]) # only r=0 valid
# load result as index/mask
def test_load_as_index(self):
with Context(CHECK_OOB=1, SPEC=2):
buf0 = UOp.param(0, dtypes.int.ptr(16))
buf1 = UOp.param(1, dtypes.int.ptr(64))
buf0 = UOp.param(0, dtypes.int, (16,))
buf1 = UOp.param(1, dtypes.int, (64,))
r = UOp.range(42, 0, AxisType.GLOBAL)
ld0 = buf0.index(r.valid(r < 8), ptr=True).load(dtype=dtypes.int).cast(dtypes.weakint)
to_uops_list([buf1.index((ld0 * 2).valid((ld0 >= 0) & (ld0 < 32)), ptr=True).load(dtype=dtypes.int)]) # valid
ld0 = buf0.index(r.valid(r < 8)).load(dtype=dtypes.int).cast(dtypes.index)
to_uops_list([buf1.index((ld0 * 2).valid((ld0 >= 0) & (ld0 < 32))).load(dtype=dtypes.int)]) # valid
with self.assertRaises(RuntimeError):
to_uops_list([buf1.index((ld0 * 2).valid((ld0 >= 0) & (ld0 < 64)), ptr=True).load(dtype=dtypes.int)]) # oob
to_uops_list([buf1.index((ld0 * 2).valid((ld0 >= 0) & (ld0 < 64))).load(dtype=dtypes.int)]) # oob
def test_load_bool_as_mask(self):
with Context(CHECK_OOB=1, SPEC=2):
buf_bool = UOp.param(0, dtypes.bool.ptr(16))
buf_int = UOp.param(1, dtypes.int.ptr(8))
gidx = UOp(Ops.SPECIAL, dtypes.weakint, (UOp.const(dtypes.weakint, 16),), "gidx0")
ld_bool = buf_bool.index(gidx, ptr=True).load()
buf_bool = UOp.param(0, dtypes.bool, (16,))
buf_int = UOp.param(1, dtypes.int, (8,))
gidx = UOp(Ops.SPECIAL, src=(UOp.const(dtypes.index, 16),), arg="gidx0")
ld_bool = buf_bool.index(gidx).load()
with self.assertRaises(RuntimeError):
to_uops_list([buf_int.index(gidx.valid(ld_bool), ptr=True).load()]) # gidx 0..15, buf_int size 8
to_uops_list([buf_int.index(gidx.valid(ld_bool)).load()]) # gidx 0..15, buf_int size 8
# skipped tests (moved from test_uop_graph.py)
@unittest.skip("if not allowed in graph")
def test_in_bounds_access_gated_local(self):
with Context(CHECK_OOB=1):
# Define buffers
gbuf = UOp.param(0, dtypes.uint.ptr(400))
gbuf = UOp.param(0, dtypes.uint, (400,))
sbuf = UOp.placeholder((8,), dtypes.uint, slot=0, addrspace=AddrSpace.LOCAL)
# Define indices, valids and barrier
gidx = UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 416),), "gidx0")
lidx = UOp(Ops.SPECIAL, dtypes.int, (UOp.const(dtypes.int, 10),), "lidx0")
gidx = UOp(Ops.SPECIAL, src=(UOp.const(dtypes.int, 416),), arg="gidx0")
lidx = UOp(Ops.SPECIAL, src=(UOp.const(dtypes.int, 10),), arg="lidx0")
gate = (gidx<400) & (lidx<8)
local_store = sbuf.index(lidx.valid(lidx<8)).store(UOp.const(dtypes.uint, 1))
barrier = UOp(Ops.BARRIER, dtypes.void, (local_store,))
if_barrier = UOp(Ops.IF, dtypes.void, (gate, barrier))
barrier = UOp(Ops.BARRIER, src=(local_store,))
if_barrier = UOp(Ops.IF, src=(gate, barrier))
# Load from local memory (after the IF/barrier)
local_load = UOp(Ops.LOAD, dtypes.uint, (sbuf.index(lidx, ptr=True), if_barrier))
local_load = UOp(Ops.LOAD, src=(sbuf.index(lidx), if_barrier))
# Store to global memory
global_store = UOp(Ops.STORE, dtypes.void, (gbuf.index(gidx), local_load))
global_store = UOp(Ops.STORE, src=(gbuf.index(gidx), local_load))
to_uops_list([global_store])
@unittest.skip("Bool load is not supported yet")
def test_load_mask(self):
with Context(CHECK_OOB=1):
glbl0 = UOp.param(0, dtypes.int.ptr(16))
mask = UOp.param(0, dtypes.bool.ptr(16))
glbl0 = UOp.param(0, dtypes.int, (16,))
mask = UOp.param(0, dtypes.bool, (16,))
ridx = UOp.range(20, 0)
ld0 = UOp(Ops.LOAD, dtypes.int, (glbl0.index(UOp.const(ridx, ridx<16&mask), ptr=True)))
ld0 = UOp(Ops.LOAD, src=(glbl0.index(UOp.const(ridx, ridx<16&mask))))
to_uops_list([ld0])
if __name__ == "__main__":
+8 -8
View File
@@ -211,9 +211,10 @@ class TestViz(unittest.TestCase):
graphs = flatten(x["graph"].values() for x in viz.get_details(0, 0))
self.assertEqual(graphs[0], uop_to_json(VizData(), a)[id(a)])
self.assertEqual(graphs[1], uop_to_json(VizData(), b)[id(b)])
# fallback to NOOP with the error message
nop = UOp(Ops.NOOP, arg="infinite loop in fixed_point_rewrite")
self.assertEqual(graphs[2], uop_to_json(VizData(), nop)[id(nop)])
# fallback to REWRITE_ERROR with the error message
self.assertIn("REWRITE_ERROR\nTraceback", graphs[2]["label"])
# cut after the first error, instead of going through all REWRITE_STACK_LIMIT matches
self.assertEqual(len(graphs), 3)
def test_walk_rewrite(self):
from tinygrad.uop.ops import _substitute
@@ -251,8 +252,8 @@ class TestViz(unittest.TestCase):
self.assertEqual(list(graphs[1]), [id(z), id(y), id(ret)])
def test_const_reshape_expand_folded(self):
# CONST->RESHAPE->EXPAND should be folded into the ALU node, not shown as separate RESHAPE/EXPAND nodes
c = UOp.const(dtypes.float, 1.0, shape=(3,4)) # creates CONST->RESHAPE->EXPAND chain
# CONST->EXPAND should be folded into the ALU node, not shown as separate EXPAND nodes
c = UOp.const(dtypes.float, 1.0, shape=(3,4)) # creates CONST->EXPAND chain
a = UOp.variable("a", 0.0, 10.0, dtypes.float)
alu = a + c
with save_viz() as viz:
@@ -261,19 +262,18 @@ class TestViz(unittest.TestCase):
excluded_nodes = {v["label"].split("\n")[0] for v in graph.values() if v["exclude"]}
self.assertIn("CONST", excluded_nodes)
self.assertIn("STACK", excluded_nodes)
self.assertIn("RESHAPE", excluded_nodes)
self.assertIn("EXPAND", excluded_nodes)
self.assertIn("CONST1 1", graph[id(alu)]["label"])
def test_stack_movement_not_folded_unless_all_const(self):
a = UOp.variable("a", 0, 10, dtype=dtypes.int)
c = UOp.const(dtypes.int, 1)
stack = a.vectorize(c)
stack = a.stack(c)
reshaped = stack.reshape((1, 2))
graph = uop_to_json(VizData(), reshaped)
self.assertFalse(graph[id(stack)]["exclude"])
const_stack = c.vectorize(UOp.const(dtypes.int, 2))
const_stack = c.stack(UOp.const(dtypes.int, 2))
const_reshaped = const_stack.reshape((1, 2))
const_graph = uop_to_json(VizData(), const_reshaped)
self.assertTrue(const_graph[id(const_stack)]["exclude"])
+42
View File
@@ -103,6 +103,16 @@ class TestAssign(unittest.TestCase):
out = x.item()
assert out == 1, f"expected 1, got {out}"
def test_pending_assign_chain_preserves_intermediate_reads(self):
x = Tensor([0.0]).contiguous().realize()
y0 = x + 0
x.assign(x + 1)
y1 = x + 0
x.assign(x + 1)
y2 = x + 0
x.assign(x + 1)
assert [y0.item(), y1.item(), y2.item(), x.item()] == [0.0, 1.0, 2.0, 3.0]
def test_assign_add_jit(self):
@TinyJit
def f(x):
@@ -306,6 +316,16 @@ class TestAssign(unittest.TestCase):
t.assign(t + 100)
np.testing.assert_equal(t.numpy(), [[100, 104, 108, 112], [101, 105, 109, 113], [102, 106, 110, 114], [103, 107, 111, 115]])
def test_assign_corealize_order_independent(self):
for order in [lambda x,y: Tensor.realize(x, y), lambda x,y: Tensor.realize(y, x)]:
x = Tensor([1.0]).realize()
y = x + 10
x.assign(x*2)
x.assign(x+3)
order(x, y)
self.assertEqual(y.tolist(), [11.0])
self.assertEqual(x.tolist(), [5.0])
def test_assign_contiguous(self):
b = Tensor.arange(16).reshape(4,4).clone().realize()
a = (Tensor.arange(16).reshape(4,4).clone().realize() + 1)
@@ -853,6 +873,28 @@ class TestAssignOrdering(unittest.TestCase):
b_np *= 0.9
np.testing.assert_allclose(param.item(), p_np, atol=1e-5)
def test_war_reader_already_depends_on_write(self):
x = Tensor([1.0]).contiguous().realize()
y = Tensor([2.0]).contiguous().realize()
x_expr = x + 10
x.assign(x * 2)
y.assign(y + x)
z = y + x_expr
Tensor.realize(x, y, z)
# TODO: z should be 15: x_expr means 11 (x captured at build time), but the read is fused past the assign and
# sees the new bytes. once stale readers are scheduled before the overwrite, update this to 15
np.testing.assert_allclose([x.item(), y.item(), z.item()], [2.0, 4.0, 16.0])
def test_war_multi_read_then_assign(self):
devices = ("CPU:0", "CPU:1")
for realize_reader_first in (False, True):
buf = Tensor([1., 2., 3., 4.], device="CPU").contiguous().realize().shard(devices, 0).realize()
stale = buf.to("CPU")
buf.assign(Tensor.full(buf.shape, 10.0, device="CPU").shard(devices, 0).contiguous().realize())
Tensor.realize(stale, buf) if realize_reader_first else Tensor.realize(buf, stale)
np.testing.assert_equal(stale.numpy(), [1., 2., 3., 4.])
np.testing.assert_equal(buf.numpy(), [10., 10., 10., 10.])
def test_multiple_slice_assigns_then_read(self):
"""Multiple non-overlapping slice assigns then read."""
buf = Tensor.zeros(4).contiguous().realize()
+9 -2
View File
@@ -3,7 +3,7 @@ import numpy as np
from tinygrad import Tensor, Device, dtypes
from tinygrad.dtype import DType, DTYPES_DICT
from tinygrad.nn.state import safe_load, safe_save, get_state_dict, torch_load
from tinygrad.helpers import Timing, fetch, OSX, dedup
from tinygrad.helpers import Timing, fetch, OSX, dedup, Context
from test.helpers import slow
class TempDirTestCase(unittest.TestCase):
@@ -88,7 +88,7 @@ class TestRawDiskBuffer(unittest.TestCase):
# Those two should be moved to test_dtype.py:test_shape_change_bitcast after bitcast works on non-disk
with self.assertRaises(RuntimeError):
# should fail because 3 int8 is 3 bytes but float16 is two and 3 isn't a multiple of 2
Tensor.empty((3,), dtype=dtypes.int8, device=f"DISK:{tmp}").bitcast(dtypes.float16)
Tensor.empty((3,), dtype=dtypes.int8, device=f"DISK:{tmp}").bitcast(dtypes.float16).shape
pathlib.Path(tmp).unlink()
@@ -410,6 +410,13 @@ class TestDiskTensor(TempDirTestCase):
on_dev = t.to(Device.DEFAULT).realize()
np.testing.assert_equal(on_dev.numpy(), t.numpy())
def test_shard_copy_from_disk_slice(self):
fn = pathlib.Path(self.tmp("dt_shard_copy_from_disk_slice"))
fn.write_bytes(bytes(range(32)))
with Context(CACHELEVEL=0):
t = Tensor.empty(8, 4, device=f"disk:{fn}", dtype=dtypes.uint8)[0:4].shard(("CPU:0", "CPU:1"), axis=0).realize()
np.testing.assert_equal(t.to("CPU").numpy(), np.arange(16, dtype=np.uint8).reshape(4, 4))
@slow
def test_copy_from_disk_huge(self):
fn = pathlib.Path(self.tmp("dt_copy_from_disk_huge"))

Some files were not shown because too many files have changed in this diff Show More