Source code for track_gen.exec.errors
"""Errors raised by the execution layer (:mod:`track_gen.exec`).
All of them derive from :class:`CaptureError`, itself a ``RuntimeError`` (Warp
reports capture failures as ``RuntimeError`` too), so callers can catch the
whole family at once. :class:`RebindAfterCaptureWarning` is a warning.
"""
from __future__ import annotations
__all__ = ["CaptureError", "NotCapturableError", "LegacyPluginStreamError",
"RebindAfterCaptureWarning"]
[docs]
class RebindAfterCaptureWarning(UserWarning):
"""A bound input array was replaced after a hot method that reads it was recorded
into a capture.
The recorded graph keeps reading the arrays that were bound when it was recorded, so
after a rebind its replays silently read the old buffers. Emitted (once per bind
call) by ``Course.bind``, ``Component.bind`` and the utilities' ``bind`` /
``bind_inputs`` / ``bind_window`` when a pointer changes after their ``query()`` /
``update()`` ran while its stream (or a CPU graph capture) was capturing; rebinding
the same arrays is silent. The documented recipe "rebind, then record again" also
warns once: filter this category when you re-record on purpose::
import warnings
import track_gen.exec
warnings.simplefilter("ignore", track_gen.exec.RebindAfterCaptureWarning)
"""
[docs]
class CaptureError(RuntimeError):
"""A call was made in a capture state it does not support.
For example, constructing a generator (which runs one real warm-up pass)
while the resolved stream is being captured into a CUDA graph.
"""
[docs]
class NotCapturableError(CaptureError):
"""A non-capturable producer was asked to record into a caller's capture (a CUDA
graph capture, or a CPU graph capture: ``wp.ScopedCapture("cpu")``).
Raised BEFORE any device work is issued, so the caller's capture is left
untouched. Outside a capture the same producer runs: its HOST stages eagerly, the
rest of a pipeline from its own graphs (segmented execution, see
:class:`~track_gen.exec.GraphRunner`).
Attributes:
name: The producer's name (e.g. the generator name).
stage_ids: The ids of the HOST stages that make it non-capturable (``()``
when unknown, e.g. a bare ``GraphRunner(capturable=False)``).
"""
[docs]
def __init__(self, name: str = "", detail: str = "", *,
stage_ids=()) -> None:
self.name = name
self.stage_ids = tuple(str(s) for s in stage_ids)
what = repr(name) if name else "this producer"
if self.stage_ids:
msg = (
f"{what} cannot be recorded into a graph capture: HOST stage(s) "
f"{list(self.stage_ids)} read device data back or run host control "
"flow, so they run eagerly (outside a capture the rest still replays "
"from graphs). Call it outside the capture, or use STATIC / BOUNDED "
"stages (PipelineSpec(require_caller_capturable=True) rejects HOST "
"stages at build)."
)
else:
msg = (
f"{what} cannot be recorded into a graph capture: its generator is "
"registered with capturable=False (it may host-synchronize, read back "
"or allocate per call). Call it outside the capture (it then runs "
"eagerly) or use a capturable generator."
)
if detail:
msg = f"{msg} {detail}"
super().__init__(msg)
[docs]
class LegacyPluginStreamError(CaptureError):
"""A legacy-ABI plugin would launch on the wrong stream under capture.
Legacy generator plugins (registered without ``accepts_ctx=True``) issue
their own ``wp.launch`` calls on Warp's CURRENT stream. Recording one into
a caller's capture is therefore only correct when the capture stream IS
Warp's current stream.
"""
[docs]
def __init__(self, name: str = "", detail: str = "") -> None:
self.name = name
what = repr(name) if name else "this producer"
msg = (
f"{what} uses a legacy plugin (no accepts_ctx=True) that launches on "
"Warp's current stream, but the call resolved a different, capturing "
"stream. Make the capture stream Warp's current stream for the "
"duration of the capture: capture Warp's current stream (a plain "
"`wp.ScopedCapture(device)`), or, for torch.cuda.graph, pass it an "
"explicit capture stream s and make that stream current first, through "
"ONE wrapper you keep for the stream's lifetime: `ws = "
"wp.stream_from_torch(s)` once, then `with wp.ScopedStream(ws): with "
"torch.cuda.graph(g, stream=s): ...` (a throwaway wrapper unregisters "
"the stream when it is collected; a plain `torch.cuda.graph(g)` "
"captures on torch's internal stream, which cannot be made current "
"beforehand). Porting the plugin to accept ctx= removes the "
"restriction."
)
if detail:
msg = f"{msg} {detail}"
super().__init__(msg)