Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 70 additions & 2 deletions docs/source/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ Signature
rtol: float = 1e-3,
atol: float = 1e-5,
strict: bool = True,
graph_configs: GraphConfigs | None = None,
)

Parameters
Expand All @@ -147,6 +148,8 @@ Parameters
``torch.allclose()``. Defaults to ``1e-5``.
* ``strict`` (bool, optional): Raise on validation failure. Defaults
to ``True``.
* ``graph_configs`` (GraphConfigs, optional): Graph-level metadata to serialize
under the generated YAML ``pipeline.configs`` entry.

Behavior
~~~~~~~~
Expand All @@ -161,6 +164,42 @@ The method performs the complete pipeline:
#. Generate ``{name}.png`` if ``visualize=True``.
#. Log graph statistics.

``GraphConfigs``
----------------

Graph-level metadata passed to :func:`~leapp.compile_graph`.

.. code-block:: python

from leapp import GraphConfigs

leapp.compile_graph(
graph_configs=GraphConfigs(
frequency_hz=50,
extra={"runtime": "isaac_lab"},
),
)

This emits fields under the generated ``pipeline.configs`` entry:

.. code-block:: yaml

pipeline:
data_flow: {}
feedback_flow: {}
inputs: {}
outputs: {}
configs:
frequency: 50
runtime: isaac_lab

Parameters
~~~~~~~~~~

* ``frequency`` (float | int, optional): Graph-level execution frequency in Hz.
* ``extra`` (dict, optional): Additional graph-level fields. Keys are flattened
into the ``pipeline.configs`` entry rather than serialized under an ``extra`` key.

Generated artifacts
~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -204,7 +243,7 @@ Output YAML structure
python version: "3.12.9"
torch version: "2.7.0+cu126"
leapp version: "0.5.2"
leapp config version: "1.1"
leapp config version: "1.2"
cuda version: "12.6"
os: Linux

Expand All @@ -222,6 +261,34 @@ The method logs statistics including:
Semantic metadata
=================

``TemporalAxis``
--------------------

Mark one ``element_names`` axis as temporal and attach an absolute period in
milliseconds to the tensor YAML entry.

.. code-block:: python

from leapp import TensorSemantics, TemporalAxis

TensorSemantics(
name="actions",
ref=actions,
element_names=[TemporalAxis(period_ms=100), ["hip", "knee", "ankle"]],
)

This emits the reserved bare axis sentinel and the period metadata:

.. code-block:: yaml

element_names:
- __temporal_axis__
- [hip, knee, ankle]
temporal_period_ms: 100

``__temporal_axis__`` is reserved for LEAPP output; use
``TemporalAxis`` rather than writing the sentinel directly.

``TensorSemantics``
-------------------

Expand Down Expand Up @@ -252,7 +319,8 @@ Parameters
the tensor. Enum values are serialized to their string ``.value``.
* ``element_names`` (list | str, optional): Human-readable element names. A
string becomes ``[[name]]``; a flat list of strings becomes ``[[...]]``; a
per-dimension list is preserved with optional ``None`` entries.
per-dimension list is preserved with optional ``None`` entries. Include
``TemporalAxis(...)`` as a bare axis item to mark that axis temporal.
* ``extra`` (dict, optional): Additional semantic fields. Keys are flattened
into the tensor YAML entry rather than serialized under an ``extra`` key.

Expand Down
97 changes: 92 additions & 5 deletions docs/source/guides/semantics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,55 @@ automatically:
TensorSemantics("gravity", tensor, element_names="z")


``TemporalAxis``
--------------------

Use ``TemporalAxis`` inside ``element_names`` to mark one tensor axis as
temporal and record the period between samples on that axis. This is useful for
chunked outputs such as an action tensor shaped ``[num_chunks, num_joints]``.

Place ``TemporalAxis`` directly in the outer ``element_names`` list. Do not
wrap it in a list. LEAPP serializes the temporal axis as the reserved
``__temporal_axis__`` sentinel and emits ``temporal_period_ms`` as a sibling
field on the tensor entry.

.. code-block:: python

from leapp import TemporalAxis

TensorSemantics(
"actions",
actions,
kind=OutputKindEnum.JOINT_TORQUES,
element_names=[
TemporalAxis(period_ms=100),
["hip", "knee", "ankle"],
],
)

.. code-block:: yaml

- name: actions
dtype: float32
shape: [4, 3]
type: tensor
kind: target/joint/torques
element_names:
- __temporal_axis__
- [hip, knee, ankle]
temporal_period_ms: 100

Downstream consumers can find the temporal axis by locating
``__temporal_axis__`` in ``element_names``. LEAPP does not validate
``temporal_period_ms`` against ``GraphConfigs.frequency``.

.. warning::

``__temporal_axis__`` is reserved for LEAPP output. Use
``TemporalAxis`` in Python annotations rather than writing the
sentinel directly. A tensor may contain at most one temporal axis marker.


``extra``
---------

Expand Down Expand Up @@ -401,10 +450,48 @@ Limitations
within the same node's inputs (or outputs). Duplicate names raise an
error.
#. **Semantic fields are optional** --- all semantic fields (``kind``,
``element_names``, and ``extra``) are optional. A ``TensorSemantics``
with no semantic fields behaves identically to passing a raw tensor with
the same name.
``element_names``, temporal metadata from ``TemporalAxis``, and
``extra``) are optional. A ``TensorSemantics`` with no semantic fields
behaves identically to passing a raw tensor with the same name.
#. **Extra fields are flattened** --- keys in ``extra`` become top-level YAML
fields on the tensor entry. Avoid keys that collide with built-in fields
such as ``name``, ``dtype``, ``shape``, ``type``, ``kind``, or
``element_names``.
such as ``name``, ``dtype``, ``shape``, ``type``, ``kind``,
``element_names``, ``temporal_period_ms``, or ``__temporal_axis__``.

Graph-level semantics
=====================

Use ``GraphConfigs`` for metadata that applies to the whole exported LEAPP
bundle rather than to a specific tensor. ``GraphConfigs`` is passed to
:func:`~leapp.compile_graph`, after tracing has stopped.

.. code-block:: python

from leapp import GraphConfigs

leapp.compile_graph(
graph_configs=GraphConfigs(
frequency=50,
extra={"skip-first-run": True},
),
)

``frequency`` describes the graph-level execution frequency in Hz. ``extra``
works like ``TensorSemantics.extra`` within ``configs``: keys are flattened into
the generated ``pipeline.configs`` entry rather than nested under an ``extra``
key.

.. code-block:: yaml

pipeline:
data_flow: {}
feedback_flow: {}
inputs: {}
outputs: {}
configs:
frequency: 50
skip-first-run: true

Graph-level semantics are independent of tensor-level temporal metadata. LEAPP
does not validate ``TemporalAxis`` values against ``GraphConfigs.frequency``.

1 change: 1 addition & 0 deletions docs/source/spelling_wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ TracedTensor
TracedTensors
TracedTensorNode
TensorSemantics
TemporalAxis
InferenceManager
InputKindEnum
OutputKindEnum
Expand Down
6 changes: 4 additions & 2 deletions leapp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@
from .inference_manager import InferenceManager
from .leapp import annotate, start, stop, compile_graph
from .utils.enums import InputKindEnum, OutputKindEnum
from .utils.tensor_description import TensorSemantics
from .utils.tensor_description import GraphConfigs, TensorSemantics, TemporalAxis

__version__ = "0.5.2"
__config_version__ = "1.1"
__config_version__ = "1.2"
__author__ = "Frank Lai"
__email__ = "frlai@nvidia.com"

Expand All @@ -46,4 +46,6 @@
"compile_graph",
"__version__",
"TensorSemantics",
"TemporalAxis",
"GraphConfigs",
]
13 changes: 11 additions & 2 deletions leapp/leapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@
from .utils.tracing_lock import TracingLock
from .utils.utils import get_system_info
from .export_manager import ExportManager
from .utils.tensor_description import GraphConfigs


_MANAGER = ExportManager()


def start(name, save_path=".", verbose=False, dry_run=False, non_traced=None, max_cached_io=5, global_patching=True):
def start(name, save_path=".", verbose=False, dry_run=False, non_traced=None,
max_cached_io=5, global_patching=True):
"""Initialize and start LEAPP graph interpretation.

``name`` may be a bare graph name (``"my_graph"``), a relative path
Expand Down Expand Up @@ -104,7 +106,8 @@ def stop():
manager.set_patches_applied(False)


def compile_graph(visualize=True, verbose=None, validate=True, dry_run=False, rtol=1e-3, atol=1e-5, strict=True):
def compile_graph(visualize=True, verbose=None, validate=True, dry_run=False,
rtol=1e-3, atol=1e-5, strict=True, graph_configs=None):
"""Compile and save the computational graph from traced nodes."""
manager = _MANAGER

Expand All @@ -126,6 +129,12 @@ def compile_graph(visualize=True, verbose=None, validate=True, dry_run=False, rt
graph = LeappGraph(manager.get_nodes(), manager.get_graph_name())
pipeline = graph.get_full_pipeline_description()

if graph_configs is not None:
if not isinstance(graph_configs, GraphConfigs):
raise TypeError(
"compile_graph(graph_configs=...) expects a GraphConfigs instance")
pipeline["pipeline"]["configs"] = graph_configs.to_dict()

initial_value_filename = None
if not manager.is_dry_run():
initial_value_filename = graph.save_feedback_initial_values(
Expand Down
Loading
Loading