Metadata-Version: 2.5
Name: openjoule
Version: 0.0.1
Summary: Plant-level inference and control engine for AI infrastructure. Estimates plant state under partial observation, forecasts outcomes under specified actions, imposes interventions, and measures results.
Project-URL: Homepage, https://github.com/joule-lat/OpenJoule
Project-URL: Repository, https://github.com/joule-lat/OpenJoule
Project-URL: Issues, https://github.com/joule-lat/OpenJoule/issues
Project-URL: Documentation, https://github.com/joule-lat/OpenJoule#readme
Project-URL: AID paper, https://abiaryan.com/assets/pre-print-aid.pdf
Author-email: Abi Aryan <dev@joule.com>
Maintainer-email: Abi Aryan <dev@joule.com>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: ai-infrastructure,control,inference,llm,mlsys,observability,power,serving,vllm
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: System :: Monitoring
Classifier: Typing :: Typed
Requires-Python: >=3.10
Provides-Extra: dev
Requires-Dist: build>=1.0.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: ruff>=0.4.0; extra == 'dev'
Requires-Dist: twine>=5.0.0; extra == 'dev'
Provides-Extra: power
Requires-Dist: nvidia-ml-py>=12.0.0; extra == 'power'
Description-Content-Type: text/markdown

<p align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="docs/assets/openjoule-logo-dark.jpg">
    <img alt="OpenJoule" src="docs/assets/openjoule-logo-light.jpg" width="68%">
  </picture>
</p>

<p align="center">
  <a href="https://pypi.org/project/openjoule/"><img alt="PyPI" src="https://img.shields.io/badge/pypi-0.0.1-3775A9?logo=pypi&logoColor=white"></a>
  <a href="https://pypi.org/project/openjoule/"><img alt="Python" src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-3776AB?logo=python&logoColor=white"></a>
  <a href="https://github.com/joule-lat/OpenJoule/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/badge/license-Apache%202.0-blue"></a>
</p>

# OpenJoule

OpenJoule is a plant-level inference and control engine for AI infrastructure.

OpenJoule watches one plant. It estimates the state from the telemetry it has. It forecasts what an action will do. It can impose that action. Then it measures what happened.

A serving system such as vLLM connects through a plant adapter. The adapter is separate from the core package. Telemetry comes in. Actions go out.

A plant is the system you are controlling. Serving, memory, workload, and power sit in one model.

[GitHub](https://github.com/joule-lat/OpenJoule) · [Issues](https://github.com/joule-lat/OpenJoule/issues) · [AID paper](https://abiaryan.com/assets/pre-print-aid.pdf)

## Install

You need Python 3.10 or newer.

```bash
pip install openjoule
```

You can add the NVIDIA power helper if you want NVML writes.

```bash
pip install "openjoule[power]"
```

## Quickstart

You pass in a few gauges and run one control step. You do not need a trace file.

```python
from openjoule import Engine, GenericMetricsAdapter

plant = GenericMetricsAdapter(stale_after=None)
plant.ingest({"gpu_util": 0.4, "watts": 280, "kv": 10}, time=1.0)

engine = Engine()
record = engine.control_step(plant, now=1.0)
print(record.imposed, record.reason)
print(engine.status(plant, now=1.0))
```

If `kv` is present, OpenJoule imposes a power cap. If it only sees utilization and watts, it holds.

You can also choose an action from one observation. You do not need a plant attached for that.

```python
from openjoule import Engine, Observation

engine = Engine()
obs = Observation(values={"gpu_util": 0.7, "watts": 320.0, "mean_qps": 12.0})
action = engine.loop(obs)
print(action)
```

The git repo includes a sample log at `traces/telemetry.jsonl`. That file is not part of the pip package. The sample has utilization and occupancy, and it has no `kv`, so the default step holds.

```python
from openjoule import Engine, ReplayPlant

plant = ReplayPlant("traces/telemetry.jsonl")
record = Engine().control_step(plant)
print(record.imposed, record.reason)
```

`engine.forecast` rolls the logged action forward. `engine.intervene` rolls an imposed action. The scripts in `experiments/` show the difference.

## What it does

| Step | What happens |
|------|------|
| **Estimate** | OpenJoule builds a plant state from the telemetry you have. |
| **Forecast** | OpenJoule predicts what a chosen action will do. |
| **Impose** | OpenJoule sends an action the plant can accept, such as a power cap. |
| **Measure** | OpenJoule records the outcome and the decision. |

These pieces move together. Memory changes how fast the plant can serve. That changes the queue. The queue changes power and heat. The action changes service again.

## Adapters

| Piece | What it is |
|------|------|
| `Plant` | This is the interface. It has `capabilities`, `read`, `write`, and `health`. |
| `ReplayPlant` | This reads a JSONL, Prometheus, or OTLP file. |
| `GenericMetricsAdapter` | This reads live gauges. It does not import a serving runtime. |
| `VLLMPlantAdapter` | This reads live gauges using vLLM names. It does not import vLLM. |
| `PowerCapWriter` | This writes a power cap through NVML or DCGM. You install `openjoule[power]` for NVML. |

The core package does not depend on vLLM or TensorRT-LLM.

## Safety defaults

`control_step` is the path that writes to a plant. By default it will not write in these cases.

- The reading is stale.
- The estimate is ambiguous. Utilization with no `kv` is one example.
- The plant cannot take that action.
- The last write was too recent.
- The write fails.

Each decision can stay in memory. You can also append it to a JSONL audit file.

## Project layout

```
openjoule/      The engine, the adapters, estimate, and control live here.
experiments/    These scripts ask which state a decision needs.
tests/          These are the tests.
traces/         These are sample readings. They are in the git repo, not the pip package.
```

## Status

This is version 0.0.1, and it is alpha. The API can still change. You can use it for experiments, for replay, and for a careful loop on one plant.

## Paper

OpenJoule follows AID, which stands for AI Infrastructure Dynamics. The paper asks which state you must see before a prediction under a new action is reliable.

[AID: A Framework for AI Infrastructure Dynamics](https://abiaryan.com/assets/pre-print-aid.pdf)

## Contributing

Issues and pull requests are welcome on [GitHub](https://github.com/joule-lat/OpenJoule).

```bash
git clone https://github.com/joule-lat/OpenJoule.git
cd OpenJoule
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
ruff check openjoule tests
pytest
```

Python in this repo has no comments and no docstrings. The Ruff line length is 100.

## License

OpenJoule is released under the Apache 2.0 license. You can read it in [LICENSE](LICENSE).

## Links

- The source is at https://github.com/joule-lat/OpenJoule
- Issues are at https://github.com/joule-lat/OpenJoule/issues
