diffusers-workflow 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diffusers_workflow-0.4.0.dist-info/METADATA +318 -0
- diffusers_workflow-0.4.0.dist-info/RECORD +260 -0
- diffusers_workflow-0.4.0.dist-info/WHEEL +5 -0
- diffusers_workflow-0.4.0.dist-info/entry_points.txt +7 -0
- diffusers_workflow-0.4.0.dist-info/licenses/LICENSE +201 -0
- diffusers_workflow-0.4.0.dist-info/top_level.txt +2 -0
- dw/__init__.py +440 -0
- dw/adapter_compatibility.py +226 -0
- dw/arguments.py +1231 -0
- dw/assessment_rules.py +159 -0
- dw/assets.py +130 -0
- dw/cache_blocks.json +16 -0
- dw/cache_blocks.py +146 -0
- dw/community_pipelines/pipeline_flux_rf_inversion.py +1184 -0
- dw/content_types.py +150 -0
- dw/dissolve_frame_errors.py +121 -0
- dw/docs/ACCELERATION.md +352 -0
- dw/docs/AGENT_LOOP.md +95 -0
- dw/docs/DEPENDENCIES.md +91 -0
- dw/docs/IP_ADAPTER.md +109 -0
- dw/docs/LORAS.md +131 -0
- dw/docs/MCP.md +517 -0
- dw/docs/PROMPT_WEIGHTING.md +78 -0
- dw/docs/QUANTIZATION.md +230 -0
- dw/docs/RECIPES_24GB.md +201 -0
- dw/docs/RELEASING.md +195 -0
- dw/docs/REMOTE.md +140 -0
- dw/docs/REPL_COMMANDS.md +121 -0
- dw/docs/REPL_WORKER_GUIDE.md +51 -0
- dw/docs/SECURITY.md +272 -0
- dw/docs/SECURITY_QUICKREF.md +112 -0
- dw/docs/SERVER.md +679 -0
- dw/docs/TASKS.md +1741 -0
- dw/docs/TESTING.md +71 -0
- dw/docs/WORKFLOW_GUIDE.md +2038 -0
- dw/docs/WORKSPACES.md +316 -0
- dw/download_watch.py +335 -0
- dw/elision.py +306 -0
- dw/events.py +275 -0
- dw/for_each.py +409 -0
- dw/host_memory.py +258 -0
- dw/host_memory_projection.py +230 -0
- dw/hub_cache.py +432 -0
- dw/introspection.py +1228 -0
- dw/kernel_availability.py +208 -0
- dw/locations.py +599 -0
- dw/log_setup.py +45 -0
- dw/loudness.py +82 -0
- dw/media_audio.py +217 -0
- dw/media_frames.py +367 -0
- dw/media_info.py +297 -0
- dw/pipeline_processors/chain.py +821 -0
- dw/pipeline_processors/config_objects.py +237 -0
- dw/pipeline_processors/pipeline.py +2297 -0
- dw/pipeline_processors/remote.py +46 -0
- dw/plan.py +920 -0
- dw/previous_results.py +411 -0
- dw/probe_paths.py +59 -0
- dw/prompt_schema.json +48 -0
- dw/prompt_weighting.py +378 -0
- dw/prompts.py +159 -0
- dw/realize.py +250 -0
- dw/reference_limits.py +215 -0
- dw/reference_names.py +125 -0
- dw/repl.py +338 -0
- dw/repl_commands.py +836 -0
- dw/repl_worker.py +159 -0
- dw/result.py +1720 -0
- dw/result_fps.py +82 -0
- dw/run.py +162 -0
- dw/runs.py +768 -0
- dw/scalar_result_validation.py +97 -0
- dw/schema.py +283 -0
- dw/security.py +1038 -0
- dw/select_validation.py +115 -0
- dw/serve.py +277 -0
- dw/server/__init__.py +2 -0
- dw/server/app.py +4586 -0
- dw/server/assess.py +132 -0
- dw/server/catalog_shape.py +487 -0
- dw/server/enhancers.py +129 -0
- dw/server/exports.py +480 -0
- dw/server/guides.py +257 -0
- dw/server/jobs.py +1561 -0
- dw/server/mcp_mount.py +95 -0
- dw/server/netinfo.py +124 -0
- dw/server/observed_cost.py +379 -0
- dw/server/sysinfo.py +71 -0
- dw/server/ui/assets/abap-08VXUWAP.js +1 -0
- dw/server/ui/assets/apex-BWPQTe0t.js +1 -0
- dw/server/ui/assets/azcli-Bc_sGQ0U.js +1 -0
- dw/server/ui/assets/bat-i0X4ZdIN.js +1 -0
- dw/server/ui/assets/bicep-B5-_aFwp.js +2 -0
- dw/server/ui/assets/cameligo-DMUM7wLl.js +1 -0
- dw/server/ui/assets/clojure-Cm7r79vr.js +1 -0
- dw/server/ui/assets/codicon-Brq4_Ui5.ttf +0 -0
- dw/server/ui/assets/coffee-Ba7i2nA0.js +1 -0
- dw/server/ui/assets/cpp-C7h46wYY.js +1 -0
- dw/server/ui/assets/csharp-BKxtCVv1.js +1 -0
- dw/server/ui/assets/csp-bTuwJoIa.js +1 -0
- dw/server/ui/assets/css-DIMkf-bt.js +3 -0
- dw/server/ui/assets/css.worker-B3ciXF_0.js +93 -0
- dw/server/ui/assets/cssMode-CPznxfY8.js +1 -0
- dw/server/ui/assets/cypher-CVaqCwHa.js +1 -0
- dw/server/ui/assets/dart-onAF5SnQ.js +1 -0
- dw/server/ui/assets/dockerfile-DZFCIeNp.js +1 -0
- dw/server/ui/assets/ecl-D05T4iGw.js +1 -0
- dw/server/ui/assets/editor-jjEx9u7D.css +1 -0
- dw/server/ui/assets/editor.api-CpWcotrd.js +847 -0
- dw/server/ui/assets/editor.worker-q-txB4vs.js +30 -0
- dw/server/ui/assets/elixir-6RTg0lbw.js +1 -0
- dw/server/ui/assets/flow9-C5_-GSwl.js +1 -0
- dw/server/ui/assets/freemarker2-CXtRM8N4.js +3 -0
- dw/server/ui/assets/fsharp-C8Ef5oNN.js +1 -0
- dw/server/ui/assets/go-C-y9NEjX.js +1 -0
- dw/server/ui/assets/graphql-fmXr3nnJ.js +1 -0
- dw/server/ui/assets/handlebars-N7x-6NMY.js +1 -0
- dw/server/ui/assets/hcl-CpzslTdj.js +1 -0
- dw/server/ui/assets/html-PhsdjHSr.js +1 -0
- dw/server/ui/assets/html.worker-C93Ht9o9.js +506 -0
- dw/server/ui/assets/htmlMode-Dgj0SEok.js +1 -0
- dw/server/ui/assets/index-3Vw6WAPW.css +1 -0
- dw/server/ui/assets/index-DgrYhQd9.js +43 -0
- dw/server/ui/assets/ini-sBoK_t0W.js +1 -0
- dw/server/ui/assets/java-BEtHBSE6.js +1 -0
- dw/server/ui/assets/javascript-BJqN9Qhv.js +1 -0
- dw/server/ui/assets/json.worker-B2V3pomh.js +62 -0
- dw/server/ui/assets/jsonMode-DbM4SWSv.js +7 -0
- dw/server/ui/assets/julia-Bri6UV-V.js +1 -0
- dw/server/ui/assets/kotlin-BOotOW0E.js +1 -0
- dw/server/ui/assets/less-B9JPFI3C.js +2 -0
- dw/server/ui/assets/lexon-CfSJPG6W.js +1 -0
- dw/server/ui/assets/liquid-BWr8lEc4.js +1 -0
- dw/server/ui/assets/lspLanguageFeatures-C1iGuDyZ.js +4 -0
- dw/server/ui/assets/lua-CsQS60Ue.js +1 -0
- dw/server/ui/assets/m3-D-oSqn_W.js +1 -0
- dw/server/ui/assets/markdown-Cimd5fb3.js +1 -0
- dw/server/ui/assets/mdx-DAdMi_0p.js +1 -0
- dw/server/ui/assets/mips-CIPQ_RoX.js +1 -0
- dw/server/ui/assets/monaco--ixms01u.css +1 -0
- dw/server/ui/assets/monaco-BGCeEqaw.js +56 -0
- dw/server/ui/assets/msdax-DauUninz.js +1 -0
- dw/server/ui/assets/mysql-SOo6toE5.js +1 -0
- dw/server/ui/assets/objective-c-FvmIjYaQ.js +1 -0
- dw/server/ui/assets/pascal-DrH0SRf2.js +1 -0
- dw/server/ui/assets/pascaligo-D-ptJ9y-.js +1 -0
- dw/server/ui/assets/perl-oz_6vUea.js +1 -0
- dw/server/ui/assets/pgsql-DTj74zXo.js +1 -0
- dw/server/ui/assets/php-nr791fC2.js +1 -0
- dw/server/ui/assets/pla-CopQ2nXW.js +1 -0
- dw/server/ui/assets/postiats-43DmfD33.js +1 -0
- dw/server/ui/assets/powerquery-D3hlyOfw.js +1 -0
- dw/server/ui/assets/powershell-DmHpPYUd.js +1 -0
- dw/server/ui/assets/protobuf-C531GsRP.js +2 -0
- dw/server/ui/assets/pug-Z5eAx3Zn.js +1 -0
- dw/server/ui/assets/python-Bcn70HdC.js +1 -0
- dw/server/ui/assets/qsharp-DkqhCAOL.js +1 -0
- dw/server/ui/assets/r-BwWrilGY.js +1 -0
- dw/server/ui/assets/razor-D1HmNnby.js +1 -0
- dw/server/ui/assets/redis-ClamHrr6.js +1 -0
- dw/server/ui/assets/redshift-DT7zqm-g.js +1 -0
- dw/server/ui/assets/restructuredtext-BYgofb2h.js +1 -0
- dw/server/ui/assets/ruby-DezsRK8O.js +1 -0
- dw/server/ui/assets/rust-DdL9SqIa.js +1 -0
- dw/server/ui/assets/sb-CcwsVR0C.js +1 -0
- dw/server/ui/assets/scala-DHpiXF5c.js +1 -0
- dw/server/ui/assets/scheme-BeGwcela.js +1 -0
- dw/server/ui/assets/scss-gp-XZpBa.js +3 -0
- dw/server/ui/assets/shell-CC2rA5mh.js +1 -0
- dw/server/ui/assets/solidity-BEEn4gHE.js +1 -0
- dw/server/ui/assets/sophia-CRfGWb83.js +1 -0
- dw/server/ui/assets/sparql-D_Lu-MrJ.js +1 -0
- dw/server/ui/assets/sql-NEE52Syq.js +1 -0
- dw/server/ui/assets/st-DbInun42.js +1 -0
- dw/server/ui/assets/swift-Bxkupp3x.js +1 -0
- dw/server/ui/assets/systemverilog-Bz4Y3fRF.js +1 -0
- dw/server/ui/assets/tcl-DISqw1ZD.js +1 -0
- dw/server/ui/assets/ts.worker-D7T1-Ig5.js +67738 -0
- dw/server/ui/assets/tsMode-D6u0XmOW.js +11 -0
- dw/server/ui/assets/twig-De2hgUGE.js +1 -0
- dw/server/ui/assets/typescript-BU6v-LMV.js +1 -0
- dw/server/ui/assets/typespec-B8J7ngcE.js +1 -0
- dw/server/ui/assets/vb-DV3o63ZY.js +1 -0
- dw/server/ui/assets/wgsl-DpFanUEy.js +298 -0
- dw/server/ui/assets/workers-Cn7cTUKr.js +1 -0
- dw/server/ui/assets/xml--0LP2Lwk.js +1 -0
- dw/server/ui/assets/yaml-mpBg9jnt.js +1 -0
- dw/server/ui/index.html +17 -0
- dw/server/updater.py +192 -0
- dw/settings.py +98 -0
- dw/shot_span_preflight.py +116 -0
- dw/shots.py +359 -0
- dw/slice_preflight.py +148 -0
- dw/step.py +187 -0
- dw/step_cache.py +442 -0
- dw/subfolders.py +107 -0
- dw/task_domains.py +307 -0
- dw/tasks/assess.py +826 -0
- dw/tasks/audio_transcription.py +88 -0
- dw/tasks/audio_utils.py +1862 -0
- dw/tasks/background_remover.py +43 -0
- dw/tasks/borders.py +113 -0
- dw/tasks/compose_text.py +74 -0
- dw/tasks/concat_videos.py +300 -0
- dw/tasks/depth_estimator.py +54 -0
- dw/tasks/diffusion_upscale.py +109 -0
- dw/tasks/dissolve_videos.py +342 -0
- dw/tasks/format_messages.py +24 -0
- dw/tasks/gather.py +173 -0
- dw/tasks/grade.py +97 -0
- dw/tasks/image_to_text.py +43 -0
- dw/tasks/image_utils.py +764 -0
- dw/tasks/interpolate_frames.py +252 -0
- dw/tasks/judge.py +68 -0
- dw/tasks/model_cache.py +55 -0
- dw/tasks/pair_audio.py +268 -0
- dw/tasks/qr_code.py +19 -0
- dw/tasks/restore_faces.py +175 -0
- dw/tasks/rife_model.py +192 -0
- dw/tasks/segment.py +121 -0
- dw/tasks/select.py +111 -0
- dw/tasks/speech_generation.py +228 -0
- dw/tasks/stabilize.py +129 -0
- dw/tasks/task.py +920 -0
- dw/tasks/tensor_image.py +57 -0
- dw/tasks/text_generation.py +169 -0
- dw/tasks/text_sections.py +80 -0
- dw/tasks/upscale.py +203 -0
- dw/tasks/video_utils.py +624 -0
- dw/tasks/zoe_depth.py +71 -0
- dw/teacache.py +381 -0
- dw/teacache_models.json +99 -0
- dw/test.py +29 -0
- dw/type_helpers.py +231 -0
- dw/validate.py +68 -0
- dw/variable_constraints.py +444 -0
- dw/variables.py +443 -0
- dw/video_extensions.py +141 -0
- dw/vram_estimate.py +116 -0
- dw/worker.py +764 -0
- dw/workflow.py +2007 -0
- dw/workflow_schema.json +1346 -0
- dw/workflow_sources.py +383 -0
- dw/workflows/h3_context_ir.json +57 -0
- dw/workflows/test.json +31 -0
- dw/workspace.py +730 -0
- dw_mcp/__init__.py +6 -0
- dw_mcp/__main__.py +133 -0
- dw_mcp/assets.py +336 -0
- dw_mcp/authoring.py +114 -0
- dw_mcp/catalog.py +360 -0
- dw_mcp/client.py +486 -0
- dw_mcp/diagnose.py +371 -0
- dw_mcp/exports.py +84 -0
- dw_mcp/guides.py +35 -0
- dw_mcp/media.py +638 -0
- dw_mcp/models.py +97 -0
- dw_mcp/prompts.py +104 -0
- dw_mcp/server.py +1343 -0
- dw_mcp/workspaces.py +212 -0
dw/assessment_rules.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""The rules the assessment probes read their measurements against (#387).
|
|
2
|
+
|
|
3
|
+
A probe (`dw/tasks/assess.py`) measures a finished file and reports every
|
|
4
|
+
number it took; a rule here names one of those numbers and the threshold
|
|
5
|
+
past which it is worth a look, and a crossing becomes a `finding` in the
|
|
6
|
+
probe's answer. Findings are places to look, not verdicts: nothing in the
|
|
7
|
+
engine acts on one, no run fails for one, and a finding a person has looked
|
|
8
|
+
at and accepted is simply left alone. That is the authority rule, and it is
|
|
9
|
+
why the thresholds sit in one table - a number an agent is told to trust
|
|
10
|
+
has to be one somebody can find, argue with and change in one place.
|
|
11
|
+
|
|
12
|
+
Each entry names the probe that reports the field, the field (a key of the
|
|
13
|
+
probe's per-shot or per-seam record, or of its answer itself -
|
|
14
|
+
`tests/test_assessment_rules.py` pins every one to a real probe field so a
|
|
15
|
+
rename cannot leave a rule reading nothing), how the value is compared,
|
|
16
|
+
the threshold, the severity and what a crossing says. `magnitude` compares
|
|
17
|
+
the value's absolute size, for a signed measurement whose direction is not
|
|
18
|
+
the problem. `shot_level_spread` shares its threshold with the run-time
|
|
19
|
+
`LEVEL_SPREAD_WARN_DB`, so the join-time warning and the after-the-fact
|
|
20
|
+
probe cannot disagree about the same cut.
|
|
21
|
+
|
|
22
|
+
Two rules carry a guard the table alone cannot express, applied by the
|
|
23
|
+
probe and named here in `unless` so it is written down beside the number:
|
|
24
|
+
`seam_hole` holds only while both sides of the seam are voiced, and
|
|
25
|
+
`seam_frame_jump` does not fire at a seam whose incoming shot is marked
|
|
26
|
+
`hard_cut: true` - a cut meant as a cut.
|
|
27
|
+
|
|
28
|
+
The thresholds were settled against real runs on the server in stage D
|
|
29
|
+
(#386); `docs/WORKFLOW_GUIDE.md` and `docs/TASKS.md` quote every one, and
|
|
30
|
+
`tests/test_assessment_rules.py` pins those quotes to this table.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from .tasks.audio_utils import LEVEL_SPREAD_WARN_DB
|
|
34
|
+
|
|
35
|
+
SEVERITIES = ("info", "warn")
|
|
36
|
+
|
|
37
|
+
# How loud both sides of a seam have to be for a quiet join to be a hole
|
|
38
|
+
# rather than a pause the shots themselves hold
|
|
39
|
+
HOLE_VOICED_DBFS = -30.0
|
|
40
|
+
|
|
41
|
+
COMPARATORS = {
|
|
42
|
+
">": lambda value, threshold: value > threshold,
|
|
43
|
+
">=": lambda value, threshold: value >= threshold,
|
|
44
|
+
"<": lambda value, threshold: value < threshold,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
RULES = (
|
|
48
|
+
{
|
|
49
|
+
"name": "shot_level_spread",
|
|
50
|
+
"probe": "analyze_shots",
|
|
51
|
+
"field": "rms_range_db",
|
|
52
|
+
"comparator": ">=",
|
|
53
|
+
"threshold": LEVEL_SPREAD_WARN_DB,
|
|
54
|
+
"severity": "warn",
|
|
55
|
+
"says": "the shots sit this far apart in level - a jump a listener hears at the cut",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "seam_level_step",
|
|
59
|
+
"probe": "analyze_seams",
|
|
60
|
+
"field": "level_step_db",
|
|
61
|
+
"comparator": ">",
|
|
62
|
+
"threshold": 3.0,
|
|
63
|
+
"severity": "warn",
|
|
64
|
+
"says": "the shots either side of the seam sit this far apart in level",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"name": "seam_click",
|
|
68
|
+
"probe": "analyze_seams",
|
|
69
|
+
"field": "click_db",
|
|
70
|
+
"comparator": ">",
|
|
71
|
+
"threshold": 12.0,
|
|
72
|
+
"severity": "warn",
|
|
73
|
+
"says": "the join peaks this far above the audio either side of it - an audible click",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"name": "seam_hole",
|
|
77
|
+
"probe": "analyze_seams",
|
|
78
|
+
"field": "floor_dbfs",
|
|
79
|
+
"comparator": "<",
|
|
80
|
+
"threshold": -50.0,
|
|
81
|
+
"severity": "warn",
|
|
82
|
+
"says": "the track drops out at the join while both sides are voiced",
|
|
83
|
+
"unless": f"either side's rms is at or below {HOLE_VOICED_DBFS} dBFS",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"name": "seam_frame_jump",
|
|
87
|
+
"probe": "analyze_seams",
|
|
88
|
+
"field": "jump_ratio",
|
|
89
|
+
"comparator": ">",
|
|
90
|
+
# Above every intended cut the stage D field runs measured (#386):
|
|
91
|
+
# seven ordinary cuts between generated shots read 9.9-20.3, dissolves
|
|
92
|
+
# about 1.8. At the original 8 the rule fired on every cut
|
|
93
|
+
"threshold": 25.0,
|
|
94
|
+
"severity": "info",
|
|
95
|
+
"says": "the picture changes this many times more across the seam than inside either shot",
|
|
96
|
+
"unless": "the incoming shot is marked hard_cut: true",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"name": "sync_drift",
|
|
100
|
+
"probe": "analyze_sync_drift",
|
|
101
|
+
"field": "end_offset_ms",
|
|
102
|
+
"comparator": ">",
|
|
103
|
+
"threshold": 40.0,
|
|
104
|
+
"severity": "warn",
|
|
105
|
+
"magnitude": True,
|
|
106
|
+
"says": "by this shot's end the audio sits this far off the picture",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"name": "sync_length",
|
|
110
|
+
"probe": "analyze_sync_drift",
|
|
111
|
+
"field": "length_delta_ms",
|
|
112
|
+
"comparator": ">",
|
|
113
|
+
"threshold": 40.0,
|
|
114
|
+
"severity": "warn",
|
|
115
|
+
"magnitude": True,
|
|
116
|
+
"says": "the soundtrack and the picture differ in length by this much",
|
|
117
|
+
},
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
RULES_BY_NAME = {rule["name"]: rule for rule in RULES}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def rules_for(probe):
|
|
124
|
+
"""The rules one probe's measurements are read against, in table order."""
|
|
125
|
+
return [rule for rule in RULES if rule["probe"] == probe]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def crosses(rule, value):
|
|
129
|
+
"""Whether a measured value crosses a rule's threshold. None never does -
|
|
130
|
+
a measurement that could not be taken (a silent window, no soundtrack)
|
|
131
|
+
is not a finding."""
|
|
132
|
+
if value is None:
|
|
133
|
+
return False
|
|
134
|
+
if rule.get("magnitude"):
|
|
135
|
+
value = abs(value)
|
|
136
|
+
return COMPARATORS[rule["comparator"]](value, rule["threshold"])
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def finding(rule, value, at):
|
|
140
|
+
"""A crossing as the probe reports it."""
|
|
141
|
+
return {
|
|
142
|
+
"rule": rule["name"],
|
|
143
|
+
"severity": rule["severity"],
|
|
144
|
+
"at": at,
|
|
145
|
+
"value": value,
|
|
146
|
+
"threshold": rule["threshold"],
|
|
147
|
+
"says": rule["says"],
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
__all__ = [
|
|
152
|
+
"HOLE_VOICED_DBFS",
|
|
153
|
+
"RULES",
|
|
154
|
+
"RULES_BY_NAME",
|
|
155
|
+
"SEVERITIES",
|
|
156
|
+
"crosses",
|
|
157
|
+
"finding",
|
|
158
|
+
"rules_for",
|
|
159
|
+
]
|
dw/assets.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""The asset library: input media a workflow references by name.
|
|
2
|
+
|
|
3
|
+
A workflow's media paths resolve against the workflow file's own directory,
|
|
4
|
+
which means a workflow that reads anything has to keep that thing beside it -
|
|
5
|
+
the reason generated media ends up gitignored inside a source tree. An
|
|
6
|
+
'asset:name' reference is rooted at the asset library instead, the way
|
|
7
|
+
'prompt:name' is rooted at the prompt library, so the same reference means the
|
|
8
|
+
same file from every workflow and neither has to live next to the other.
|
|
9
|
+
|
|
10
|
+
A reference resolves to a path, not to a value: 'asset:frames/iris.jpg'
|
|
11
|
+
becomes the absolute path of that file, and whatever would have loaded a path
|
|
12
|
+
written there loads it unchanged.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import contextvars
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
|
|
19
|
+
from .security import validate_asset_reference, validate_path
|
|
20
|
+
from .workspace import ASSETS_SUBDIR, discover_library, library_fallbacks
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger("dw")
|
|
23
|
+
|
|
24
|
+
# The prefix marking a value as a reference to a stored asset
|
|
25
|
+
ASSET_PREFIX = "asset:"
|
|
26
|
+
|
|
27
|
+
# Set by an entry point from --asset-dir, and inherited by a spawned worker,
|
|
28
|
+
# the way DW_PROMPT_DIR is
|
|
29
|
+
ASSET_DIR_ENV_VAR = "DW_ASSET_DIR"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# The asset library of the run in progress. A server holds several
|
|
33
|
+
# workspaces and each has its own assets, so this cannot be a process-wide
|
|
34
|
+
# environment variable there the way the prompt library can - there is one
|
|
35
|
+
# prompt library, shared, but assets belong to a workspace. Set per job by
|
|
36
|
+
# the worker; unset for the CLI and REPL, which have one workspace per
|
|
37
|
+
# process and read the environment below
|
|
38
|
+
_active_asset_dir = contextvars.ContextVar("dw_asset_dir", default=None)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def activate_asset_dir(directory):
|
|
42
|
+
"""Make an asset library the active one; returns a token for deactivate."""
|
|
43
|
+
return _active_asset_dir.set(directory)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def deactivate_asset_dir(token):
|
|
47
|
+
_active_asset_dir.reset(token)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_asset_dir(base_dir=None):
|
|
51
|
+
"""The directory 'asset:' references are rooted at.
|
|
52
|
+
|
|
53
|
+
A library activated for this run wins outright - that is the server
|
|
54
|
+
telling the worker which workspace's assets this job uses. Otherwise
|
|
55
|
+
discovery mirrors the prompt library's - see workspace.discover_library
|
|
56
|
+
for the shared precedence (DW_ASSET_DIR, then a named workspace, then
|
|
57
|
+
./assets, then a walk up from base_dir, then the workspace's assets/ as
|
|
58
|
+
the fallback).
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
base_dir: The workflow file's directory, when one anchors the search
|
|
62
|
+
"""
|
|
63
|
+
active = _active_asset_dir.get()
|
|
64
|
+
if active:
|
|
65
|
+
return active
|
|
66
|
+
|
|
67
|
+
return discover_library(ASSETS_SUBDIR, ASSET_DIR_ENV_VAR, base_dir)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def is_asset_reference(value):
|
|
71
|
+
"""Whether a value references a file in the asset library."""
|
|
72
|
+
return isinstance(value, str) and value.startswith(ASSET_PREFIX)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def asset_search_path(asset_dir=None, base_dir=None):
|
|
76
|
+
"""Every directory an 'asset:' reference is looked for in, in order.
|
|
77
|
+
|
|
78
|
+
The workspace's own library first, then the read-only ones an entry
|
|
79
|
+
point put on the path (workspace.library_fallbacks - the assets a
|
|
80
|
+
--examples-dir tree brings with it), so an example workflow reaches the
|
|
81
|
+
media it ships with while an upload still lands in the workspace.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
asset_dir: The first directory; defaults to get_asset_dir()
|
|
85
|
+
base_dir: The workflow file's directory, anchoring discovery when no
|
|
86
|
+
asset directory is configured
|
|
87
|
+
"""
|
|
88
|
+
primary = asset_dir or get_asset_dir(base_dir)
|
|
89
|
+
return [primary] + library_fallbacks(ASSETS_SUBDIR, primary)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def resolve_asset_reference(reference, asset_dir=None, base_dir=None):
|
|
93
|
+
"""Resolve an 'asset:' reference to the file it names.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
reference: The 'asset:name.ext' or 'asset:folder/name.ext' string
|
|
97
|
+
asset_dir: Directory the name is rooted at; defaults to get_asset_dir()
|
|
98
|
+
base_dir: The workflow file's directory, anchoring discovery when no
|
|
99
|
+
asset directory is configured
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
The validated absolute path of the asset file
|
|
103
|
+
|
|
104
|
+
Raises:
|
|
105
|
+
InvalidInputError: If the name is not a valid asset name
|
|
106
|
+
PathTraversalError: If the name escapes the asset directory
|
|
107
|
+
ValueError: If no file exists under that name in any directory on
|
|
108
|
+
the search path
|
|
109
|
+
"""
|
|
110
|
+
name = validate_asset_reference(reference.removeprefix(ASSET_PREFIX).strip())
|
|
111
|
+
roots = asset_search_path(asset_dir, base_dir)
|
|
112
|
+
for root in roots:
|
|
113
|
+
# Confined to the library it was found in: the name is joined onto a
|
|
114
|
+
# directory, so the containment check is what makes a name a name
|
|
115
|
+
# rather than a path
|
|
116
|
+
path = validate_path(os.path.join(root, name), root)
|
|
117
|
+
if os.path.isfile(path):
|
|
118
|
+
logger.debug(f"Resolved {reference} to {path}")
|
|
119
|
+
return path
|
|
120
|
+
searched = ", ".join(roots)
|
|
121
|
+
raise ValueError(
|
|
122
|
+
f"Asset '{name}' not found in {searched} - an 'asset:' reference "
|
|
123
|
+
f"names a file in the asset library, with its extension, like "
|
|
124
|
+
f"'asset:iris.jpg' or 'asset:gyre/frame_1.jpg'"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def fetch_asset(reference, asset_dir=None, base_dir=None):
|
|
129
|
+
"""The path an 'asset:' reference names, for whatever loads paths."""
|
|
130
|
+
return resolve_asset_reference(reference, asset_dir, base_dir)
|
dw/cache_blocks.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "Transformer block metadata for diffusers' cache hooks. first_block, mag and layer_skip look their block class up in diffusers.hooks._helpers.TransformerBlockRegistry and raise if it is missing. Blocks listed here are registered on demand so those cache types work on models diffusers has not registered upstream. Fields mirror diffusers.hooks._helpers.TransformerBlockMetadata, plus dw's own encoder_hidden_states_argument_name. Verify against the block's forward before adding an entry: return_hidden_states_index is the position of hidden_states in the returned tuple, and is ignored when the block returns a bare tensor; return_encoder_hidden_states_index is null when the block does not return a second stream; hidden_states_argument_name must name the forward parameter carrying hidden_states; encoder_hidden_states_argument_name names the forward parameter the second returned stream comes from, and is only needed when that is not literally 'encoder_hidden_states' - see dw/cache_blocks.py.",
|
|
3
|
+
"blocks": {
|
|
4
|
+
"diffusers.models.transformers.transformer_minimax_h3.MiniMaxH3TransformerBlock": {
|
|
5
|
+
"return_hidden_states_index": 0,
|
|
6
|
+
"return_encoder_hidden_states_index": null,
|
|
7
|
+
"hidden_states_argument_name": "hidden_states"
|
|
8
|
+
},
|
|
9
|
+
"diffusers.models.transformers.transformer_ltx2.LTX2VideoTransformerBlock": {
|
|
10
|
+
"return_hidden_states_index": 0,
|
|
11
|
+
"return_encoder_hidden_states_index": 1,
|
|
12
|
+
"hidden_states_argument_name": "hidden_states",
|
|
13
|
+
"encoder_hidden_states_argument_name": "audio_hidden_states"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
dw/cache_blocks.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Register transformer block metadata diffusers' cache hooks are missing.
|
|
2
|
+
|
|
3
|
+
The first_block, mag and layer_skip cache types look a model's transformer block
|
|
4
|
+
class up in diffusers.hooks._helpers.TransformerBlockRegistry and raise
|
|
5
|
+
ValueError when it is absent, so those caches are unavailable on any model
|
|
6
|
+
diffusers has not registered upstream - even though the model itself supports
|
|
7
|
+
enable_cache(). The blocks in cache_blocks.json fill that gap; entries become
|
|
8
|
+
redundant, not wrong, once diffusers registers the same class itself.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import logging
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from .type_helpers import load_type_from_name
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger("dw")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
_REGISTRY_PATH = Path(__file__).parent / "cache_blocks.json"
|
|
21
|
+
|
|
22
|
+
# Registration walks every entry, so do it once rather than per pipeline load
|
|
23
|
+
_registered = False
|
|
24
|
+
|
|
25
|
+
# The registry field that is dw's own rather than one of TransformerBlockMetadata's -
|
|
26
|
+
# see _dual_stream_metadata_class for what it is for
|
|
27
|
+
ARGUMENT_REMAP_KEY = "encoder_hidden_states_argument_name"
|
|
28
|
+
|
|
29
|
+
# One subclass per metadata class, built on first use - the class it derives from
|
|
30
|
+
# only exists once diffusers has been imported
|
|
31
|
+
_dual_stream_classes = {}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _load_registry():
|
|
35
|
+
"""Load the block metadata registry from the JSON file."""
|
|
36
|
+
with open(_REGISTRY_PATH) as f:
|
|
37
|
+
return json.load(f).get("blocks", {})
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _dual_stream_metadata_class(metadata_class):
|
|
41
|
+
"""A metadata class that can name the block's second stream itself.
|
|
42
|
+
|
|
43
|
+
A block that returns two streams has the second one read back out of its
|
|
44
|
+
forward arguments when the cache skips it, and diffusers looks that argument
|
|
45
|
+
up under the fixed name 'encoder_hidden_states' - the only two-stream shape it
|
|
46
|
+
registers upstream is text beside image. LTX-2's blocks return video beside
|
|
47
|
+
*audio* while also taking an 'encoder_hidden_states' of their own (the text
|
|
48
|
+
conditioning), so the fixed name silently reads the wrong tensor and feeds the
|
|
49
|
+
text embeddings back as the audio stream. Remapping the identifier is what
|
|
50
|
+
makes first_block caching correct on those blocks rather than merely quiet.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
metadata_class: diffusers' TransformerBlockMetadata
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
A subclass reading the second stream from the argument the entry names
|
|
57
|
+
"""
|
|
58
|
+
if metadata_class not in _dual_stream_classes:
|
|
59
|
+
|
|
60
|
+
class DualStreamMetadata(metadata_class):
|
|
61
|
+
# Overridden per instance from the registry entry; the default keeps
|
|
62
|
+
# the class behaving exactly like the one it derives from
|
|
63
|
+
encoder_hidden_states_argument_name = "encoder_hidden_states"
|
|
64
|
+
|
|
65
|
+
def _get_parameter_from_args_kwargs(self, identifier, args=(), kwargs=None):
|
|
66
|
+
if identifier == "encoder_hidden_states":
|
|
67
|
+
identifier = self.encoder_hidden_states_argument_name
|
|
68
|
+
return super()._get_parameter_from_args_kwargs(identifier, args, kwargs)
|
|
69
|
+
|
|
70
|
+
_dual_stream_classes[metadata_class] = DualStreamMetadata
|
|
71
|
+
|
|
72
|
+
return _dual_stream_classes[metadata_class]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def build_metadata(metadata_class, entry):
|
|
76
|
+
"""Build the metadata object one registry entry describes.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
metadata_class: diffusers' TransformerBlockMetadata
|
|
80
|
+
entry: One block's fields from cache_blocks.json
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
An instance of metadata_class, or of the dual-stream subclass when the
|
|
84
|
+
entry names the argument its second returned stream comes from
|
|
85
|
+
"""
|
|
86
|
+
remapped = entry.get(ARGUMENT_REMAP_KEY)
|
|
87
|
+
fields = {k: v for k, v in entry.items() if k != ARGUMENT_REMAP_KEY}
|
|
88
|
+
|
|
89
|
+
if remapped is None:
|
|
90
|
+
return metadata_class(**fields)
|
|
91
|
+
|
|
92
|
+
metadata = _dual_stream_metadata_class(metadata_class)(**fields)
|
|
93
|
+
metadata.encoder_hidden_states_argument_name = remapped
|
|
94
|
+
return metadata
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def register_cache_blocks():
|
|
98
|
+
"""Register any known-missing transformer blocks with diffusers.
|
|
99
|
+
|
|
100
|
+
Safe to call repeatedly and before any cache type - blocks diffusers already
|
|
101
|
+
knows are left alone, and a block whose class the installed diffusers does
|
|
102
|
+
not have is skipped rather than raising.
|
|
103
|
+
"""
|
|
104
|
+
global _registered
|
|
105
|
+
if _registered:
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
from diffusers.hooks._helpers import (
|
|
110
|
+
TransformerBlockMetadata,
|
|
111
|
+
TransformerBlockRegistry,
|
|
112
|
+
)
|
|
113
|
+
except ImportError:
|
|
114
|
+
# A diffusers without the cache hooks has nothing to register against
|
|
115
|
+
logger.debug(
|
|
116
|
+
"diffusers cache hook helpers unavailable, skipping block registration"
|
|
117
|
+
)
|
|
118
|
+
_registered = True
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
for class_name, metadata in _load_registry().items():
|
|
122
|
+
try:
|
|
123
|
+
# A server-owned registry name, registered, never constructed
|
|
124
|
+
block_class = load_type_from_name(class_name, constructed=False)
|
|
125
|
+
except (ImportError, AttributeError):
|
|
126
|
+
# The installed diffusers predates this model - its blocks are not
|
|
127
|
+
# missing from the registry, they do not exist
|
|
128
|
+
logger.debug(f"Cache block '{class_name}' not in this diffusers, skipping")
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
# TransformerBlockRegistry.get raises rather than returning None, and an
|
|
132
|
+
# upstream registration is the more authoritative one either way
|
|
133
|
+
try:
|
|
134
|
+
TransformerBlockRegistry.get(block_class)
|
|
135
|
+
logger.debug(f"Cache block '{class_name}' already registered, leaving it")
|
|
136
|
+
continue
|
|
137
|
+
except ValueError:
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
logger.debug(f"Registering cache block metadata for '{class_name}'")
|
|
141
|
+
TransformerBlockRegistry.register(
|
|
142
|
+
model_class=block_class,
|
|
143
|
+
metadata=build_metadata(TransformerBlockMetadata, metadata),
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
_registered = True
|