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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,5 @@ __marimo__/
# aim
data/
datatest/

tmp/
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,26 @@ uv add aimx
pip install aimx
```

### Install the agent skill
### Install the agent skills

This repository also includes an `aimx` skill for agent workflows such as
`autoresearch` `log_experiment`, where an LLM needs to collect run parameters,
metric summaries, traces, and image evidence from a local Aim repository.
This repository also includes agent skills for Aimx AutoResearch workflows.
The existing `aimx` skill is the Observe subsystem for `log_experiment`, where
an LLM collects run parameters, metric summaries, traces, and image evidence
from a local Aim repository. The build skills help create or audit repositories
that are ready for that loop.

```bash
npx skills install blizhan/aimx
```

After installation, invoke the skill as `$aimx`. The skill assumes the `aimx`
CLI is available in the environment that performs the experiment inspection.
After installation, invoke:

- `$aimx` for read-only experiment evidence collection.
- `$aimx-hydra-lightning-builder` for Hydra + Lightning + Aim scaffold and
migration-audit workflows.

The skills assume the `aimx` CLI is available in the environment that performs
experiment inspection.

### Check your environment

Expand Down
75 changes: 75 additions & 0 deletions skills/aimx-hydra-lightning-builder/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
name: aimx-hydra-lightning-builder
description: Use when creating, auditing, or planning migration for Hydra + PyTorch Lightning ML repositories that should log to Aim, expose Aimx-readable experiment evidence, or use configurable datamodules/models/plmodules/callbacks/loggers.
---

# Aimx Hydra Lightning Builder

## Overview

Build or migrate Hydra + Lightning repositories that satisfy the Aimx AutoResearch contract.

The pattern is distilled from production Hydra + Lightning research repositories: Hydra composes the experiment, Lightning owns the training lifecycle, and Aim/Aimx preserve evidence for agentic iteration.

## Workflows

### Scaffold a new repository

Run:

```bash
python skills/aimx-hydra-lightning-builder/scripts/scaffold_repo.py \
--stack hydra-lightning \
--name <repo-name> \
--package <package_name> \
--preset classification \
--output <target-dir>
```

Then validate inside the generated repository:

```bash
uv sync
uv run pytest
uv run python src/train.py trainer.fast_dev_run=true trainer.logger=false
```

### Audit an existing repository

Run read-only:

```bash
python skills/aimx-hydra-lightning-builder/scripts/audit_repo.py \
--stack hydra-lightning \
--repo <repo-path> \
--format json > audit.json
python skills/aimx-hydra-lightning-builder/scripts/plan_migration.py \
--audit audit.json \
--target-stack hydra-lightning
```

Never edit, format, sync dependencies, generate files, or run mutation/codegen commands inside audit targets unless the user separately approves migration execution.

## Architecture Pattern

Read `references/architecture.md` before scaffold or migration work.

- `configs/<task>.yaml` composes `data`, `datamodule`, `model`, `plmodule`, `trainer`, `callbacks`, `logger`, `paths`, `accelerate`, and optional `experiment`.
- `src/train.py` seeds, instantiates configured objects, logs hyperparameters, and calls `trainer.fit/validate/test`.
- `BaseLitModule` owns `cfg`, `cfg.model` instantiation, optimizer/scheduler, compile/SDPA options, and shared trace helpers.
- Task modules own batch parsing, loss, metrics, and prediction/evaluation outputs.
- DataModules own splits, dataloaders, sampler/collate policy, and data preparation boundaries.
- Aim trace uses Lightning loggers for scalars and explicit `experiment.track(...)` for images/distributions.

## References

- `references/architecture.md`: core relationships and file layout.
- `references/aim-trace.md`: evidence naming, context, and Aimx query conventions.
- `references/migration-audit.md`: read-only audit checklist and migration staging.

## Scripts And Assets

- `scripts/scaffold_repo.py`: copies `assets/template-repo`.
- `scripts/audit_repo.py`: scans a repository without writing to it.
- `scripts/plan_migration.py`: generates a staged migration plan from audit JSON.
- `assets/template-repo`: minimal runnable Hydra + Lightning + Aim template.
4 changes: 4 additions & 0 deletions skills/aimx-hydra-lightning-builder/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Aimx Hydra Lightning Builder"
short_description: "Build Hydra Lightning Aim repositories"
default_prompt: "Use $aimx-hydra-lightning-builder to scaffold or audit a Hydra Lightning repository for Aimx AutoResearch."
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.venv/
__pycache__/
.pytest_cache/
.ruff_cache/
outputs/
multirun/
.aim/
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root marker for rootutils
27 changes: 27 additions & 0 deletions skills/aimx-hydra-lightning-builder/assets/template-repo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# {{ project_name }}

Hydra + Lightning + Aim template for Aimx AutoResearch.

## Quick Start

```bash
uv sync
uv run python src/train.py trainer.fast_dev_run=true trainer.logger=false
uv run pytest
```

Enable Aim logging by leaving `trainer.logger=true` and using `logger=aim`.

```bash
uv run python src/train.py
aimx query params "run.hash != ''" --repo .
aimx query metrics "metric.name != ''" --repo .
aimx query metrics "metric.name == 'acc'" --repo . --json
```

System parameters are not logged by default to avoid storing environment
variables in experiment evidence. Opt in only for safe environments:

```bash
uv run python src/train.py logger.aim.log_system_params=true
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
compile: false
precision: "32-true"
fp32_matmul_precision: "highest"
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
stack: "{{ stack }}"
train_command: "uv run python src/train.py"
fast_dev_command: "uv run python src/train.py trainer.fast_dev_run=true trainer.logger=false"
config_entrypoint: "configs/train.yaml"
aim_repo: ${paths.root_dir}
experiment_name: "{{ project_name }}"
objective:
metric: "acc"
direction: "maximize"
context:
subset: "val"
evidence:
params: true
metrics: true
traces: true
images: false
distributions: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
autoresearch_marker:
_target_: {{ package_name }}.callbacks.autoresearch.AutoResearchMarker
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
_target_: {{ package_name }}.datamodules.dummy.RandomClassificationDataModule
num_samples: 64
num_features: 8
num_classes: 2
batch_size: 16
num_workers: 0
seed: ${seed}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
aim:
_target_: aim.pytorch_lightning.AimLogger
repo: ${paths.root_dir}
experiment: ${autoresearch.experiment_name}
train_metric_prefix: "train/"
val_metric_prefix: "val/"
test_metric_prefix: "test/"
system_tracking_interval: 10
log_system_params: false
capture_terminal_logs: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
_target_: {{ package_name }}.models.mlp.MLP
in_dim: ${datamodule.num_features}
hidden_dim: 16
out_dim: ${datamodule.num_classes}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
optimizer:
_target_: torch.optim.AdamW
lr: 0.001
weight_decay: 0.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
root_dir: ${oc.env:PROJECT_ROOT}
output_dir: ${hydra:runtime.output_dir}
work_dir: ${hydra:runtime.cwd}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
_target_: {{ package_name }}.plmodules.classifier.ClassificationModule
_partial_: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defaults:
- _self_
- autoresearch: default
- paths: default
- datamodule: dummy
- model: mlp
- plmodule: classifier
- trainer: default
- callbacks: default
- logger: aim
- opt: default
- accelerate: default

task_name: train
tags: ["dev", "{{ preset }}"]
seed: 42

hydra:
run:
dir: outputs/${task_name}/${now:%Y-%m-%d}/${now:%H-%M-%S}
sweep:
dir: multirun/${task_name}/${now:%Y-%m-%d}/${now:%H-%M-%S}
subdir: ${hydra:job.num}
job:
chdir: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
_target_: lightning.Trainer
default_root_dir: ${paths.output_dir}
accelerator: cpu
devices: 1
max_epochs: 1
fast_dev_run: false
logger: true
enable_checkpointing: false
enable_progress_bar: true
log_every_n_steps: 1
precision: ${accelerate.precision}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[project]
name = "{{ project_name }}"
version = "0.1.0"
description = "Aimx AutoResearch {{ stack }} repository"
readme = "README.md"
requires-python = ">=3.10,<3.13"
dependencies = [
"aim==3.27.0",
"aimx>=0.3.3",
"hydra-core>=1.3",
"lightning>=2.3",
"numpy>=1.24,<2.0.0",
"omegaconf>=2.3",
"rich>=13.7",
"rootutils>=1.0.7",
"sqlalchemy==1.4.49",
"torch>=2.1",
]

[dependency-groups]
dev = [
"pytest>=8.0",
]

[[tool.uv.index]]
name = "pypi"
url = "https://pypi.org/simple"
default = true

[tool.pytest.ini_options]
pythonpath = ["src"]
testpaths = ["tests"]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/{{ package_name }}"]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Aimx AutoResearch template package."""
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from {{ package_name }}.callbacks.autoresearch import AutoResearchMarker

__all__ = ["AutoResearchMarker"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from __future__ import annotations

import lightning as L


class AutoResearchMarker(L.Callback):
"""Log a small completion marker before Lightning finalizes loggers."""

def on_train_end(self, trainer: L.Trainer, pl_module: L.LightningModule) -> None:
if not trainer.logger:
return
for logger in trainer.loggers:
logger.log_metrics({"autoresearch/complete": 1.0}, step=int(trainer.global_step))
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from {{ package_name }}.datamodules.dummy import RandomClassificationDataModule

__all__ = ["RandomClassificationDataModule"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from __future__ import annotations

import torch
from lightning import LightningDataModule
from torch.utils.data import DataLoader, TensorDataset, random_split


class RandomClassificationDataModule(LightningDataModule):
def __init__(
self,
num_samples: int = 64,
num_features: int = 8,
num_classes: int = 2,
batch_size: int = 16,
num_workers: int = 0,
seed: int = 42,
) -> None:
super().__init__()
self.save_hyperparameters()
self.train_dataset = None
self.val_dataset = None

def setup(self, stage: str | None = None) -> None:
generator = torch.Generator().manual_seed(int(self.hparams.seed))
x = torch.randn(int(self.hparams.num_samples), int(self.hparams.num_features), generator=generator)
weights = torch.randn(int(self.hparams.num_features), int(self.hparams.num_classes), generator=generator)
y = torch.argmax(x @ weights, dim=1)
dataset = TensorDataset(x, y)
train_len = max(1, int(0.8 * len(dataset)))
val_len = len(dataset) - train_len
self.train_dataset, self.val_dataset = random_split(dataset, [train_len, val_len], generator=generator)

def train_dataloader(self) -> DataLoader:
return DataLoader(
self.train_dataset,
batch_size=int(self.hparams.batch_size),
num_workers=int(self.hparams.num_workers),
shuffle=True,
)

def val_dataloader(self) -> DataLoader:
return DataLoader(
self.val_dataset,
batch_size=int(self.hparams.batch_size),
num_workers=int(self.hparams.num_workers),
shuffle=False,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from {{ package_name }}.models.mlp import MLP

__all__ = ["MLP"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from __future__ import annotations

from torch import nn


class MLP(nn.Module):
def __init__(self, in_dim: int, hidden_dim: int, out_dim: int) -> None:
super().__init__()
self.net = nn.Sequential(
nn.Linear(in_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, out_dim),
)

def forward(self, x):
return self.net(x)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from {{ package_name }}.plmodules.classifier import ClassificationModule

__all__ = ["ClassificationModule"]
Loading
Loading