Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
fe9f2c6
chore: open 0.11.0-alpha.1 on develop
dkackman Oct 9, 2026
51f74ff
chore: update dependencies in package.json
dkackman Oct 9, 2026
27e1c79
ci: run the architecture ratchet whatever the other checks do (#790)
dkackman Oct 10, 2026
3732a51
refactor(cuts): split plan_cuts and _align_lyrics under the ratchet (…
dkackman Oct 10, 2026
efd8f3c
refactor(tasks): split task.py's handlers by family (#790)
dkackman Oct 10, 2026
7f5e2ac
refactor(references): one iter_steps walk for every step-validation p…
dkackman Oct 10, 2026
b54eac5
refactor: bring ten long or complex functions under the ratchet (#790)
dkackman Oct 10, 2026
0daafee
refactor(h3): move the H3 block classes to module level (#790)
dkackman Oct 10, 2026
dc3fdd8
refactor(task_domains): move the per-family rules to dw/task_problems…
dkackman Oct 10, 2026
9d0c5bc
fix(arch): leave .claude/ out of the metrics (#790)
dkackman Oct 10, 2026
4beadff
chore(arch): re-baseline after the #790 payback
dkackman Oct 10, 2026
a43b527
fix(mcp): #792 #799 - client-timeout fallback in wait step; running j…
dkackman Oct 10, 2026
21f5805
Merge fix/792-799-wait-fallback (#792, #799)
dkackman Oct 10, 2026
0ff8f1e
Merge branch 'develop' of https://github.com/dkackman/diffusers-workf…
dkackman Oct 10, 2026
e0b1c07
docs(mcp): #793 - point text-only clients from get_output_audio to ge…
dkackman Oct 10, 2026
295daca
docs(skills): #803 - text-only audio QA, save_prompt for large argume…
dkackman Oct 10, 2026
7c14e18
feat(templates): #794 - relevel-clip utility template; clip warning/f…
dkackman Oct 10, 2026
42eacbc
feat(tasks): #802 - measure_sync cross-correlates a shot's audio agai…
dkackman Oct 10, 2026
3371692
fix(templates): #795 - declare hold_audio (null) on music-video and t…
dkackman Oct 10, 2026
116a857
fix(plan): #796 - run the #593 field-shift check for derived/per_entr…
dkackman Oct 10, 2026
1eee13a
fix(server): #799 - a mid-run manifest entry with no subfolder reads …
dkackman Oct 10, 2026
f90f9df
feat(plan): #797 - price an inline workflow by pipeline identity; bas…
dkackman Oct 10, 2026
4e56a9e
test: #797 - patch.object instead of a string target
dkackman Oct 10, 2026
7834f05
feat(assets): #798 - list_assets takes limit (newest by mtime), prefi…
dkackman Oct 10, 2026
da553d5
docs(mcp): #798 - list_assets limit/prefix/total
dkackman Oct 10, 2026
f97b6b6
refactor(tasks): #802 - measure_sync builds findings with assessment_…
dkackman Oct 10, 2026
9570a54
fix(mcp): #806 - save_prompt takes a plain string as {"text": ...}; d…
dkackman Oct 10, 2026
e36604f
fix(joins): #800 - no level_spread warning when a later pair_audio re…
dkackman Oct 10, 2026
a2db75f
docs(proposal): #801 - per-entry singer on music-video needs an optio…
dkackman Oct 10, 2026
8b52148
docs(arch): #797 #800 - map rows for inherited cost and the level-spr…
dkackman Oct 10, 2026
78696f2
fix(assess): #807 - assess_output runs the whole-file level rules (fu…
dkackman Oct 10, 2026
b27b24d
feat(for_each): #801 - optional item field with a default; music-vide…
dkackman Oct 10, 2026
0370ca7
docs(arch): #801 - map row names a real test
dkackman Oct 10, 2026
184c79c
fix(codeql): model validate_media_path's own body as a validator (ale…
dkackman Oct 10, 2026
55559c0
fix(elision): #809 - name the item field, not an unpassed variable, w…
dkackman Oct 10, 2026
8f23dea
fix(plan): #796 - field-shift check gates the observed basis; only nu…
dkackman Oct 10, 2026
6090e22
feat(music-video): #808 - hold each shot's slice by default; measured…
dkackman Oct 10, 2026
64272dc
fix(validate): #813 - refuse an image/last_image argument fed previou…
dkackman Oct 10, 2026
784461e
feat(plan_cuts): #805 - divide a split sung scene's lyric across its …
dkackman Oct 10, 2026
d5d8678
feat(ui): per-card VRAM readings and a sidebar that marks the open wo…
dkackman Oct 10, 2026
6517fa3
feat(ui): show step and job durations on the job page
dkackman Oct 10, 2026
b3d2932
feat(cache): implement persistent on-disk cache for torch.compile acr…
dkackman Oct 10, 2026
a0afd77
Merge pull request #814 from dkackman/feat/ui-nav-memory-durations
dkackman Oct 10, 2026
2cfae54
feat(h3): default the T2VA/FL2VA templates to lightx2v's 4-step v1.2 …
dkackman Oct 10, 2026
447a430
loras(h3): turbo-4step-keyframe evidence from the image-to-video and …
dkackman Oct 10, 2026
579aa1f
feat(env): add example environment configuration for diffusers-workflow
dkackman Oct 10, 2026
98b500e
fix(ui): type jest-dom matchers through the vitest entry after the vi…
dkackman Oct 10, 2026
e6ff87f
chore(arch): raise modules 205 -> 206 for dw/tasks/measure_sync.py
dkackman Oct 10, 2026
66bfb05
docs(release): draft the 0.11.0 notes
dkackman Oct 10, 2026
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
132 changes: 132 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# diffusers-workflow environment knobs.
#
# Copy to .env and uncomment what you need. dw loads .env from the directory
# a process starts in (load_dotenv() in dw/__init__.py), before diffusers or
# torch is imported, so every line here reaches dw.serve, dw.run and each
# spawned worker. A variable already set in the environment wins over this file.
# Everything below is commented out, so copying this file as is changes nothing.
# Workers read .env when they start, so restart dw.serve (or its idle workers)
# after editing it.
#
# Never commit .env: it is git-ignored because tokens go in it.

# --- dw: server and clients --------------------------------------------------

# Bearer token for dw.serve. Set, every API, UI and MCP request must carry it.
# dw.run and the stdio MCP server read the same variable, so one value
# configures both ends. Required before binding to anything but localhost.
# DW_API_TOKEN=some-long-random-string

# This server's public origin, for links handed to someone not at the box
# (docs/REMOTE.md). Overrides the 'public_url' setting.
# DW_PUBLIC_URL=http://lem:8765

# Where dw.run and the stdio MCP server find dw.serve.
# DW_MCP_URL=http://127.0.0.1:8765

# The workspace the stdio MCP session starts in - a name on the server.
# DW_MCP_WORKSPACE=default

# The cap on one wait_for_job call, in seconds (default 55). Raise it only if
# your MCP client holds a tool call open longer than that.
# DW_MCP_MAX_WAIT_SECONDS=55

# --- dw: engine ----------------------------------------------------------------

# The device for a CLI run: cuda, cuda:1, mps or cpu (default: detected).
# dw.serve picks cards with --devices instead.
# DW_DEVICE=cuda

# The workspace directory: workflows, assets, outputs, prompts
# (default ~/diffusers-workspace; dw.serve --workspace wins over it).
# DW_WORKSPACE=~/diffusers-workspace

# 'flat' restores the pre-run-directory output layout for scripts that glob
# the output folder (default: one directory per run).
# DW_OUTPUT_LAYOUT=flat

# Where settings.json, the job database and the log live
# (default ~/.diffusers_helper).
# DIFFUSERS_HELPER_ROOT=~/.diffusers_helper

# --- Defaults dw sets itself (shown so you know they exist) -----------------
# Each is set at import only when absent; set it here to override.

# Lets the CUDA allocator grow segments instead of fragmenting fixed ones.
# PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True

# Loads sharded checkpoints on several threads (default true; false on macOS,
# where it segfaulted an SDNQ load on MPS).
# HF_ENABLE_PARALLEL_LOADING=true

# MPS only: let PyTorch use all unified memory.
# PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.0

# torch.compile's cache, and with it SDNQ's Triton autotune results - the
# first-step stall of a new size or frame count is paid once per box while
# this survives reboots (default $XDG_CACHE_HOME/dw/torchinductor, or
# ~/.cache/dw/torchinductor; PyTorch's own default under /tmp does not).
# TORCHINDUCTOR_CACHE_DIR=~/.cache/dw/torchinductor

# --- Hugging Face Hub (all of diffusers and transformers download through it) --

# Token for gated models such as FLUX.1-dev (or run `huggingface-cli login` once).
# HF_TOKEN=hf_...

# Where models are cached (default ~/.cache/huggingface). Point HF_HUB_CACHE
# at a bigger disk without moving the token and settings in HF_HOME.
# HF_HOME=~/.cache/huggingface
# HF_HUB_CACHE=/mnt/models/hub

# Never touch the network: every model must already be cached. Also stops
# diffusers checking Hub kernel publishers (see DIFFUSERS_TRUST_REMOTE_KERNELS).
# HF_HUB_OFFLINE=1

# Faster large downloads through the Xet backend, at the cost of more CPU
# and memory while downloading.
# HF_XET_HIGH_PERFORMANCE=1

# Seconds before a slow download is abandoned (default 10).
# HF_HUB_DOWNLOAD_TIMEOUT=60

# HF_HUB_DISABLE_TELEMETRY=1

# --- diffusers -----------------------------------------------------------------

# Allow attention kernels from Hub publishers diffusers does not list as
# trusted. Needed for "attention_backend": "sage_hub" (SageAttention 2 from
# SageAttention/sage-attention) - about 30% faster per MiniMax-H3 step on an
# RTX 3090. It downloads and runs remote code from any such publisher, not
# only that one, so enable it deliberately. CUDA only; no use on MPS.
# DIFFUSERS_TRUST_REMOTE_KERNELS=true

# The attention backend for every model that sets none (default native). A
# workflow's per-component "attention_backend" overrides it. Same values as
# that setting: native, flash_hub, sage_hub, _native_cudnn, ...
# DIFFUSERS_ATTN_BACKEND=native

# Refuse any pipeline or component that would load custom code from the Hub.
# dw's own trust gate (--trust-workflows) already covers workflow files; this
# closes the same door at the diffusers layer.
# DIFFUSERS_DISABLE_REMOTE_CODE=true

# --- transformers --------------------------------------------------------------

# Log level for transformers (debug, info, warning, error, critical).
# TRANSFORMERS_VERBOSITY=error

# Silence the tokenizers fork-safety warning after a tokenizer has been used.
# TOKENIZERS_PARALLELISM=false

# --- SDNQ quantization (CUDA) -----------------------------------------------------

# SDNQ's int matmuls run on autotuned Triton kernels by default. 0 falls back
# to PyTorch's kernels: no first-step autotune stall at a new shape, but
# slower steps. Measure before turning it off.
# SDNQ_USE_TRITON_MM=0

# Narrow the autotune search to shorten that stall, at the risk of a slower
# kernel. Comma lists; the defaults are shown.
# SDNQ_TRITON_MM_BLOCK_SIZE_M_LIST=64,128,256
# SDNQ_TRITON_MM_BLOCK_SIZE_N_LIST=64,128,256
# SDNQ_TRITON_MM_BLOCK_SIZE_K_LIST=32,64
14 changes: 11 additions & 3 deletions .github/codeql/dw-security/DwPathSanitizers.qll
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,22 @@ private class UnconfinedPath extends Path::PathNormalization::Range {
* own `os.path.realpath` / `os.path.exists` of the value it is in the
* middle of checking. This is the "trusted rather than checked" half of
* the trade described at the top of this file, and it is scoped to the
* definitions in `dw/security.py`.
* definitions in `dw/security.py`, plus `validate_media_path` in
* `dw/locations.py`: it confines against several roots at once (the
* workflow directory, the asset libraries, the output root - so even with
* no base directory it confines), a check the query cannot see, and its
* own existence test of the confined path was reported as an access.
* tests/test_locations.py is where that containment is established.
*/
private class ValidatorParameter extends PathInjection::Sanitizer {
ValidatorParameter() {
exists(Function validator |
pathValidatorName(validator.getName()) or nameValidatorName(validator.getName())
(pathValidatorName(validator.getName()) or nameValidatorName(validator.getName())) and
validator.getLocation().getFile().getRelativePath() = "dw/security.py"
or
validator.getName() = "validate_media_path" and
validator.getLocation().getFile().getRelativePath() = "dw/locations.py"
|
validator.getLocation().getFile().getRelativePath() = "dw/security.py" and
this.asExpr() = validator.getArg(_)
)
}
Expand Down
16 changes: 12 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,23 +37,31 @@ jobs:
- name: Install torch (CPU)
run: pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
- name: Install dependencies
id: install
# requirements.txt resolves pyproject.toml's dependencies + server
# extra; requirements-test.txt the dev extra. diffusers from git
# goes on afterwards so the resolver doesn't replace it with a
# release
run: |
pip install -c constraints-openapi.txt -r requirements.txt -r requirements-test.txt
pip install git+https://github.com/huggingface/diffusers
# Each check below runs whenever the install succeeded, so one failing
# check never hides another: the ratchet was red for three days behind
# a failing test (#790)
# Guards the architecture metrics against drifting past the committed
# baseline (modules, size ceiling, cycles); see docs/stabilization/
- name: Architecture ratchet
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
run: python scripts/arch_metrics.py --check docs/stabilization/baseline.json
- name: Format check
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
run: ruff format --check dw dw_mcp tests scripts
- name: Lint
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
run: ruff check dw dw_mcp tests scripts
- name: Tests
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
run: pytest -q
# Guards the architecture metrics against drifting past the committed
# baseline (modules, size ceiling, cycles); see docs/stabilization/
- name: Architecture ratchet
run: python scripts/arch_metrics.py --check docs/stabilization/baseline.json

ui:
runs-on: ubuntu-latest
Expand Down
7 changes: 4 additions & 3 deletions docs/ACCELERATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ Select the attention implementation diffusers uses for the duration of each pipe
}
```

Common values: `"flash"`, `"flash_hub"`, `"sage"`, `"sage_hub"`, `"native"`, `"flex"`. The full set is diffusers' `AttentionBackendName` enum - availability depends on what's installed (`flash-attn`, `sageattention`, etc.) and the platform. `_hub`-suffixed backends are fetched from the Hugging Face Hub kernel registry on first use, which needs the `kernels` package installed (`pip install kernels`) - it is not a dw dependency, and no bundled workflow sets a backend, so each runs on a plain install.
Common values: `"flash"`, `"flash_hub"`, `"sage"`, `"sage_hub"`, `"native"`, `"flex"`. The full set is diffusers' `AttentionBackendName` enum - availability depends on what's installed (`flash-attn`, `sageattention`, etc.) and the platform. `_hub`-suffixed backends are fetched from the Hugging Face Hub kernel registry on first use, which needs the `kernels` package installed (`pip install kernels`) - it is not a dw dependency, and no bundled workflow sets a backend, so each runs on a plain install. diffusers also refuses a Hub kernel whose publisher it does not list as trusted - `sage_hub` among them, since SageAttention 2 comes from `SageAttention/sage-attention` - until `DIFFUSERS_TRUST_REMOTE_KERNELS=true`, which allows remote code from any such publisher. On an RTX 3090 `sage_hub` measured about 30% faster per MiniMax-H3 step (13.8 s against 19.2 s at 960x544x124, 54.7 s against 81.2 s at 345 frames); the local `sage` backend needs `sageattention>=2.1.1`, which PyPI does not carry.

A component can also pin its backend persistently instead, via `set_attention_backend`:

Expand Down Expand Up @@ -186,7 +186,7 @@ Compile a component once it is fully configured - the graph captures final dtype

Typical gains are 1.3-1.5x on diffusion transformers, and compilation stacks with the caches above. Notes:

- **First run pays the compile cost.** The [server](SERVER.md)'s persistent worker keeps compiled pipelines loaded between runs, so the cost is paid once per session rather than once per generation.
- **First run pays the compile cost.** The [server](SERVER.md)'s persistent worker keeps compiled pipelines loaded between runs, so the cost is paid once per session rather than once per generation. A new session still retraces, but the kernels it built come back from the on-disk cache, which dw keeps across reboots (`TORCHINDUCTOR_CACHE_DIR`, see [Environment Defaults](#environment-defaults)).
- **Pin the attention backend** on a compiled component (`"attention_backend"` in the same `components` entry) rather than using the pipeline-level per-call context manager, which forces recompiles.
- **Composes with offloading**: apply `group_offload` and `compile` on the same component and the offload hooks are installed first, as required. Skipped with a warning on MPS.
- **Don't combine `fullgraph` with a `cache`**: the cache hooks decide skip-or-compute per step, a data-dependent branch diffusers wraps in `torch.compiler.disable` - it needs the graph break that `fullgraph: true` forbids. Compile with the default (partial) graph mode when a cache is active.
Expand Down Expand Up @@ -272,13 +272,14 @@ Device-level settings, read once at startup from `~/.diffusers_helper/settings.j

## Environment Defaults

Set automatically at import unless already present in the environment (export your own value to override):
Set automatically at import unless already present in the environment (export your own value to override, or put it in a `.env` file in the directory dw starts from - [.env.example](../.env.example) lists these and the other knobs worth knowing: the Hub, diffusers, transformers and SDNQ):

| Variable | Default | Effect |
| -------- | ------- | ------ |
| `PYTORCH_CUDA_ALLOC_CONF` | `expandable_segments:True` | Lets the CUDA allocator grow segments instead of fragmenting fixed-size ones. Multi-step workflows churn differently-shaped allocations (generate, upscale, interpolate); fragmentation is what OOMs a card that nominally has room. |
| `HF_ENABLE_PARALLEL_LOADING` | `true` | Loads sharded checkpoints in parallel - faster cold starts. |
| `PYTORCH_MPS_HIGH_WATERMARK_RATIO` | `0.0` | MPS only - use all available unified memory. |
| `TORCHINDUCTOR_CACHE_DIR` | `$XDG_CACHE_HOME/dw/torchinductor` (`~/.cache/dw/torchinductor`) | Keeps `torch.compile`'s on-disk cache across reboots - Inductor's own default is under `/tmp`. A new worker still retraces, but a cache hit skips codegen and kernel builds, the bulk of a cold compile. Entries are per shape, so a new size or frame count compiles once more. |

For faster model downloads, optionally `pip install hf_transfer` and set `HF_HUB_ENABLE_HF_TRANSFER=1`. Not enabled automatically - it bypasses the Python HTTP stack and breaks some proxy setups.

Expand Down
Loading
Loading