-
Notifications
You must be signed in to change notification settings - Fork 28
[Graph] Support member ndarrays for qd.checkpoint and qd.graph_do_while #760
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 3 commits
b0101fa
f785888
d3de129
58bba23
9c45a87
f3c9cbe
a691735
9f28295
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 |
|---|---|---|
|
|
@@ -87,7 +87,7 @@ solve(x, counter) | |
| # x is now incremented 10 times; counter is 0 | ||
| ``` | ||
|
|
||
| The argument to `qd.graph_do_while()` must be the name of a scalar `qd.i32` ndarray parameter. The loop body repeats while this value is non-zero. | ||
| The argument to `qd.graph_do_while()` must reference a scalar `qd.i32` ndarray that the kernel can access — a bare kernel parameter (`qd.graph_do_while(counter)`), a `@qd.data_oriented` member ndarray (`qd.graph_do_while(self.counter)`), or a `@dataclasses.dataclass` parameter member (`qd.graph_do_while(params.counter)`). The loop body repeats while this value is non-zero. | ||
|
|
||
| - On CUDA SM 9.0+ (Hopper), this uses CUDA conditional while nodes — the entire iteration runs on the GPU with no host involvement. | ||
| - On older CUDA GPUs, AMDGPU, and non-GPU backends, it falls back to a host-side do-while loop (see the [backend support table](#backend-support)). | ||
|
|
@@ -256,7 +256,7 @@ while status.yielded: | |
|
|
||
| - Must be used inside `@qd.kernel(graph=True, checkpoints=True)`. Without the flag, `qd.checkpoint(...)` raises `QuadrantsSyntaxError` at compile time with a fix-it pointing at `checkpoints=True`. | ||
| - `cp_id` must be an int literal or an `IntEnum` value, and must be unique across the kernel. | ||
| - `yield_on=` must be a kernel parameter that is a 0-d `qd.types.ndarray(qd.i32, ndim=0)`; expressions are not supported. | ||
| - `yield_on=` must reference a 0-d `qd.types.ndarray(qd.i32, ndim=0)` — a bare kernel parameter (`yield_on=flag`), a `@qd.data_oriented` member ndarray (`yield_on=self.flag`), or a `@dataclasses.dataclass` parameter member (`yield_on=params.flag`). Arbitrary expressions are not supported. | ||
|
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. Same remark. |
||
| - Checkpoints cannot be nested inside other checkpoints. Checkpoints inside a `qd.graph_do_while` body are fine. | ||
| - The body of a `with qd.checkpoint(...)` block cannot contain bare top-level statements (assignments, augmented assignments, or bare call/expression statements). Every top-level statement must be inside a `for`-loop (or other control-flow construct). A docstring as the first statement is allowed. Bare statements raise `QuadrantsSyntaxError` at compile time with a fix-it pointing at the explicit one-iteration `for`-wrap: | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,8 +18,10 @@ | |
| from .python_side_cache import PythonSideCache | ||
|
|
||
| # Bumped whenever the persisted CacheValue schema changes (see create_cache_key). v2 replaced the single | ||
| # graph_do_while_arg string with a nested level table. | ||
| _CACHE_VALUE_SCHEMA_VERSION = "cachevalue-v2-gdw-levels" | ||
| # graph_do_while_arg string with a nested level table. v3 added the AST-resolved flat C++ arg-ids for | ||
| # qd.graph_do_while conditions and qd.checkpoint(yield_on=...) targets so the launch path can forward them | ||
| # directly without per-launch name matching (necessary for @qd.data_oriented member ndarrays). | ||
| _CACHE_VALUE_SCHEMA_VERSION = "cachevalue-v3-ast-resolved-ids" | ||
|
|
||
|
|
||
| def create_cache_key( | ||
|
|
@@ -69,17 +71,31 @@ class CacheValue(BaseModel): | |
| frontend_cache_key: str | ||
| hashed_function_source_infos: list[HashedFunctionSourceInfo] | ||
| used_py_dataclass_parameters: set[str] | ||
| # Nested graph_do_while level table as (cond_arg_name, parent_id) pairs, indexed by level id. None / empty for | ||
| # kernels without graph_do_while. | ||
| graph_do_while_levels: list[tuple[str, int]] | None = None | ||
| # Nested graph_do_while level table as (cond_arg_name, parent_id, cond_cpp_arg_id) triples, indexed by level | ||
| # id. None / empty for kernels without graph_do_while. ``cond_cpp_arg_id`` is the flat C++ arg-id resolved at | ||
| # AST-build time by ``ASTTransformer._resolve_ndarray_kernel_arg_id`` and is required by the launch path to | ||
| # support `@qd.data_oriented` member conditions (`qd.graph_do_while(self.counter)`) -- name-matching against | ||
| # ``arg_metas`` only resolves top-level parameters. | ||
| graph_do_while_levels: list[tuple[str, int, int]] | None = None | ||
| # AST-build-time-resolved checkpoint metadata, indexed by internal cp_id. Empty for kernels without any | ||
| # `with qd.checkpoint(...)` block. See `Kernel.checkpoint_yield_on_args` / | ||
| # `Kernel.checkpoint_yield_on_cpp_arg_ids` / `Kernel.checkpoint_user_labels_by_cp_id` for what each entry means. | ||
| # Restored alongside the C++-side cached kernel so the launch path can forward `yield_on=` arg-ids and | ||
| # translate `from_checkpoint=` labels without re-running the AST transformer. | ||
| checkpoint_yield_on_args: list[str | None] = [] | ||
| checkpoint_yield_on_cpp_arg_ids: list[int] = [] | ||
| checkpoint_user_labels_by_cp_id: list[int | None] = [] | ||
|
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.
When a checkpoint uses an Useful? React 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. Good catch -- confirmed and fixed in 58bba23. Pydantic coerces Tests:
Both pass on x64 and CUDA on the cluster. Older v3 caches just invalidate via the version bump, no migration needed. |
||
|
|
||
|
|
||
| def store( | ||
| frontend_cache_key: str, | ||
| fast_cache_key: str, | ||
| function_source_infos: Iterable[FunctionSourceInfo], | ||
| used_py_dataclass_parameters: set[str], | ||
| graph_do_while_levels: list[tuple[str, int]] | None = None, | ||
| graph_do_while_levels: list[tuple[str, int, int]] | None = None, | ||
| checkpoint_yield_on_args: list[str | None] | None = None, | ||
| checkpoint_yield_on_cpp_arg_ids: list[int] | None = None, | ||
| checkpoint_user_labels_by_cp_id: list[int | None] | None = None, | ||
| ) -> None: | ||
| """ | ||
| Note that unlike other caches, this cache is not going to store the actual value we want. | ||
|
|
@@ -108,6 +124,9 @@ def store( | |
| hashed_function_source_infos=list(hashed_function_source_infos), | ||
| used_py_dataclass_parameters=used_py_dataclass_parameters, | ||
| graph_do_while_levels=graph_do_while_levels, | ||
| checkpoint_yield_on_args=checkpoint_yield_on_args or [], | ||
| checkpoint_yield_on_cpp_arg_ids=checkpoint_yield_on_cpp_arg_ids or [], | ||
| checkpoint_user_labels_by_cp_id=checkpoint_user_labels_by_cp_id or [], | ||
| ) | ||
| cache.store(fast_cache_key, cache_value_obj.model_dump_json()) | ||
|
|
||
|
|
@@ -125,23 +144,19 @@ def _try_load(cache_key: str) -> CacheValue | None: | |
| return cache_value_obj | ||
|
|
||
|
|
||
| def load( | ||
| cache_key: str, | ||
| ) -> tuple[set[str], str, list[tuple[str, int]] | None] | tuple[None, None, None]: | ||
| """ | ||
| loads function source infos from cache, if available | ||
| checks the hashes against the current source code | ||
| def load(cache_key: str) -> CacheValue | None: | ||
| """Load a validated ``CacheValue`` for *cache_key* if one exists and its source hashes still match, else None. | ||
|
|
||
| Returns the full ``CacheValue`` (rather than the historical 3-tuple) so callers can pick off the | ||
| AST-transformer-produced metadata (graph_do_while levels, checkpoint tables) without the loader having to grow | ||
| a new return slot every time we cache a new piece of AST output. | ||
| """ | ||
| cache_value = _try_load(cache_key) | ||
| if cache_value is None: | ||
| return None, None, None | ||
| return None | ||
| if function_hasher.validate_hashed_function_infos(cache_value.hashed_function_source_infos): | ||
| return ( | ||
| cache_value.used_py_dataclass_parameters, | ||
| cache_value.frontend_cache_key, | ||
| cache_value.graph_do_while_levels, | ||
| ) | ||
| return None, None, None | ||
| return cache_value | ||
| return None | ||
|
|
||
|
|
||
| def dump_stats() -> None: | ||
|
|
||
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.
'
qd.graph_do_while()' with parentheses is confusing."a bare kernel parameter, a
@qd.data_orientedmember ndarray, or a@dataclasses.dataclassparameter member" sounds good enough?