Skip to content

AI-generated implementations for DaCe library nodes - #2557

Open
tbennun wants to merge 53 commits into
mainfrom
worktree-ai-library-nodes
Open

tbennun wants to merge 53 commits into
mainfrom
worktree-ai-library-nodes

Conversation

@tbennun

@tbennun tbennun commented Sep 7, 2026 •

Copy link
Copy Markdown
Collaborator

This PR provides two elements:

  1. A reserved 'ai' implementation, available on every library node. Expanding with it asks a language model to write the replacement tasklet, given the node's context in the SDFG and on the local machine.
  2. dace.libraries.ai.nodes.ai_node.AINode, a library node that carries only a natural-language description, for microkernels that have no library node of their own.
  3. ai.refine allows AI sessions to refine the implementation (e.g., if it crashes or not fast enough)

The model is reached through a provider selected by the ai.provider configuration entry. It supports the Anthropic Messages API, the OpenAI Responses API, and arbitrary AI models through a manual mode that lets you paste prompts and responses.

@tbennun
tbennun force-pushed the worktree-ai-library-nodes branch from 7e32b3a to 9943722 Compare September 7, 2026 07:49
Comment thread dace/libraries/ai/nodes/ai_node.py Outdated
Comment thread dace/libraries/ai/nodes/ai_node.py Outdated
Comment thread dace/libraries/ai/nodes/ai_tasklet.py
Comment thread dace/libraries/ai/providers/responses_provider.py Outdated
Comment thread dace/libraries/ai/providers/manual_provider.py
Comment thread pyproject.toml Outdated
ml = ["onnx", "torch", "onnxsim", "onnxscript", "onnxruntime", "protobuf", "ninja"]
mpi = ["mpi4py", "pytest-mpi"]
gpu = ["cupy-cuda13x", "cutensor-cu13"]
ai-anthropic = ["anthropic>=1.0"]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

For simplicity we can enforce installing both anthropic and open ai when ai is requested?

@tbennun tbennun Sep 9, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

sure, I wanted to not call the dependency ai though to not confuse users with DaCeML. Thoughts?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

llm, llm-expansions, agent, gen-ai are some that come to my mind, agent might make sense the most?

Comment thread dace/libraries/ai/backend.py Outdated
Comment thread dace/libraries/ai/context.py Outdated
Comment thread dace/libraries/ai/context.py
Comment thread dace/libraries/ai/context.py Outdated
:param sdfg: The SDFG containing the state.
:return: The complete expansion context.
"""
# Computed once and shared: it deep-copies the SDFG, and every part of the context that

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Same, do we really need the full SDFG? Isn't the concept of connector+tasklet is to have information needed for a tasklet locally and derive it just from the input output data?

@tbennun tbennun Sep 9, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The deep copy can be removed in principle. However, we will need to refactor some code to do a read-only/immutable connector type inference. It is the same code that sets the default schedule and storage types (infer_types.py) when a library node is expanded.

tbennun and others added 3 commits October 4, 2026 12:48
…odes

uv.lock: took main's lockfile and re-ran uv lock to add the ai-anthropic and
ai-openai extras.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Rename the "responses" provider to "openai" (OpenAIProvider). ai.api_key_envvar
  now defaults to empty, meaning each provider's conventional variable.
- Manual provider: replace fence-stripping regex with strip().
- Add a prompt/response exchange API: LibraryNode.generate_prompt(state) and
  read_prompt_response(state, response), plus SDFG-level
  dace.libraries.ai.generate_prompts / read_prompt_responses. They share the
  manual provider's files; a supplied response that does not compile is
  reported instead of sent to a provider for repair.
- Context: use dtypes.can_access instead of ad-hoc storage tuples, define the
  missing logger, and only deep-copy the SDFG for default inference when the
  node's slot still says Default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Warnings are reported through warnings.warn with the exception text in the
message; informational messages are printed only when debugprint is set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tbennun

tbennun commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator Author

cscs-ci run

@tbennun tbennun added this to the 2.0 milestone Oct 6, 2026
Remove the yapf pre-commit hook, .style.yapf and the yapf pin in the linting group.
tbennun and others added 29 commits October 16, 2026 09:48
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nstead of editing sys.path

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Environment names must be valid identifiers and are used verbatim, so that
"my-env", "my env" and "my_env" no longer map to one module. A different
definition under an existing name is no longer overwritten. Both cases are
sent back to the model as a repair round, like a compile failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The answer cache, generated environments, sessions and manual-provider
prompts now live in ai-cache, ai-environments, ai-sessions and ai-prompts
under build_folder_root() (.dacecache by default) instead of ~/.dace or the
system temporary directory. A hyphen cannot occur in an SDFG name, so these
cannot clash with a program's build folder. The four *_dir configuration
entries are removed.

A session id is now the node's path from the root SDFG (states, nested SDFG
nodes, node), percent-encoded so that different names never share a
session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Storage inference gives a nested SDFG's non-transient the storage of the
outer container, whatever it declares. A node expanded before compilation
read the declared storage, so a single element of GPU memory became a
by-value connector, and the host loaded it straight out of device memory
(a segfault). Connector types now use the resolved storage, and the
inference runs whenever a nested container's storage differs from the
outer one. infer_types.get_storage_from_parent is made public for this.

The documentation now says when to expand relative to transformations that
move data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts own

The check no longer assembles a translation unit from strings. The tasklet
is placed in a single-tasklet SDFG with the containers, memlets, symbols
and environments of the node it replaces (inside a one-iteration GPU_Device
map for device code), and built with sdfg.compile() in
<build folder root>/ai-check/<session id>, which later attempts on the same
slot reuse. The check therefore uses the real frame code, state struct,
CMake environment resolution and CUDA or HIP toolchain, and has no
hardcoded timeout. The model gets the compiler errors and the generated
sources of the check program.

The configuration entry ai.verify is renamed to ai.compile_check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Parallel builds interleave the compiler output of different files, which
breaks attributing diagnostics to tasklets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ubset helpers

- Connector types come from infer_types (infer_in_connector_type is
  extracted from infer_connector_types for this), run on the copy whose
  defaults are resolved when needed, instead of a hand-copied rule.
- Layout is read from the memlet's own container, which is what codegen
  offsets the connector pointer from, also for a chain of views.
- The index formula uses Range.absolute_strides and contiguity uses
  Range.is_contiguous_subset; both were wrong for stepped subsets.
- Symbols in scope come from SDFGState.symbols_defined_at, which also
  covers loop and interstate symbols.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- compiler.host_cpu() describes the CPU through py-cpuinfo when it is
  installed (covering macOS and Windows) and through /proc/cpuinfo
  otherwise; host_isa_id() reads the same fields as before.
- common.local_gpus() lists the GPUs of the selected backend, through
  pynvml (nvidia-ml-py) or amdsmi (ROCm) when they can be imported and
  through nvidia-smi or rocm_agent_enumerator otherwise. Only the selected
  vendor is queried, and none of these packages is a dependency.

dace.libraries.ai.sysinfo is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stalled

Environments without an is_installed check were listed as available on
the machine. They are now listed separately, as registered but possibly
missing, and the prompt asks to reuse an environment whenever one fits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…evel

getattr/hasattr give way to the attributes the node and descriptor
classes define, isinstance checks, and the environment registry. Nested
imports remain only for the optional SDKs and the provider modules, which
import the backend.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The offline isolation moves from the conftest into ai_test_utils.offline(),
which the __main__ blocks use as well, so a direct run cannot be served
cached stub answers. Live tests run with the defaults, so their answers
stay cached. The Anthropic and manual provider tests keep pytest.main,
since every test there needs a fixture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Docstrings and comments of the AI library and of the core code it touches
are reduced to what the code does. The manual provider gets the node name
from the expansion instead of parsing it out of the prompt, and an unknown
environment in use_environments is sent back to the model like other
environment errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ons and sample

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n created

An environment written by a failed attempt stayed registered, so the repair
of that attempt could not change it and lost a round to a name conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…age for the FFTW test

The FFTW test now skips unless a program using FFTW compiles and links, and
uses the default build folder, so that saved manual-provider answers and
the answer cache are found again. The GPU tests that need cupy are skipped
before they make a model request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The system prompt claimed that every thread of the block runs the body and
that threadIdx and blockIdx are free to use. DaCe maps each iteration of the
innermost map onto one thread and skips the body on threads past the
iteration space, so code that split its work across threadIdx computed only
thread 0's share.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A fresh expansion of a node whose session already had rounds salted the
cache key with the round number, so every rerun of a program made a new
request. Refinements never needed the salt, since their feedback already
changes the conversation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed GPU scalar test

A copy from pageable host memory can return before the data reaches the device, and DaCe copies
the result out on a stream of its own, so the test read zero about half of the time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… schedule in the prompt

- The state struct is emitted before code_global, so the system prompt now says that a state
  field's type must be built in or come from an environment's headers, and that a type defined in
  code_global is held through a `struct name *` pointer, allocated in code_init and deleted in
  code_exit. A test compiles and runs that pattern.
- The environment listing shows the headers each environment includes in the file of the
  generated code, and its state fields, which the system prompt told the model to consult.
- The capabilities no longer show the node's own schedule and the enclosing block size. A node
  in a kernel resolves to GPU_ThreadBlock, while its tasklet runs on one thread; the outline
  already shows the schedules of the enclosing scopes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The tile GEMM description of ai_cpu_test named a reduction bound N that the SDFG does not
  define; it now states the size.
- The iteration feedback asked for a helper with static linkage, which the system prompt
  advises against, because every tasklet's code_global shares one translation unit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ports

The instrumentation environments (LIKWID, PAPI) register when code is first generated, and the
FFT, MPI, PBLAS and sparse environments when their library is imported. The environment listing
in the prompt, and so the answer cache key, depended on whether a program had been compiled
before in the same process: a test run alone missed the answers cached by a run of its file.
The context module now imports them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ydantic

The AI library needs pydantic, which only the agent extras provide, so the
offline AI tests failed collection in CI. General and GPU CI now install the
agent extra, and the test directory is skipped when pydantic is missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Loading the check program and unloading it when the handle was collected broke the later kernels
of the process: after the check of a host cuBLAS call, the kernel launch of the next GPU program
hung in cudaLaunchKernel (5 of 5 runs of the two live GPU tests from the answer cache) or failed
with an invalid device kernel image. The check only needs the build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ResolvedDefaults.nodes field shadowed the nodes module in its own
annotation, which Python 3.10-3.13 evaluate eagerly, so importing
dace.libraries.ai failed there. Python 3.14 defers annotations and was
unaffected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tbennun
tbennun requested a review from ThrudPrimrose October 20, 2026 20:30

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants