Skip to content

Give chunk_kda and recurrent_kda a string-enum impl selector - #318

Merged
drisspg merged 1 commit into
mainfrom
drisspg/stack/80
Aug 17, 2026
Merged

Give chunk_kda and recurrent_kda a string-enum impl selector#318
drisspg merged 1 commit into
mainfrom
drisspg/stack/80

Conversation

@drisspg

@drisspg drisspg commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Give chunk_kda and recurrent_kda a string-enum impl selector

Human Note

Agent note

The two algorithmic forms stay separate functions because their gate contracts differ
(chunk-local cumulative vs per-token log2 decay), but naive-versus-fused is now an argument
instead of parallel naive_* names, following the shape of gdn's existing mode/backend split and
FLA's ops-layer layout. Impl is a str enum ("fused" | "reference") shared from
attn_gym.linear.impl; both ops accept the enum or its string value, and unknown selectors list
the valid set. There is deliberately no "auto" fallback: a training run should never silently land
on the eager reference.

The public wrappers move to torch-only attn_gym.linear.kda.api, which validates the shared
contract and imports the fused backends lazily inside the fused branch, so chunk_kda and
recurrent_kda are now eager exports and the PEP 562 lazy set shrinks to the standalone kernels.
impl="reference" runs the naive oracles in FP32 behind the identical packed contract (empty
padding slots pass state through, capacity tails stay out of contract, states come back FP32 with
one entry per logical sequence); it lifts the fused constraints — any hardware, any head dimension,
and a differentiable recurrent path where the fused scan is inference-only — at the cost of a host
read of the offsets. Fused-only knobs stay fused-only: fastmath with the reference raises. The
naive_* functions leave the public interface and remain importable as test oracles from
attn_gym.linear.kda.naive.

Test Plan

gpu-run auto -- ~/.venvs/ag-linear/bin/python -m pytest test/test_kda_impl_dispatch.py \
  test/test_kda_recurrent.py test/test_kda.py test/test_kda_cute_forward.py \
  test/test_kda_ragged_public_backward.py test/test_kda_training_example.py -q
# 86 passed on B200. New coverage: enum/string coercion and rejection, reference-vs-fused
# agreement for both ops (dense and packed with empty slots and capacity tails), reference
# differentiability plus fused gradient rejection, non-128 head dims on the reference, and
# fastmath rejection. The one failure, test_chunk_kda_single_sequence_metadata_matches_dense_path,
# reproduces on pristine origin/main (16c2563) on this machine and is unrelated.

drisspg added a commit that referenced this pull request Aug 16, 2026
## Human Note

## Agent note

The two algorithmic forms stay separate functions because their gate contracts differ
(chunk-local cumulative vs per-token log2 decay), but naive-versus-fused is now an argument
instead of parallel `naive_*` names, following the shape of gdn's existing mode/backend split and
FLA's ops-layer layout. `Impl` is a `str` enum ("fused" | "reference") shared from
`attn_gym.linear.impl`; both ops accept the enum or its string value, and unknown selectors list
the valid set. There is deliberately no "auto" fallback: a training run should never silently land
on the eager reference.

The public wrappers move to torch-only `attn_gym.linear.kda.api`, which validates the shared
contract and imports the fused backends lazily inside the fused branch, so `chunk_kda` and
`recurrent_kda` are now eager exports and the PEP 562 lazy set shrinks to the standalone kernels.
`impl="reference"` runs the naive oracles in FP32 behind the identical packed contract (empty
padding slots pass state through, capacity tails stay out of contract, states come back FP32 with
one entry per logical sequence); it lifts the fused constraints — any hardware, any head dimension,
and a differentiable recurrent path where the fused scan is inference-only — at the cost of a host
read of the offsets. Fused-only knobs stay fused-only: `fastmath` with the reference raises. The
`naive_*` functions leave the public interface and remain importable as test oracles from
`attn_gym.linear.kda.naive`.

## Test Plan

```bash
gpu-run auto -- ~/.venvs/ag-linear/bin/python -m pytest test/test_kda_impl_dispatch.py \
  test/test_kda_recurrent.py test/test_kda.py test/test_kda_cute_forward.py \
  test/test_kda_ragged_public_backward.py test/test_kda_training_example.py -q
# 86 passed on B200. New coverage: enum/string coercion and rejection, reference-vs-fused
# agreement for both ops (dense and packed with empty slots and capacity tails), reference
# differentiability plus fused gradient rejection, non-128 head dims on the reference, and
# fastmath rejection. The one failure, test_chunk_kda_single_sequence_metadata_matches_dense_path,
# reproduces on pristine origin/main (16c2563) on this machine and is unrelated.
```

stack-info: PR: #318, branch: drisspg/stack/80
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Aug 16, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 816bac6633

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread attn_gym/linear/kda/api.py Outdated
"""
match resolve_impl(impl):
case Impl.FUSED:
from attn_gym.linear.kda.fwd.cute.chunk_kda_fwd import chunk_kda as fused_chunk_kda

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Register fused operators before strict graph capture

When a fresh process uses torch.compile(chunk_kda, fullgraph=True) before importing the fused backend, this branch imports chunk_kda_fwd and performs its torch.library registrations while Dynamo is already tracing the public function. That cold path can fail strict capture or depend on tracing-time side effects; the existing compile tests avoid it by importing the backend module first. Ensure registration occurs before graph capture while retaining lazy optional dependencies, and cover a cold public-operation compile.

AGENTS.md reference: AGENTS.md:L69-L73

Useful? React with 👍 / 👎.

Comment on lines 22 to 28
from attn_gym.linear.kda import (
active_token_mask,
chunk_kda,
mask_inactive_token_gradients,
mask_inactive_tokens,
naive_chunk_kda,
naive_chunk_kda_from_cumulative,
naive_recurrent_kda,
recurrent_kda,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update API docs when removing naive exports

Removing these eager exports leaves docs/linear.md lines 222–226 with three mkdocstrings directives for attn_gym.linear.naive_chunk_kda, naive_chunk_kda_from_cumulative, and naive_recurrent_kda, none of which now exists at that path. The generated linear API page therefore contains unresolved entries and still describes the superseded interface; update it to document the selector-based public functions or the new private oracle paths.

AGENTS.md reference: AGENTS.md:L57-L62

Useful? React with 👍 / 👎.

@drisspg
drisspg marked this pull request as draft August 16, 2026 21:39
@drisspg
drisspg changed the base branch from drisspg/stack/79 to main August 16, 2026 21:39
drisspg added a commit that referenced this pull request Aug 16, 2026
## Human Note

## Agent note

The two algorithmic forms stay separate functions because their gate contracts differ
(chunk-local cumulative vs per-token log2 decay), but naive-versus-fused is now an argument
instead of parallel `naive_*` names, following the shape of gdn's existing mode/backend split and
FLA's ops-layer layout. `Impl` is a `str` enum ("fused" | "reference") shared from
`attn_gym.linear.impl`; both ops accept the enum or its string value, and unknown selectors list
the valid set. There is deliberately no "auto" fallback: a training run should never silently land
on the eager reference.

The public wrappers move to torch-only `attn_gym.linear.kda.api`, which validates the shared
contract and imports the fused backends lazily inside the fused branch, so `chunk_kda` and
`recurrent_kda` are now eager exports and the PEP 562 lazy set shrinks to the standalone kernels.
`impl="reference"` runs the naive oracles in FP32 behind the identical packed contract (empty
padding slots pass state through, capacity tails stay out of contract, states come back FP32 with
one entry per logical sequence); it lifts the fused constraints — any hardware, any head dimension,
and a differentiable recurrent path where the fused scan is inference-only — at the cost of a host
read of the offsets. Fused-only knobs stay fused-only: `fastmath` with the reference raises. The
`naive_*` functions leave the public interface and remain importable as test oracles from
`attn_gym.linear.kda.naive`.

## Test Plan

```bash
gpu-run auto -- ~/.venvs/ag-linear/bin/python -m pytest test/test_kda_impl_dispatch.py \
  test/test_kda_recurrent.py test/test_kda.py test/test_kda_cute_forward.py \
  test/test_kda_ragged_public_backward.py test/test_kda_training_example.py -q
# 86 passed on B200. New coverage: enum/string coercion and rejection, reference-vs-fused
# agreement for both ops (dense and packed with empty slots and capacity tails), reference
# differentiability plus fused gradient rejection, non-128 head dims on the reference, and
# fastmath rejection. The one failure, test_chunk_kda_single_sequence_metadata_matches_dense_path,
# reproduces on pristine origin/main (16c2563) on this machine and is unrelated.
```

stack-info: PR: #318, branch: drisspg/stack/80
@drisspg
drisspg changed the base branch from main to drisspg/stack/79 August 16, 2026 21:39
@drisspg
drisspg marked this pull request as ready for review August 16, 2026 21:39

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a9a55411d4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread attn_gym/linear/kda/api.py Outdated
num_sequences = cu_seqlens.shape[0] - 1
output = torch.zeros_like(v)
final_state = (
torch.zeros(num_sequences, heads, key_dim, value_dim, device=q.device)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allocate packed reference states explicitly in FP32

When the caller has changed PyTorch's default dtype (for example with torch.set_default_dtype(torch.float64)) and omits initial_state, this allocation produces an FP64 final state even though the public contract promises FP32 states. Besides returning the wrong dtype, that state cannot be passed into the next segmented call because validate_kda_inputs rejects FP64 inputs; specify dtype=torch.float32 or derive the allocation from the already-normalized FP32 query.

Useful? React with 👍 / 👎.

Comment thread attn_gym/linear/kda/api.py Outdated
if initial_state is None
else initial_state.clone()
)
for sequence, (start, end) in enumerate(pairwise(cu_seqlens.cpu().tolist())):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep packed reference dispatch compatible with full-graph capture

When either public KDA operation is compiled with fullgraph=True, impl="reference", and packed cu_seqlens, converting the offsets through .cpu().tolist() extracts tensor data into Python and prevents Dynamo from capturing this implementation as one graph. The repository's custom-operator workflow requires strict compilation coverage for each materially different public implementation, so this packed dispatch needs compiler-visible tensor operations or an explicitly narrowed compile contract.

AGENTS.md reference: AGENTS.md:L69-L73

Useful? React with 👍 / 👎.

@drisspg
drisspg marked this pull request as draft August 16, 2026 23:06
@drisspg
drisspg changed the base branch from drisspg/stack/79 to main August 16, 2026 23:06
drisspg added a commit that referenced this pull request Aug 16, 2026
The two algorithmic forms stay separate functions because their gate contracts differ
(chunk-local cumulative vs per-token log2 decay), but naive-versus-fused is now an argument
instead of parallel `naive_*` names, following the shape of gdn's existing mode/backend split and
FLA's ops-layer layout. `Impl` is a `str` enum ("fused" | "reference") shared from
`attn_gym.linear.impl`; both ops accept the enum or its string value, and unknown selectors list
the valid set. There is deliberately no "auto" fallback: a training run should never silently land
on the eager reference.

The public wrappers move to torch-only `attn_gym.linear.kda.api`, which validates the shared
contract and imports the fused backends lazily inside the fused branch, so `chunk_kda` and
`recurrent_kda` are now eager exports and the PEP 562 lazy set shrinks to the standalone kernels.
`impl="reference"` runs the naive oracles in FP32 behind the identical packed contract (empty
padding slots pass state through, capacity tails stay out of contract, states come back FP32 with
one entry per logical sequence); it lifts the fused constraints — any hardware, any head dimension,
and a differentiable recurrent path where the fused scan is inference-only — at the cost of a host
read of the offsets. Fused-only knobs stay fused-only: `fastmath` with the reference raises. The
`naive_*` functions leave the public interface and remain importable as test oracles from
`attn_gym.linear.kda.naive`.

```bash
gpu-run auto -- ~/.venvs/ag-linear/bin/python -m pytest test/test_kda_impl_dispatch.py \
  test/test_kda_recurrent.py test/test_kda.py test/test_kda_cute_forward.py \
  test/test_kda_ragged_public_backward.py test/test_kda_training_example.py -q
```

stack-info: PR: #318, branch: drisspg/stack/80
@drisspg
drisspg changed the base branch from main to drisspg/stack/79 August 16, 2026 23:06
@drisspg
drisspg marked this pull request as ready for review August 16, 2026 23:07

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f8668b30da

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread attn_gym/linear/kda/api.py Outdated
if initial_state is None
else initial_state.clone()
)
for sequence, (start, end) in enumerate(pairwise(cu_seqlens.cpu().tolist())):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate packed offsets before slicing

When impl="reference" receives malformed offset values—such as a nonzero first offset, decreasing boundaries, or an endpoint beyond T—Python slicing silently clamps or overlaps spans and returns incorrect outputs/states. The fused path rejects these cases in chunk_scheduler.py via its begin >= 0, begin <= end, end <= tokens, and initial-zero assertions, so validate the host list against the same invariants before iterating to preserve the shared public contract.

Useful? React with 👍 / 👎.

@drisspg
drisspg marked this pull request as draft August 16, 2026 23:37
@drisspg
drisspg changed the base branch from drisspg/stack/79 to main August 16, 2026 23:37
drisspg added a commit that referenced this pull request Aug 16, 2026
The two algorithmic forms stay separate functions because their gate contracts differ
(chunk-local cumulative vs per-token log2 decay), but naive-versus-fused is now an argument
instead of parallel `naive_*` names, following the shape of gdn's existing mode/backend split and
FLA's ops-layer layout. `Impl` is a `str` enum ("fused" | "reference") shared from
`attn_gym.linear.impl`; both ops accept the enum or its string value, and unknown selectors list
the valid set. There is deliberately no "auto" fallback: a training run should never silently land
on the eager reference.

The public wrappers move to torch-only `attn_gym.linear.kda.api`, which validates the shared
contract and imports the fused backends lazily inside the fused branch, so `chunk_kda` and
`recurrent_kda` are now eager exports and the PEP 562 lazy set shrinks to the standalone kernels.
`impl="reference"` runs the naive oracles in FP32 behind the identical packed contract (empty
padding slots pass state through, capacity tails stay out of contract, states come back FP32 with
one entry per logical sequence); it lifts the fused constraints — any hardware, any head dimension,
and a differentiable recurrent path where the fused scan is inference-only — at the cost of a host
read of the offsets. Fused-only knobs stay fused-only: `fastmath` with the reference raises. The
`naive_*` functions leave the public interface and remain importable as test oracles from
`attn_gym.linear.kda.naive`.

```bash
gpu-run auto -- ~/.venvs/ag-linear/bin/python -m pytest test/test_kda_impl_dispatch.py \
  test/test_kda_recurrent.py test/test_kda.py test/test_kda_cute_forward.py \
  test/test_kda_ragged_public_backward.py test/test_kda_training_example.py -q
```

stack-info: PR: #318, branch: drisspg/stack/80
@drisspg
drisspg changed the base branch from main to drisspg/stack/79 August 16, 2026 23:37
@drisspg
drisspg marked this pull request as ready for review August 16, 2026 23:37

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d12db55d2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread attn_gym/linear/kda/api.py Outdated
applies only to the fused implementation.
"""
if resolve_impl(impl) is Impl.FUSED:
from attn_gym.linear.kda.fwd.cute.chunk_kda_fwd import chunk_kda as fused_chunk_kda

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the optional-backend installation error

When a base installation without the linear extra calls chunk_kda(..., impl="fused"), this direct import exposes a low-level ModuleNotFoundError for cutlass instead of the actionable pip install attn-gym[linear] error previously provided by attn_gym.linear.kda.__getattr__. Wrap the lazy backend import so the documented optional-dependency behavior is retained.

Useful? React with 👍 / 👎.

@drisspg
drisspg marked this pull request as draft August 16, 2026 23:48
@drisspg
drisspg changed the base branch from drisspg/stack/79 to main August 16, 2026 23:48
drisspg added a commit that referenced this pull request Aug 16, 2026
The two algorithmic forms stay separate functions because their gate contracts differ
(chunk-local cumulative vs per-token log2 decay), but naive-versus-fused is now an argument
instead of parallel `naive_*` names, following the shape of gdn's existing mode/backend split and
FLA's ops-layer layout. `Impl` is a `str` enum ("fused" | "reference") shared from
`attn_gym.linear.impl`; both ops accept the enum or its string value, and unknown selectors list
the valid set. There is deliberately no "auto" fallback: a training run should never silently land
on the eager reference.

The public wrappers move to torch-only `attn_gym.linear.kda.api`, which validates the shared
contract and imports the fused backends lazily inside the fused branch, so `chunk_kda` and
`recurrent_kda` are now eager exports and the PEP 562 lazy set shrinks to the standalone kernels.
`impl="reference"` runs the naive oracles in FP32 behind the identical packed contract (empty
padding slots pass state through, capacity tails stay out of contract, states come back FP32 with
one entry per logical sequence); it lifts the fused constraints — any hardware, any head dimension,
and a differentiable recurrent path where the fused scan is inference-only — at the cost of a host
read of the offsets. Fused-only knobs stay fused-only: `fastmath` with the reference raises. The
`naive_*` functions leave the public interface and remain importable as test oracles from
`attn_gym.linear.kda.naive`.

```bash
gpu-run auto -- ~/.venvs/ag-linear/bin/python -m pytest test/test_kda_impl_dispatch.py \
  test/test_kda_recurrent.py test/test_kda.py test/test_kda_cute_forward.py \
  test/test_kda_ragged_public_backward.py test/test_kda_training_example.py -q
```

stack-info: PR: #318, branch: drisspg/stack/80
@drisspg
drisspg marked this pull request as ready for review August 17, 2026 00:40
@drisspg
drisspg marked this pull request as draft August 17, 2026 01:06
@drisspg
drisspg changed the base branch from drisspg/stack/79 to main August 17, 2026 01:06
drisspg added a commit that referenced this pull request Aug 17, 2026
The two algorithmic forms stay separate functions because their gate contracts differ
(chunk-local cumulative vs per-token log2 decay), but naive-versus-fused is now an argument
instead of parallel `naive_*` names, following the shape of gdn's existing mode/backend split and
FLA's ops-layer layout. `Impl` is a `str` enum ("fused" | "reference") shared from
`attn_gym.linear.impl`; both ops accept the enum or its string value, and unknown selectors list
the valid set. There is deliberately no "auto" fallback: a training run should never silently land
on the eager reference.

The public wrappers move to torch-only `attn_gym.linear.kda.api`, which now owns the shared
contract for *both* implementations: `validation.py` runs once at the API boundary, the fused
modules keep only their own constraints behind private `forward` entry points (no second
public-looking `chunk_kda` in `fwd/cute`), and the fused backends import lazily inside the fused
branch, so `chunk_kda` and `recurrent_kda` are eager exports and the PEP 562 lazy set shrinks to
the standalone kernels. `docs/linear.md` documents the new surface (the removed `naive_*` anchors
would have failed `mkdocs build --strict`) including the serving limitations that are deliberate
for now: dense state slots, out-of-place final state, unfused decode-step launches, and no
spec-decode rollback. `naive_recurrent_kda`'s own `cu_seqlens` branch is removed: it had no
remaining callers and narrower packed semantics than the public contract, whose reference
statement now lives solely in `api._packed_reference` (with the documented offset checks enforced
at its existing host read).
`impl="reference"` runs the naive oracles in FP32 behind the identical packed contract (empty
padding slots pass state through, capacity tails stay out of contract, states come back FP32 with
one entry per logical sequence); it lifts the fused constraints — any hardware, any head dimension,
and a differentiable recurrent path where the fused scan is inference-only — at the cost of a host
read of the offsets. Fused-only knobs stay fused-only: `fastmath` with the reference raises. The
`naive_*` functions leave the public interface and remain importable as test oracles from
`attn_gym.linear.kda.naive`.

```bash
gpu-run auto -- ~/.venvs/ag-linear/bin/python -m pytest test/test_kda_impl_dispatch.py \
  test/test_kda_recurrent.py test/test_kda.py test/test_kda_cute_forward.py \
  test/test_kda_ragged_public_backward.py test/test_kda_training_example.py -q
```

stack-info: PR: #318, branch: drisspg/stack/80
@drisspg
drisspg changed the base branch from main to drisspg/stack/79 August 17, 2026 01:06
@drisspg
drisspg marked this pull request as ready for review August 17, 2026 01:06
@drisspg
drisspg marked this pull request as draft August 17, 2026 01:31
@drisspg
drisspg changed the base branch from drisspg/stack/79 to main August 17, 2026 01:31
drisspg added a commit that referenced this pull request Aug 17, 2026
The two algorithmic forms stay separate functions because their gate contracts differ
(chunk-local cumulative vs per-token log2 decay), but naive-versus-fused is now an argument
instead of parallel `naive_*` names, following the shape of gdn's existing mode/backend split and
FLA's ops-layer layout. `Impl` is a `str` enum ("fused" | "reference") shared from
`attn_gym.linear.impl`; both ops accept the enum or its string value, and unknown selectors list
the valid set. There is deliberately no "auto" fallback: a training run should never silently land
on the eager reference.

The public wrappers move to torch-only `attn_gym.linear.kda.api`, which now owns the shared
contract for *both* implementations: `validation.py` runs once at the API boundary, the fused
modules keep only their own constraints behind private `forward` entry points (no second
public-looking `chunk_kda` in `fwd/cute`), and the fused backends import lazily inside the fused
branch, so `chunk_kda` and `recurrent_kda` are eager exports and the PEP 562 lazy set shrinks to
the standalone kernels. `docs/linear.md` documents the new surface (the removed `naive_*` anchors
would have failed `mkdocs build --strict`) including the serving limitations that are deliberate
for now: dense state slots, out-of-place final state, unfused decode-step launches, and no
spec-decode rollback. `api._packed_reference` enforces the documented
offset contract at its existing host read.
`impl="reference"` runs the naive oracles in FP32 behind the identical packed contract (empty
padding slots pass state through, capacity tails stay out of contract, states come back FP32 with
one entry per logical sequence); it lifts the fused constraints — any hardware, any head dimension,
and a differentiable recurrent path where the fused scan is inference-only — at the cost of a host
read of the offsets. Fused-only knobs stay fused-only: `fastmath` with the reference raises. The
`naive_*` functions leave the public interface and remain importable as test oracles from
`attn_gym.linear.kda.naive`.

```bash
gpu-run auto -- ~/.venvs/ag-linear/bin/python -m pytest test/test_kda_impl_dispatch.py \
  test/test_kda_recurrent.py test/test_kda.py test/test_kda_cute_forward.py \
  test/test_kda_ragged_public_backward.py test/test_kda_training_example.py -q
```

stack-info: PR: #318, branch: drisspg/stack/80
@drisspg
drisspg changed the base branch from main to drisspg/stack/79 August 17, 2026 01:32
@drisspg
drisspg marked this pull request as ready for review August 17, 2026 01:32

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d481888ed7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread attn_gym/linear/kda/api.py Outdated
Comment on lines +98 to +100
q, k, v = (tensor.float() for tensor in (q, k, v))
gate = gate.float()
beta = beta.float()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Disable autocast around the FP32 reference path

When impl="reference" is called inside a CUDA autocast region, converting the operands with .float() does not force subsequent autocast-eligible operations to remain in FP32; the einsum and matrix multiplications in both naive implementations can still execute in BF16/FP16. This violates the newly documented FP32-oracle contract and can make reference training or fused-backend comparisons substantially less accurate under the common AMP context, so run the dense reference operation with autocast disabled.

Useful? React with 👍 / 👎.

@drisspg
drisspg marked this pull request as draft August 17, 2026 01:40
@drisspg
drisspg changed the base branch from drisspg/stack/79 to main August 17, 2026 01:40
drisspg added a commit that referenced this pull request Aug 17, 2026
The two algorithmic forms stay separate functions because their gate contracts differ
(chunk-local cumulative vs per-token log2 decay), but naive-versus-fused is now an argument
instead of parallel `naive_*` names, following the shape of gdn's existing mode/backend split and
FLA's ops-layer layout. `Impl` is a `str` enum ("fused" | "reference") shared from
`attn_gym.linear.impl`; both ops accept the enum or its string value, and unknown selectors list
the valid set. There is deliberately no "auto" fallback: a training run should never silently land
on the eager reference.

The public wrappers move to torch-only `attn_gym.linear.kda.api`, which now owns the shared
contract for *both* implementations: `validation.py` runs once at the API boundary, the fused
modules keep only their own constraints behind private `forward` entry points (no second
public-looking `chunk_kda` in `fwd/cute`), and the fused backends import lazily inside the fused
branch, so `chunk_kda` and `recurrent_kda` are eager exports and the PEP 562 lazy set shrinks to
the standalone kernels. `docs/linear.md` documents the new surface (the removed `naive_*` anchors
would have failed `mkdocs build --strict`) including the serving limitations that are deliberate
for now: dense state slots, out-of-place final state, unfused decode-step launches, and no
spec-decode rollback. `api._packed_reference` enforces the documented
offset contract at its existing host read.
`impl="reference"` runs the naive oracles in FP32 behind the identical packed contract (empty
padding slots pass state through, capacity tails stay out of contract, states come back FP32 with
one entry per logical sequence); it lifts the fused constraints — any hardware, any head dimension,
and a differentiable recurrent path where the fused scan is inference-only — at the cost of a host
read of the offsets. Fused-only knobs stay fused-only: `fastmath` with the reference raises. The
`naive_*` functions leave the public interface and remain importable as test oracles from
`attn_gym.linear.kda.naive`.

```bash
gpu-run auto -- ~/.venvs/ag-linear/bin/python -m pytest test/test_kda_impl_dispatch.py \
  test/test_kda_recurrent.py test/test_kda.py test/test_kda_cute_forward.py \
  test/test_kda_ragged_public_backward.py test/test_kda_training_example.py -q
```

stack-info: PR: #318, branch: drisspg/stack/80
@drisspg
drisspg changed the base branch from main to drisspg/stack/79 August 17, 2026 01:40
@drisspg
drisspg marked this pull request as ready for review August 17, 2026 01:40
@drisspg
drisspg marked this pull request as draft August 17, 2026 01:42
@drisspg
drisspg changed the base branch from drisspg/stack/79 to main August 17, 2026 01:42
drisspg added a commit that referenced this pull request Aug 17, 2026
The two algorithmic forms stay separate functions because their gate contracts differ
(chunk-local cumulative vs per-token log2 decay), but naive-versus-fused is now an argument
instead of parallel `naive_*` names, following the shape of gdn's existing mode/backend split and
FLA's ops-layer layout. `Impl` is a `str` enum ("fused" | "reference") shared from
`attn_gym.linear.impl`; both ops accept the enum or its string value, and unknown selectors list
the valid set. There is deliberately no "auto" fallback: a training run should never silently land
on the eager reference.

The public wrappers move to torch-only `attn_gym.linear.kda.api`, which now owns the shared
contract for *both* implementations: `validation.py` runs once at the API boundary, the fused
modules keep only their own constraints behind private `forward` entry points (no second
public-looking `chunk_kda` in `fwd/cute`), and the fused backends import lazily inside the fused
branch, so `chunk_kda` and `recurrent_kda` are eager exports and the PEP 562 lazy set shrinks to
the standalone kernels. `docs/linear.md` documents the new surface (the removed `naive_*` anchors
would have failed `mkdocs build --strict`) including the serving limitations that are deliberate
for now: dense state slots, out-of-place final state, unfused decode-step launches, and no
spec-decode rollback. `api._packed_reference` enforces the documented
offset contract at its existing host read.
`impl="reference"` runs the naive oracles in FP32 behind the identical packed contract (empty
padding slots pass state through, capacity tails stay out of contract, states come back FP32 with
one entry per logical sequence); it lifts the fused constraints — any hardware, any head dimension,
and a differentiable recurrent path where the fused scan is inference-only — at the cost of a host
read of the offsets. Fused-only knobs stay fused-only: `fastmath` with the reference raises. The
`naive_*` functions leave the public interface and remain importable as test oracles from
`attn_gym.linear.kda.naive`.

```bash
gpu-run auto -- ~/.venvs/ag-linear/bin/python -m pytest test/test_kda_impl_dispatch.py \
  test/test_kda_recurrent.py test/test_kda.py test/test_kda_cute_forward.py \
  test/test_kda_ragged_public_backward.py test/test_kda_training_example.py -q
```

stack-info: PR: #318, branch: drisspg/stack/80

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0ad98988b8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

metadata: RaggedChunkMetadata | None,
*,
output_final_state: bool,
tune: bool,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Forward the tune flag into intra-chunk recomputation

When chunk_kda(..., tune=False) executes the fused forward path, this flag reaches the recurrence and output-composition stages but not chunk_kda_fwd_intra; that function calls recompute_w_u_fwd without tune, so its autotuner still benchmarks and selects a configuration using the default True. Consequently the advertised fixed-configuration behavior can still vary across machines or cold caches. Pass tune through chunk_kda_fwd_intra to its recompute call.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5a96c3650e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +175 to +178
tuple(
_canonicalize(kernel.compile_call(candidate, *runtime_args))
for candidate in candidates
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include workload dimensions in autotune winner keys

When a process runs chunk_kda backward at multiple sequence lengths, ChunkKdaBwdWyDqkgTunable.compile_call produces the same values for every length because it includes only heads, dimensions, flags, and the candidate's grid waves. Hashing only those compile calls here therefore reuses the first length's persisted winner without benchmarking later workloads, even though the persistent-grid winner depends on the runtime token and chunk counts. This makes the default autotuning result shape-order-dependent and potentially substantially suboptimal; include performance-relevant runtime dimensions in the winner key rather than only compile-specialization arguments.

AGENTS.md reference: AGENTS.md:L74-L75

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6e1dc9593a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread attn_gym/_backends/cute/tune.py Outdated
Comment on lines +266 to +267
winner_key = _winner_key(kernel, candidates, runtime_args)
selected = _load_winner(winner_key)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Separate cached winners by benchmark policy

When callers use the documented benchmark= override, the winner key remains identical to one produced with the default benchmark or a different callback. A prior invocation can therefore cause this call to skip its requested timing policy entirely and reuse a configuration selected under different criteria, producing an incorrect tuning result. Include the benchmark policy in the cache identity, or bypass persistent winner reuse whenever a custom benchmark is supplied.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aee6e958a6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread attn_gym/_backends/cute/tune.py Outdated
)
except TypeError:
fast_key = None
selected = _WINNERS_FAST.get(fast_key) if fast_key is not None else None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard fast-cache lookup for unhashable compile calls

When a valid compile_call() tuple contains a pickleable but unhashable static value such as a list or dict, constructing fast_key succeeds because tuple construction does not hash its members, but _WINNERS_FAST.get(fast_key) then raises TypeError. This prevents run_tunable() from reaching the canonicalized persistent-key path even though jit_cache supports these static values; perform the lookup inside the try or canonicalize the fast key first.

AGENTS.md reference: AGENTS.md:L74-L75

Useful? React with 👍 / 👎.

Comment thread attn_gym/_backends/cute/tune.py Outdated
Comment on lines +205 to +206
winner = pickle.loads(path.read_bytes())
except (OSError, pickle.PickleError):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat truncated winner files as cache misses

When a persisted winner file is empty or truncated, pickle.loads() raises EOFError, which is not a pickle.PickleError and therefore escapes this handler. A damaged best-effort cache entry consequently blocks every matching autotuned call until the file is manually removed instead of re-tuning as intended; catch EOFError alongside the existing cache-read failures.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d9f81e8eda

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread attn_gym/linear/kda/api.py Outdated

import torch

from attn_gym.linear.kda.fwd.triton.recurrent import forward as _fused_recurrent_forward

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Lazily import the fused recurrent backend

On CPU-only or macOS PyTorch installations where triton is unavailable, importing attn_gym.linear now fails here before impl="reference" can be selected. This makes the newly advertised hardware-independent reference implementation unusable in exactly those environments; import the Triton recurrent backend only inside the fused dispatch branch, and keep the reference-facing public import path free of fused-only dependencies.

Useful? React with 👍 / 👎.

Comment on lines +184 to +187
tuple(
_canonicalize(kernel.compile_call(candidate, *runtime_args))
for candidate in candidates
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Key tuning winners by workload size

When the same process trains multiple sequence lengths, this key can reuse a winner selected for an unrelated workload because it contains only compile_call results. In particular, ChunkKdaBwdWyDqkgTunable.compile_call omits token/chunk capacity even though its grid_waves candidates control a runtime grid whose best choice depends on that capacity, so the first tuned shape permanently determines later shapes' schedule. Include performance-relevant runtime dimensions through a kernel-supplied tuning key rather than assuming compile-specialization arguments fully identify a tuning decision.

Useful? React with 👍 / 👎.

The two algorithmic forms stay separate functions because their gate contracts differ
(chunk-local cumulative vs per-token log2 decay), but naive-versus-fused is now an argument
instead of parallel `naive_*` names, following the shape of gdn's existing mode/backend split and
FLA's ops-layer layout. `Impl` is a `str` enum ("fused" | "reference") shared from
`attn_gym.linear.impl`; both ops accept the enum or its string value, and unknown selectors list
the valid set. There is deliberately no "auto" fallback: a training run should never silently land
on the eager reference.

The public wrappers move to torch-only `attn_gym.linear.kda.api`, which now owns the shared
contract for *both* implementations: `validation.py` runs once at the API boundary, the fused
modules keep only their own constraints behind private `forward` entry points (no second
public-looking `chunk_kda` in `fwd/cute`), and the fused backends import lazily inside the fused
branch, so `chunk_kda` and `recurrent_kda` are eager exports and the PEP 562 lazy set shrinks to
the standalone kernels. `docs/linear.md` documents the new surface (the removed `naive_*` anchors
would have failed `mkdocs build --strict`) including the serving limitations that are deliberate
for now: dense state slots, out-of-place final state, unfused decode-step launches, and no
spec-decode rollback. `api._packed_reference` enforces the documented
offset contract at its existing host read.
`impl="reference"` runs the naive oracles in FP32 behind the identical packed contract (empty
padding slots pass state through, capacity tails stay out of contract, states come back FP32 with
one entry per logical sequence); it lifts the fused constraints — any hardware, any head dimension,
and a differentiable recurrent path where the fused scan is inference-only — at the cost of a host
read of the offsets. Fused-only knobs stay fused-only: `fastmath` with the reference raises. The
`naive_*` functions leave the public interface and remain importable as test oracles from
`attn_gym.linear.kda.naive`.

A perf-focused review round (measured on B200, warm launch-bound B=1/T=64/H=1/D=128, CPU enqueue
medians per the checked-in probe) then removed ~190us/step from fwd+bwd and ~72us from forward:
profiler ranges now compile to nullcontext unless a torch profiler is active, compile-target
detection is memoized per device, the warm winner path is O(1) (one default-config compile call
keys the memo; candidates materialize only on a miss), and `autotune=False` now genuinely reaches
every stage (the forward recompute and the ragged dAv fallback previously ignored it). Residual
warm `autotune=True` overhead (~70us/step) is Triton's own per-call autotuner dispatch;
`autotune=False` avoids it entirely.

## Test Plan

```bash
gpu-run auto -- ~/.venvs/ag-linear/bin/python -m pytest test/test_kda_impl_dispatch.py \
  test/test_kda_recurrent.py test/test_kda.py test/test_kda_cute_forward.py \
  test/test_kda_ragged_public_backward.py test/test_kda_training_example.py -q
```

stack-info: PR: #318, branch: drisspg/stack/80
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant