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
51 changes: 39 additions & 12 deletions .cursor/commands/scaffold.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
Usage: `/scaffold <component> <Name>`

- `$1` = component. Today the registry ships `scheduler`. Adding `model` or `pipeline` is a data change (see `templates/README.md`), not a new command.
- `$2` = PascalCase name **without** the type suffix. Example: `EulerLite` → class `EulerLiteScheduler`, file `scheduling_euler_lite.py`.
- `$2` = PascalCase name **without** the type suffix. Example: `EulerLite` → class `EulerLiteScheduler`, file `scheduling_euler_lite.py`. If those files already exist, pick a new unused `$2`.

Done is only what `conventions/rules.yaml` checks. Copy the templates. Do not
start from a blank file, from library scheduler source, or from a docs search.
Done is what `conventions/rules.yaml` checks **and**, on a real library checkout, what the library's own CI checks. Copy the templates. Do not start from a blank file, from library scheduler source, or from a docs search.

## 0. Ground in the registry

Expand All @@ -24,32 +23,60 @@ Convert `$2` to snake_case (`EulerLite` → `euler_lite`). The class is `$2Sched
- Test: `tests/schedulers/test_scheduling_<snake>.py`

This stand-in repo uses a thin `src/diffusers/schedulers/` tree so the path
matches the real library. It is not a full diffusers checkout.
matches the real library. On the fork, that path is the real library.

## 2. Copy the template, then rename
## 2. Copy the template, then rename every placeholder

- Start from `templates/scheduler/scheduling_TEMPLATE.py`. Copy it to the implementation path.
- Rename `TemplateScheduler` to `$2Scheduler`.
- Keep `TODO(engineer)` in `step()`. Do not replace the placeholder.
- Keep `TODO(engineer)` in `step()`. Do not replace that placeholder.
- After the copy, the new files must not contain `TEMPLATE —`, `CHANGE_ME`,
`ChangeMeScheduler`, or `TemplateScheduler`. Add a `# Copied from` marker only
when a block is actually copied (malformed markers fail `check_copies`).

## 3. Copy the contract test (TEST001 / TEST002)

Copy `tests/_templates/scheduler_test.py` to the test path. Set `TARGET` and
`CLASS` only.
`CLASS`. Replace every other template token. The test docstring names
`$2Scheduler`, not ChangeMe.

## 4. File-scoped gate
## 4. File-scoped convention gate (never `--all` on the library)

```bash
python3 tools/convention_check.py src/diffusers/schedulers/scheduling_<snake>.py
python3 -m unittest tests.schedulers.test_scheduling_<snake> -v
```

Fix every **blocking** finding. Stop at 0 blocking. Behavioral tests skip
without torch — do not edit `step()` to make them pass.
On the fork the checker lives at `ramp-kit/tools/convention_check.py`. Fix every
**blocking** finding. Stop at 0 blocking. Do not edit `step()` math to make
behavioral tests pass.

## 5. Report
## 5. On a real library checkout: register, then invoke library CI

If this workspace has the library `Makefile` (`style` / `quality` targets):

1. Register `$2Scheduler` alphabetically in
`src/diffusers/schedulers/__init__.py` (`_import_structure` + TYPE_CHECKING
import), `src/diffusers/__init__.py` (schedulers lazy list + TYPE_CHECKING
import), then run `python utils/check_dummies.py --fix_and_overwrite`.
2. **Invoke** the library's own tooling until each exits 0. Do not reimplement
them:

```bash
make style
make quality
python utils/check_copies.py && python utils/check_dummies.py && python utils/check_repo.py
python3 -m unittest tests.schedulers.test_scheduling_<snake> -v
```

Only open a PR when those commands pass. Do not disable inherited workflows.
Do not overwrite `AGENTS.md` or `.ai/`.

## 6. Report

- Blocking rule ids the gate checked
- 0 blocking
- `TODO(engineer)` still in `step()`
- Confirmation you added only those two files
- No leftover `TEMPLATE —` / `CHANGE_ME` / `ChangeMeScheduler` / `TemplateScheduler`
- On the fork: `make style` / `make quality` / check_copies / check_dummies /
check_repo exit codes
51 changes: 29 additions & 22 deletions docs/CURSOR_PROMPTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,42 +22,48 @@ Do **not** enable Hub HTTP MCP. The draft PR **base must be `main`** so overlay
CI (`ramp-kit-overlay` / `overlay-gate`) actually runs. Do not stack onto a
`cursor/…` topic branch.

`EulerLiteScheduler` is already on fork `main`. This prompt scaffolds **HeunLite**.
`EulerLiteScheduler` and `HeunLiteScheduler` files are already on fork `main`.
This prompt scaffolds a **new unused** name (example: **PNDMLite**) and runs the
library's own CI tools until they pass. Do not overwrite existing lite files.

```
You just joined. First contribution on this huggingface/diffusers FORK (alex-16moro/diffusers). Overlay: ramp-kit/ (from alex-16moro/diffuser_agent).

Done is only what ramp-kit/conventions/rules.yaml checks. The templates already pass those checks. Copy them. Do not design a scheduler.
Copy the overlay templates. Do not design a scheduler. Keep TODO(engineer) in step(). Do not disable inherited workflows. Do not overwrite .ai/ or root AGENTS.md.

If ramp-kit/ is missing:
git clone --depth 1 https://github.com/alex-16moro/diffuser_agent.git ramp-kit

Scope — create exactly these two files, then stop:
- src/diffusers/schedulers/scheduling_heun_lite.py
- tests/schedulers/test_scheduling_heun_lite.py
Pick an unused PascalCase name. If scheduling_euler_lite.py / scheduling_heun_lite.py / scheduling_pndm_lite.py already exist, pick a different unused $2. Example when those three are free: PNDMLite → PNDMLiteScheduler.

Do this:
Follow ramp-kit overlay /scaffold (.cursor/commands/scaffold.md):

1. Read ramp-kit/conventions/rules.yaml. Blocking ids for this change: SCHED001, SCHED002, SCHED003, REPRO001, DEVICE001, DEPR001, MUT001, TEST001, TEST002.
1. Read ramp-kit/conventions/rules.yaml. Blocking ids: SCHED001, SCHED002, SCHED003, REPRO001, DEVICE001, DEPR001, MUT001, TEST001, TEST002.

2. Copy ramp-kit/templates/scheduler/scheduling_TEMPLATE.py → src/diffusers/schedulers/scheduling_heun_lite.py
Rename TemplateScheduler → HeunLiteScheduler. Keep TODO(engineer) in step().
2. Copy ramp-kit/templates/scheduler/scheduling_TEMPLATE.py → src/diffusers/schedulers/scheduling_<snake>.py
Rename TemplateScheduler → $2Scheduler. Keep TODO(engineer) in step().
After the copy, no TEMPLATE —, CHANGE_ME, ChangeMeScheduler, or TemplateScheduler may remain.

3. Copy ramp-kit/tests/_templates/scheduler_test.py → tests/schedulers/test_scheduling_heun_lite.py
Set TARGET and CLASS only.
3. Copy ramp-kit/tests/_templates/scheduler_test.py → tests/schedulers/test_scheduling_<snake>.py
Set TARGET and CLASS. Replace every other template token.

4. python3 ramp-kit/tools/convention_check.py src/diffusers/schedulers/scheduling_heun_lite.py
4. Register $2Scheduler alphabetically in src/diffusers/schedulers/__init__.py, src/diffusers/__init__.py, then: python utils/check_dummies.py --fix_and_overwrite

5. python3 ramp-kit/tools/convention_check.py src/diffusers/schedulers/scheduling_<snake>.py
Fix blocking findings only. Stop at 0 blocking. Never convention_check.py --all.

5. python3 -m unittest tests.schedulers.test_scheduling_heun_lite -v
Structural/signature must pass. Skips without torch are success — do not edit step() to make behavioral tests pass.
6. Invoke the library's own tooling until each exits 0 (do not reimplement them):
make style
make quality
python utils/check_copies.py && python utils/check_dummies.py && python utils/check_repo.py
python3 -m unittest tests.schedulers.test_scheduling_<snake> -v
Do not edit step() math. Behavioral tests with torch must pass.

6. Draft PR on this fork, base main, those two files only.
Title: [fork demo — not for upstream] Scaffold HeunLiteScheduler contract
Never PR huggingface/diffusers. Do not overwrite .ai/ or root AGENTS.md. Do not delete inherited workflows.
Do not copy ramp-kit/examples/candidate_scheduler/. Do not touch scheduling_euler_lite.py.
7. Draft PR on this fork, base main, only after those commands pass.
Title: [fork demo — not for upstream] Add $2Scheduler scaffold
Never PR huggingface/diffusers. Do not copy ramp-kit/examples/candidate_scheduler/.

Then stop. Report the two paths, blocking rule ids, 0 blocking, leftover TODO(engineer), draft PR URL. Do not merge.
Then stop. Report paths, blocking rule ids, 0 blocking, leftover TODO(engineer), library-gate exit codes, draft PR URL. Do not merge.
```

---
Expand Down Expand Up @@ -153,13 +159,14 @@ the tooling. Keep it minimal; do not implement a real model.
the overlay templates. Euler/DDPM source is how *we* verified the YAML, not
the first-PR recipe. MCP is opt-in, not the demo.
- **Catch-early:** you run this on the **kit**, not inside Prompt A.
- **Correct scaffold:** two files in `src/diffusers/schedulers/` + matching
test; file-scoped gate 0 blocking; `TODO(engineer)` still in `step()`.
- **Correct scaffold:** scheduler + matching test with no leftover placeholders;
registered in inits/dummies; file-scoped gate 0 blocking; `TODO(engineer)`
still in `step()`; library `make style` / `make quality` / check_* green.
- **File-scoped gate:** never `convention_check.py --all` on the fork.
- **Multi-audience:** open `projections/pm|qa|devops/` and `.github/` — same
rules, different surface. Note the upstream-vs-customer split. One registry,
many projections — not a capability per SDLC step.
- **Fork PR hygiene:** draft, **base `main`**, `[fork demo — not for upstream]`.
Overlay-gate is the customer check-run. Overlay green does not mean Hugging
Face CI is green; do not delete their workflows. Two files only; math stays TODO.
Face CI is green; do not delete their workflows. Math stays TODO.
- **Maintainability:** Prompt B — one edit propagates everywhere.
8 changes: 6 additions & 2 deletions overlay/OVERLAY.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,14 @@ Hub HTTP and extra servers stay in `.cursor/mcp.optional.json`.
`make demo-maintain` if you show maintainability.
2. Then launch on **this fork** (`alex-16moro/diffusers`), branch `main`.
3. Scaffold writes `src/diffusers/schedulers/scheduling_<name>.py` here
(HeunLite if EulerLite already exists). Gate:
(a new unused name if EulerLite/HeunLite already exist). Gate:
`python3 ramp-kit/tools/convention_check.py <that file>` (never `--all`).
Register the class in `schedulers/__init__.py`, `diffusers/__init__.py`, and
dummy objects. Invoke `make style`, `make quality`, and
`python utils/check_copies.py && python utils/check_dummies.py && python utils/check_repo.py`
until they exit 0.
4. Open the PR **on this fork**, not on huggingface/diffusers, **draft, base
`main`**, those two files only.
`main`**, only after those library gates pass.

Catch-early fixture: `ramp-kit/examples/candidate_scheduler` — do not copy it.

Expand Down
60 changes: 45 additions & 15 deletions overlay/scaffold.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ Usage: `/scaffold <component> <Name>`

This workspace is `huggingface/diffusers` (fork). Overlay kit is `ramp-kit/`
(cloned from alex-16moro/diffuser_agent). Do not overwrite `.ai/` or root `AGENTS.md`.
Do not disable or guard inherited GitHub workflows.

`$1` = component (`scheduler`). `$2` = PascalCase name without suffix (`HeunLite`
→ `HeunLiteScheduler`, `scheduling_heun_lite.py`).
`$1` = component (`scheduler`). `$2` = PascalCase name without suffix (`PNDMLite`
→ `PNDMLiteScheduler`, `scheduling_pndm_lite.py`). If `scheduling_<snake>.py`
already exists, pick a new unused `$2`.

Done is only what `ramp-kit/conventions/rules.yaml` checks. Copy the templates.
Do not start from library scheduler source and do not search docs for this PR.
Copy the overlay templates. Do not start from library scheduler source and do
not search docs for this PR. Keep `TODO(engineer)` in `step()`.

## 0. Ground in the registry

Expand All @@ -22,33 +24,61 @@ read or copy `ramp-kit/examples/candidate_scheduler/`.

## 1. Paths (library layout, not the kit stand-in)

Convert `$2` to snake_case (`HeunLite` → `heun_lite`). The class is `$2Scheduler`.
Convert `$2` to snake_case (`PNDMLite` → `pndm_lite`). The class is `$2Scheduler`.

- Implementation: `src/diffusers/schedulers/scheduling_<snake>.py`
- Test: `tests/schedulers/test_scheduling_<snake>.py`

If `scheduling_euler_lite.py` already exists, do not overwrite it — use a new `$2`.
Do not overwrite `scheduling_euler_lite.py` or `scheduling_heun_lite.py`.

## 2. Copy from the overlay templates
## 2. Copy from the overlay templates — replace every placeholder

- `ramp-kit/templates/scheduler/scheduling_TEMPLATE.py` → implementation
- Rename `TemplateScheduler` → `$2Scheduler`
- Keep `TODO(engineer)` in `step()`. Do not replace the placeholder.
- Keep `TODO(engineer)` in `step()`. Do not replace that placeholder.
- After the copy, the new files must not contain `TEMPLATE —`, `CHANGE_ME`,
`ChangeMeScheduler`, or `TemplateScheduler`.
- Add a `# Copied from` marker only when a block is actually copied.

## 3. Copy the contract test (TEST001 / TEST002)

Copy `ramp-kit/tests/_templates/scheduler_test.py`. Set `TARGET` and `CLASS`
only. Do not add files or tests the template does not already contain.
Copy `ramp-kit/tests/_templates/scheduler_test.py`. Set `TARGET` and `CLASS`.
Replace every other template token so the test names `$2Scheduler`.

## 4. Gate (file-scoped — do not `--all` this library)
## 4. Register the scheduler (library public API)

Alphabetically insert `$2Scheduler` in:

- `src/diffusers/schedulers/__init__.py` — `_import_structure["scheduling_<snake>"]`
and the matching TYPE_CHECKING import
- `src/diffusers/__init__.py` — the torch schedulers lazy-import list and the
TYPE_CHECKING import from `.schedulers`

Then invoke (do not hand-write dummy classes):

```bash
python utils/check_dummies.py --fix_and_overwrite
```

## 5. File-scoped convention gate (never `--all`)

```bash
python3 ramp-kit/tools/convention_check.py src/diffusers/schedulers/scheduling_<snake>.py
python3 -m unittest tests.schedulers.test_scheduling_<snake> -v
```

Fix every **blocking** finding. Stop at 0 blocking. Behavioral skips without
torch are success — do not edit `step()` to make them pass.
Fix every **blocking** finding. Stop at 0 blocking. Do not edit `step()` math.

## 6. Library CI — invoke until green, then open the PR

Iterate until all of these exit 0. Do not reimplement them:

```bash
make style
make quality
python utils/check_copies.py && python utils/check_dummies.py && python utils/check_repo.py
python3 -m unittest tests.schedulers.test_scheduling_<snake> -v
```

Do not open a PR against huggingface/diffusers — PR this fork, **draft, base
`main`**, those two files only.
Only then open a **draft** PR on this fork, **base `main`**. Never PR
huggingface/diffusers. Title prefix: `[fork demo — not for upstream]`.
50 changes: 33 additions & 17 deletions templates/scheduler/scheduling_TEMPLATE.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
# TEMPLATE — the /scaffold command copies this to
# src/diffusers/schedulers/scheduling_<snake>.py and renames the class.
# It is convention-correct (passes the gate); the numerical method is left as a
# TODO because that is the engineer's actual work, not something to fabricate.
# Copyright 2026 The HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Optional, Tuple, Union

import torch
Expand All @@ -11,7 +21,7 @@


class TemplateScheduler(SchedulerMixin, ConfigMixin):
"""One-line summary of the scheduler (replace TemplateScheduler with <Name>Scheduler)."""
"""Discrete scheduler scaffold. Sampler math stays in `step()` as TODO(engineer)."""

@register_to_config
def __init__(
Expand All @@ -31,13 +41,16 @@ def set_timesteps(self, num_inference_steps: int, device: Union[str, torch.devic
"""Set the discrete timesteps used for the denoising loop.

Args:
num_inference_steps: Number of diffusion steps used at inference.
device: Device the timesteps should be moved to.
num_inference_steps (`int`):
Number of diffusion steps used at inference.
device (`str` or `torch.device`, *optional*):
Device the timesteps tensor should be moved to.
"""
self.num_inference_steps = num_inference_steps
step = self.config.num_train_timesteps // num_inference_steps
timesteps = (torch.arange(0, num_inference_steps) * step).round().long()
timesteps = torch.flip(timesteps, dims=[0]) # torch has no [::-1]
# torch.Tensor has no [::-1]; reverse with flip (see DDPM-style spacing).
timesteps = torch.flip(timesteps, dims=[0])
self.timesteps = timesteps.to(device) if device is not None else timesteps

def step(
Expand All @@ -51,18 +64,21 @@ def step(
"""Predict the sample at the previous timestep.

Args:
model_output: Direct output from the learned diffusion model.
timestep: The current discrete timestep in the diffusion chain.
sample: A current instance of a sample created by the diffusion process.
generator: A torch.Generator for reproducible sampling.
return_dict: Whether to return a SchedulerOutput or a plain tuple.
model_output (`torch.Tensor`):
Direct output from the learned diffusion model.
timestep (`int`):
Current discrete timestep in the diffusion chain.
sample (`torch.Tensor`):
Current sample.
generator (`torch.Generator`, *optional*):
PRNG for reproducible noise. Never use a global RNG.
return_dict (`bool`, *optional*, defaults to `True`):
Whether to return a [`SchedulerOutput`] or a plain tuple.

Returns:
SchedulerOutput or tuple with the predicted previous sample.
[`SchedulerOutput`] or `tuple`: Predicted previous sample.
"""
noise = torch.randn(
sample.shape, generator=generator, device=sample.device, dtype=sample.dtype
)
noise = torch.randn(sample.shape, generator=generator, device=sample.device, dtype=sample.dtype)
# TODO(engineer): replace this placeholder with the real update rule.
prev_sample = sample - model_output + 0.0 * noise

Expand Down
Loading
Loading