Skip to content

[STF] locality domain for python - #10703

Draft
caugonnet wants to merge 706 commits into
NVIDIA:mainfrom
caugonnet:stf-locality-domain-python
Draft

[STF] locality domain for python#10703
caugonnet wants to merge 706 commits into
NVIDIA:mainfrom
caugonnet:stf-locality-domain-python

Conversation

@caugonnet

Copy link
Copy Markdown
Contributor

Description

Some PR to combine locality domain (#10658 ) and python support (#5315)

closes

Checklist

  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

caugonnet and others added 30 commits May 20, 2026 10:11
Clarify how the FHE smoke tests demonstrate explicit task dependencies and the decorator-based integration path without changing test behavior.
Keep stream-context construction compatible with active CUDA graph capture and turn the back-to-back stream-context repro into an asserting regression test.
Replace direct libcudart.so ctypes loads with cuda.bindings.runtime wrappers for graph DOT export, memcpy, and stream synchronization.
The python_experimental (STF) test job for py3.13 needs the base
cuda.cccl wheel for the same py_version as a runtime dependency, but
the pull_request matrix only built that wheel for py3.10 and py3.14
after NVIDIA#9 reduced the base python coverage. The result was that both
"Test cuda.stf (CTK12.9/CTK13.2)" jobs failed after ~4m trying to
download a non-existent wheel-cccl-linux-amd64-py3.13 artifact.

Add a single matrix entry that schedules the base python test (and
thereby produces the wheel) at py3.13 on Linux/gcc13, mirroring the
footprint of the python_experimental entry directly below it.
Fold the standalone cuda-cccl-experimental Python package back into cuda-cccl
and expose CUDASTF as cuda.stf._experimental, mirroring the existing
cuda.coop._experimental precedent. This removes the second wheel build and
CI matrix that had no real maintenance benefit.

Highlights:
- Move STF module files to python/cuda_cccl/cuda/stf/_experimental/ and add an
  empty cuda/stf/__init__.py namespace package (no re-exports).
- Simplify the STF bindings shim to match cuda.compute._bindings (drop the
  _BINDINGS_AVAILABLE fallback; raise ImportError on missing extension).
- Build cccl.c.experimental.stf and the _stf_bindings_impl Cython extension
  from python/cuda_cccl/CMakeLists.txt, installing them under
  cuda/stf/_experimental/cuXX[/cccl]. STF is gated off on Windows.
- Extend merge_cuda_wheels.py to merge both cuda/compute/cuXX and
  cuda/stf/_experimental/cuXX directories.
- Move STF tests/probes/benches to python/cuda_cccl/tests/stf/ and rewrite
  imports to cuda.stf._experimental.
- Rewire CI: drop build_py_experimental_wheel, point test_py_stf at
  build_py_wheel, rename the test script to ci/test_cuda_stf_python.sh, and
  collapse python_experimental to depend on the python project in
  ci/project_files_and_dependencies.yaml.
- Update docs/python/stf.rst, docs/python/index.rst, and AGENTS.md to the
  new import path and install instructions (pip install cuda-cccl[cu13]).
- Delete the python/cuda_cccl_experimental/ tree and associated CI scripts.

This is a non-backwards-compatible migration: users on
`pip install cuda-cccl-experimental[cu1X]` and `import cuda.stf as stf` must
move to `pip install cuda-cccl[cu1X]` and
`import cuda.stf._experimental as stf`.
The probe_*.py and bench_*.py files under python/cuda_cccl/tests/stf/ were
workshop fragments used to chase specific bugs and run one-off perf sweeps.
They use `def run(...)` rather than `test_*` functions, so pytest does not
collect them, and no maintained test or example imports them.

Delete the 22 probe_*.py files, the 4 bench_*.py files, and the
burger_scaling_smoke.{json,png} smoke artifacts. Also drop dead references
to those files from llm_helpers.py, test_llm_decode_loop.py, and
test_node_stf.py docstrings/comments.

The shared helpers (llm_helpers, pytorch_task, numba_helpers, numba_task,
numba_decorator) and the example_*.py files referenced from the docs are
kept untouched.
`branch_while_cuda_graph.dot` and `branch_while_cuda_graph.pdf` were
checked in by mistake alongside the example
`example_stackable_branch_while_warp.py`. The .dot was a debug rendering
of one specific run; the .pdf is the compiled output. Neither is
referenced by code, tests, or docs.
`python_experimental` was the matrix-project alias for the previous
standalone `cuda-cccl-experimental` wheel. Once STF moved under
`python/cuda_cccl/cuda/stf/_experimental/`, the alias had no source
files of its own and existed only to schedule `test_py_stf`.

This commit folds the STF tests directly into the `python` project:

* Removed the `python_experimental` block from
  `ci/project_files_and_dependencies.yaml` (fixes the
  `inspect_changes.py` SystemExit caused by an empty `include_regexes`).
* In `ci/matrix.yaml`, every `{project: 'python_experimental', jobs: ['test']}`
  row becomes `{project: 'python', jobs: ['test_py_stf']}`, the project
  alias under `projects:` is gone, and the MSVC exclusion is rewritten
  to target `jobs: ['test_py_stf']`.
* Regenerated `ci/test/inspect_changes/*.output` (all 10 fixtures pass).
  The one remaining diff from `origin/main` is `c2h_dependency.output`
  picking up `python` in LITE_BUILD, which is intentional now that
  `cccl_c_stf` is a lite dependency of the `python` project.
Drop exploratory Python STF demos that are not suitable as CCCL unit tests, while keeping focused API coverage and the ODE integration example.
Drop a local two-step Warp/STF experiment that is not suitable as CCCL unit-test coverage.
Splits python/cuda_cccl/tests/stf into three buckets and promotes the
Numba/PyTorch task adapters into a real package surface so they stop
living in tests/.

* New package: cuda.stf._experimental.interop.{numba, pytorch}, with
  lazy optional imports. Importing cuda.stf._experimental no longer
  pulls in Numba, PyTorch, or pytest.
* tests/stf/ root: pure STF unit/API tests. Renames drop the now-
  redundant "stackable" qualifier (graph_scope / launchable_graph /
  nested_scopes).
* tests/stf/interop/: STF<->external-runtime tests (Numba/PyTorch/Warp
  adapters, @jit decorator, cuda.compute integration, capture/legacy
  adoption). FDTD collapses to a single test_fdtd.py.
* tests/stf/examples/: runnable demos with test_/example_ prefixes
  dropped and a main() entry point. burger_reference.py kept as the
  one intentionally non-STF baseline. tests/test_examples.py extended
  to discover ("STF", "stf/examples").
* Drops example_tiled_edit_distance.py, test_burger_stackable_fast.py,
  and the two redundant FDTD variants.
caugonnet and others added 11 commits August 6, 2026 05:29
The device/green_ctx data places allocate via cudaMallocAsync, which draws
from the *current* device's default pool, so they must set the device first.
The locality-domain place instead calls cuMemAllocFromPoolAsync with an
explicit pool created with props.location.id == devid, so placement does not
depend on the current device.

No caller established a matching device either: neither STF allocate call
site (stream_ctx.cuh, adapters.cuh) nor the allocator layers above them set
one, and the enclosing exec_place_scope from task::acquire activates the
*exec* place, which may be a different device than the data place. The
switch was therefore both unnecessary and unrestored, leaking the current
device into caller code.

Removing it also makes allocate() symmetric with deallocate(), which never
switched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Release the retained primary context on every exit path in the fake-cache
  granularity probe: a throwing cuCtxGetDevResource leaked the reference.
- Override is_device() to true on the fallback exec place impl: the native
  backend reports true through exec_place_cuda_ctx_impl, and callers that
  branch on is_device() (e.g. the parallel_for reduction path) must see the
  same answer from both backends.
- Include <cstdio> (fprintf) and scope_guard.cuh (SCOPE) directly in
  locality_domain.cuh, and <cstdio>/<cstddef> in the tests that use
  fprintf/size_t, instead of relying on transitive includes.
- Strengthen the exec test: cudaSetDevice(dev) beforehand made the in-scope
  device check pass trivially for a no-op activate(); on the native backend
  also verify the current driver context actually changed under the scope.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Make the places.rst example handle the zero-domain case with a fallback
  branch instead of assert, and mark it schematic (it assumes the usual
  ctx/lX/kernel setup from the STF introduction).
- Clarify the CUDA version qualifier in stf.rst: native support is 13.4+,
  and the same API compiles on older toolkits with the whole-device
  fallback reporting a single domain.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…instead

The rationale of the API is that a device without locality-domain support
reports a single domain covering the whole device, so callers keep one code
path and never need a zero-count special case.

- The public count validates the device ordinal with EXPECT (like
  data_place::device) and otherwise always returns >= 1.
- On a native (13.4+) build whose driver cannot answer the locality-domain
  query, the count degrades to 1 and places actually work: the context cache
  builds one green context spanning the full SM resource instead of throwing,
  and the memory pool / mem_create paths fall back to plain device memory
  (the localized location type would be rejected by such a driver) -- i.e.
  exactly the documented pre-13.4 fallback semantics, selected at runtime.
- The fallback backend count returns 1 unconditionally (device validation
  moved to the public function).
- Tests: zero-count waivers become EXPECT(count >= 1); invalid-device checks
  now expect an exception instead of 0.
- Docs: drop the zero-count language and the zero-guard from the example.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Stacks on stf_c_api (NVIDIA#5315) + stf-locality-domain-places (NVIDIA#10658): the
placement work shines with uGPU locality domains, so expose them where the
allocation surface lives.

C API: stf_locality_domain_count (never 0 for a valid device -- the
whole-device degrade contract; 0 = invalid device, detail on stderr),
stf_exec_place_locality_domain, stf_exec_place_locality_domain_grid
(make_locality_domain_grid), stf_data_place_locality_domain.

Python: stf.locality_domain_count(dev), exec_place.locality_domain(dev, d),
data_place.locality_domain(dev, d), exec_place_grid.locality_domains(dev).

Tests adapt to the reported count (single whole-device domain on fallback
backends): count contract, place identity, per-domain allocation
roundtrip, a domain-grid task reading at the replicated place (the uGPU
counterpart of the replicated grid test), and per-domain pinned tasks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…dings

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@copy-pr-bot

copy-pr-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@cccl-authenticator-app cccl-authenticator-app Bot moved this from Todo to In Progress in CCCL Aug 7, 2026
@caugonnet caugonnet changed the title Stf locality domain python [STF] locality domain for python Aug 7, 2026
@caugonnet caugonnet self-assigned this Aug 7, 2026
@caugonnet caugonnet added stf Sequential Task Flow programming model places labels Aug 7, 2026
caugonnet and others added 2 commits August 7, 2026 08:58
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The missing entry point for "give me a grid for this machine":
granularity="device" (one place per CUDA device, like from_devices over
all of them) or granularity="locality_domain" (one place per locality
domain of every device, device-major). A device without native support
contributes its single whole-device domain, so the domain granularity
degrades to the device one exactly where domains are unavailable -- one
code path for callers, per the never-0 count contract.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
caugonnet added a commit to caugonnet/cccl that referenced this pull request Aug 7, 2026
Parametrize the example suite's grid over two granularities: the repeat
device grid, and the machine's locality domains (via
exec_place_grid.machine(granularity="locality_domain") when the PR NVIDIA#10703
bindings are present, locality_domains(0) as fallback, clean skip
otherwise). The whole programming-model spectrum -- compiled pointwise,
unsplit-dim softmax, partials+fold, misalignment, CUDA-graph capture,
TinyMLP -- now exercises the substrate the placement work is for. The
partials assert becomes grid-size-agnostic (domain counts vary by
machine).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
caugonnet and others added 5 commits August 7, 2026 09:17
A create()-built grid has no affine data place; a dependency without an
explicit place then throws C++-side and TERMINATES through the void C API
instead of raising -- the known 5315-family error-channel gap, hit here on
GB300. Use the proven replicated-read pattern and document the constraint
in the test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Design decision (Cedric): blocked along dimension 0 is the natural
strategy for a machine-level grid, so machine() attaches it as the grid's
affine data place (native stf_partition_fn_blocked, no callback
trampoline). Bare dependencies -- lX.rw() with no explicit data place --
now resolve on machine grids instead of terminating through the void C API
for lack of an affine; the test exercises exactly the case that aborted on
GB300, alongside an explicit replicated read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ntyped access)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…er test

Found on GB300 while validating machine()'s default affine: a replicated
read whose valid source instance lives at a composite place terminates in
the device exec-place deactivate with "invalid device ordinal" -- the
broadcast-copy path derives a restore ordinal from a composite affine.
machine() merely makes the sequence (composite write, then replicated
read) easy to reach; the interplay predates it. The reproducer is recorded
in the test comment; the test sources the replicated read from the host
instance until the C++ path is fixed. Bare rw at the default blocked
affine works and stays covered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GB300 bisection: the aborts were NOT a replicated-broadcast issue (that
sequence passes) but affine mutation on a SHARED place. make_grid
degenerates a size-1 grid to the place itself; for the device granularity
that is the process-shared exec_place::device(0), so attaching the blocked
composite affine mutated the shared scalar place -- and every later
deactivate derived its restore ordinal from a composite affine
(cudaSetDevice(-5), invalid device ordinal). Attach the default affine
only when the machine grid has more than one place; a scalar place's own
device affine already resolves bare dependencies. Corrects the earlier
misdiagnosis recorded in the test comment; the write-then-replicated-read
sequence is restored in the test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@caugonnet caugonnet mentioned this pull request Aug 7, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

places stf Sequential Task Flow programming model

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

3 participants