-
Notifications
You must be signed in to change notification settings - Fork 26
[DataOriented] Fastcache, perf, pruning #705
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 126 commits
06b7c6a
d4350ef
97afa6d
49a723b
9bdeca5
dc7997b
906ce19
a0db648
c9598ad
93893e5
ce769a7
dd4de40
46825ab
ee5fbbb
b132b81
6d1c820
1de65b9
a648c3f
a55d360
6667ba6
e9c50b4
abf242b
4c27e2e
1f539e6
730cbcb
57e1b95
d4ca211
b72a7a7
18ff7bd
33f4744
883243e
3504250
cc01339
df3113e
7f5fd12
e7fafeb
f9a35df
d336dcd
4c5f622
56a4399
ef5f8a6
06580f1
8899357
8fef507
92f5fe1
9ea8e5b
6ff0848
fd8cd0a
004cd9a
bf85e4e
ccaae54
820c01a
06d2e86
f7dd090
8c0377c
71a53da
46fef24
18f995b
3ce0ab0
35be370
94e455a
31b27d7
36dc933
07dc486
c7d6737
93f597e
c25f49c
8f64016
aa9a88f
fd8c440
e3a3d88
067a471
cc1e380
34f8532
5e54902
55ecf95
6d9c307
49ffb3b
7757907
fb38fec
7cabaa0
b5b360a
3aa4fe1
dce1305
984ac40
12fb215
356394e
45129bc
1f25d9c
5fc9b4c
89bb005
710ee47
090f1a8
be4b030
75c08f6
29dd841
4bd2d10
aef1a26
197d150
173b051
a47a5ab
5debfe4
4e714c7
39602c6
bd37c94
59ce5ff
a63b834
f6c68d8
ae36b11
c61d32c
706f9b5
4398af7
8a7ead4
8861dc0
e8bcd18
44a535f
6805ce3
e4bf125
2719dca
4a97b91
cfeac8b
ed3b6c2
0415c05
9fb8287
66d095f
0401c7a
d32e8fc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -95,14 +95,17 @@ Fastcache supports the following parameter types: | |
| | `qd.types.NDArray` (scalar, vector, matrix) | Yes | dtype, ndim, layout | | ||
| | `torch.Tensor` | Yes | dtype, ndim | | ||
| | `numpy.ndarray` | Yes | dtype, ndim | | ||
| | `dataclasses.dataclass` | Yes | member types recursively; member values if annotated with `FIELD_METADATA_CACHE_VALUE` (see [Appendix — compound-type cache keying](#compound-type-cache-keying)) | | ||
| | `@qd.data_oriented` objects | Yes | member types recursively; primitive member types and values baked into kernel (see [Appendix — compound-type cache keying](#compound-type-cache-keying)) | | ||
| | `dataclasses.dataclass` | Yes | member types recursively (narrowed to members the kernel reads or writes); member values if annotated with `FIELD_METADATA_CACHE_VALUE` (see [Advanced — compound-type cache keying](#compound-type-cache-keying)) | | ||
| | `@qd.data_oriented` objects | Yes | member types recursively (narrowed to members the kernel reads or writes); primitive member types and values baked into kernel (see [Advanced — compound-type cache keying](#compound-type-cache-keying)) | | ||
| | `qd.Template` primitives (int, float, bool) | Yes | type and value (baked into kernel) | | ||
| | Non-template primitives (int, float, bool) | Yes | type only | | ||
| | `enum.Enum` | Yes | name and value | | ||
| | `qd.field` / `ScalarField` / `MatrixField` | **No** | — | | ||
| | `qd.field` / `ScalarField` / `MatrixField` at a kernel-read path | **No** | — | | ||
| | Anything else at a kernel-read path | **No** | — | | ||
|
|
||
| If any parameter is of an unsupported type, fastcache is disabled for that call and the kernel falls back to normal compilation. For `qd.field` / `ScalarField` / `MatrixField` arriving through a `qd.Tensor`-annotated parameter, this is silent — no warning is emitted. For other unsupported types, a warning is logged at the `warn` level identifying the offending parameter. | ||
| If any kernel-used parameter is of an unsupported type, fastcache is disabled for that call and the kernel falls back to normal compilation. For `qd.field` / `ScalarField` / `MatrixField` arriving through a `qd.Tensor`-annotated parameter, this is silent — no warning is emitted. For other unsupported types, a warning is logged at the `warn` level identifying the offending parameter. | ||
|
|
||
| Kernel-unused members of any type — including unrecognised ones — do **not** disable fastcache. Fastcache skips them entirely, so opaque metadata (UUIDs, Pydantic configs, parent back-pointers) attached to a `@qd.data_oriented` or `dataclasses.dataclass` instance is harmless as long as the kernel doesn't read it. | ||
|
|
||
| ### 3. Source code must be available | ||
|
|
||
|
|
@@ -120,6 +123,12 @@ Each compiled artifact is stored under a key derived from all of the following: | |
|
|
||
| When any of these change, the resulting key is different, so a new compilation occurs and a new entry is stored. Previous entries remain on disk — multiple cached versions coexist. You do not need to manually clear the cache when making code changes — the hash mismatch causes a transparent recompilation. | ||
|
|
||
| ### Two strict invariants | ||
|
|
||
| 1. **If the kernel does not read or write a variable, it is entirely ignored by fastcache.** It will not cause fastcache to fail, nor emit a warning, nor emit an error. | ||
|
|
||
| 2. **Unrecognised types at variables the kernel reads or writes must not be silently dropped or hashed by type-name.** If the value of such a variable has a type fastcache doesn't explicitly handle (Pydantic models, UUIDs, third-party tensor wrappers, …), fastcache is disabled for the call with a one-shot `[FASTCACHE][UNKNOWN_TYPE]` warning identifying the offending type plus an `[INVALID_FUNC]` log line confirming the cache is off. | ||
|
|
||
| ## Advanced | ||
|
|
||
| ### Diagnostics | ||
|
|
@@ -143,32 +152,25 @@ print(obs.cache_stored) # True if the compiled kernel was stored to cach | |
|
|
||
| On the first run you'll see `cache_stored=True` but `cache_loaded=False`. On the second run (after `qd.init`), `cache_loaded=True`. | ||
|
|
||
| ## Appendix | ||
|
|
||
| ### Compound-type cache keying | ||
|
|
||
| The args hasher walks compound-type kernel parameters recursively. For each leaf member it decides what (if anything) contributes to the cache key. The headline rules: | ||
| For `@qd.data_oriented` and `dataclasses.dataclass` kernel parameters, fastcache walks members recursively. Any members that are not themselves read or written by the kernel, nor contain members read or written by the kernel, are skipped during the walk (per the [strict invariants](#two-strict-invariants) above). Member-by-member behavior: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is redundant with:
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it? I think they're highly related, but my reading is:
Talks about what happens if the args hasher hits an unrecognized type. And says that an unrecognized type should trigger some kind of warning, not just be silently ignored.
This talks about what happens to members that the kernel never reads nor writes: tehy are pruned from all consideration. Now, these two things are related but different. They are related because typically the various things that might be unreocgnized, suhc as id etc, are pruned, because the kernel does not read/write them, and therefore don't lead to a warning. But they are not the same, and in fact pull in differennt directions. The first thing posts warnings for unreognized things. The second thing removes those unregonzied things altogether, avoidign the warnings. Not sure if the doc should be restrutured, or rephrased, somehow to make these things clearer somehow? |
||
|
|
||
| **`@qd.data_oriented`:** the walker descends into `vars(obj)`. For each child: | ||
| - **`qd.ndarray` member** — `(dtype, ndim, layout)` is included in the cache key. Element values are not. | ||
| - **Primitive (`int` / `float` / `bool` / `enum.Enum`) member.** The handling depends on the enclosing container: | ||
| - In a `@qd.data_oriented` instance — value is baked into the kernel, same as a `qd.Template` primitive. Two instances of the same class with different primitive member values get different cache entries. | ||
| - In a `dataclasses.dataclass` instance — only the type is included by default. To include the value too, annotate the field with `FIELD_METADATA_CACHE_VALUE`: | ||
|
|
||
| - `qd.ndarray` member — `(dtype, ndim, layout)` is included in the cache key. Element values are not. | ||
| - Primitive (`int` / `float` / `bool` / `enum.Enum`) member — value is baked into the kernel (same semantics as a `qd.Template` primitive). Two instances of the same class with different primitive member values get different cache entries. | ||
| - Nested `@qd.data_oriented` member — recurses. | ||
| - Nested `dataclasses.dataclass` member — recurses (with the dataclass rules below). | ||
| - `qd.field` member — fastcache is disabled for the entire kernel call. The kernel still runs via normal compilation; a warn-level log line is emitted. | ||
|
|
||
| **`dataclasses.dataclass`:** the walker descends into the declared members. For each member, only the *type* is included in the cache key by default — **not** the value. To include a member's value, annotate it: | ||
|
|
||
| ```python | ||
| import dataclasses | ||
| from quadrants.lang._fast_caching import FIELD_METADATA_CACHE_VALUE | ||
|
|
||
| @dataclasses.dataclass | ||
| class SimConfig: | ||
| num_layers: int = dataclasses.field(metadata={FIELD_METADATA_CACHE_VALUE: True}) | ||
| dt: float = dataclasses.field(metadata={FIELD_METADATA_CACHE_VALUE: True}) | ||
| ``` | ||
| ```python | ||
| import dataclasses | ||
| from quadrants.lang._fast_caching import FIELD_METADATA_CACHE_VALUE | ||
|
|
||
| This is necessary whenever the compiled kernel depends on the member's *value* rather than just its type (for example, when the value is used as a loop bound that the compiler bakes into the generated code). Without the annotation, two `SimConfig` instances with different `num_layers` values would share a fastcache key, and the second instance would silently load a kernel compiled for the wrong value. | ||
| @dataclasses.dataclass | ||
| class SimConfig: | ||
| num_layers: int = dataclasses.field(metadata={FIELD_METADATA_CACHE_VALUE: True}) | ||
| dt: float = dataclasses.field(metadata={FIELD_METADATA_CACHE_VALUE: True}) | ||
| ``` | ||
|
|
||
| Note the asymmetry: `@qd.data_oriented` primitive members are baked into the kernel automatically (same semantics as `qd.Template`); `dataclasses.dataclass` members contribute only their *type* to the cache key unless you opt in per-member. | ||
| Annotate any member whose *value* (not just type) affects the compiled kernel. Primarily this means any variable used inside [`qd.static`](static.md). | ||
| - **Nested `@qd.data_oriented` or `dataclasses.dataclass` member** — recurses with the same rules (so an `int` inside a nested `@qd.data_oriented` is still baked into the kernel; an `int` inside a nested `dataclasses.dataclass` still needs `FIELD_METADATA_CACHE_VALUE` to bake its value). | ||
| - **`qd.field` member** — fastcache is disabled for the entire kernel call. The kernel still runs via normal compilation; a warn-level log line is emitted. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this behaviour should be enforced via post-init freeze. But I think it is tricky to get right without annoying boilerplate on user-side and/or performance penalty.
Even Python dataclass does not offer a clean way to customise init when frozen.
https://docs.python.org/3/library/dataclasses.html#dataclasses
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, so, just to confirm, this comment is more like an observation, and no action is expected on my part?