# Dials (alpha) Training-free steering for MiniMax H3 (also MOSS-Image and Krea 2), made from prompt pairs. Write two prompts that differ in one quality (*"stands perfectly still"* vs *"runs across the scene"*); Make Dial turns the difference into a ~14 KB file that works like a strength slider. Negative strength moves every generation toward the first prompt, positive toward the second. Ported from [ComfyUI-Dials](https://github.com/matlowai/ComfyUI-Dials); this is where new Dials work happens. **Research:** open, in-progress experiments live in [`dials/research/`](research/README.md): the running plan, one page per experiment, the scripts, raw logs, and [word dials](research/word_dials/README.md) made from four words with the Jacobian lens. Clips: [Dials Lab](https://matlowai.github.io/ComfyUI-Dials/lab.html), [Word Dials](https://matlowai.github.io/ComfyUI-Dials/words.html), [Dials on a First Frame](https://matlowai.github.io/ComfyUI-Dials/firstframe.html). ## Nodes (category MAINodes/Dials) | node | what it does | |---|---| | **Dial Rack** | Any number of dials on one model. One row per dial: on/off, dial, strength (slider and number), solo, **H** to hold a direction. **+ Add dial** adds a row; dials wired into the `dial_N` inputs (straight from Make Dial) appear as rows too. After a run the node shows each dial's size, the combined shift, and dials that overlap (reinforce or cancel). A **Map** moves two rows together on a 2D square (the dot is your prompt), with saved pairings to switch between. | | **Make Dial** | Prompt pairs (`A ||| B` per line, `{subject}` expanded over a subjects list) to a dial. Saves to `models/loras/dials/.safetensors` and outputs the dial for a rack input. Shows pair agreement (above 0.6 is a clean direction) and how much generic wording shift was removed. Also takes weighted pairs (`A ||| B :: 2`) and prompt sets (`+1 …` / `-1 …` lines). | | **Dial Hold Map** | De-rope with one rack: takes the Dial Rack's model and H3 Time Smear's `hold_map_used` and gives stage 2 the same rack with its time curves in real clip time. Rows without a time curve pass through unchanged. | | **Combine Dials** | `weight_a * a + weight_b * b` as one new dial, optionally removing b's direction from a first (orthogonalize). Plain mixing is easier in the rack. | | **Make Dial from Pictures** | Two picture groups (batches, any number of single pictures of any size, and/or a folder inside `input/` on `folder_a` / `folder_b`) and a shared framing to a post-refiner dial. Negative strength moves toward A, positive toward B. Equal counts pair by index for agreement; unequal counts use group means and report agreement as n/a. Saves and connects to the rack like Make Dial. | | **Render Preset** | The Simple front door's render recipe in one node, switched together by named presets: model, LoRA and strength, flow shifts (video / audio), FL2V or Ref2V, sampler, the stage-1 schedule (a manual sigma list or a scheduler) and steps, de-rope steps, cfg. Built in: *Pika H3 Flex*, *Pika + reference pictures*, *DMAD speed LoRA*; **Save as** stores yours in `models/loras/dials/presets/render/`. | | **H3 De-rope Sigmas** | The de-rope pass's schedule from the active preset's: starts where a fraction `inject` of the denoise is left (continuous, on the model's shift), then the stage-1 sigmas below it; `steps` > 0 resamples that tail. | | **Make Dial from Videos** | Matched clips (up to 12 per side) and one shared prompt to a post-refiner dial; clips are sampled at 2 fps and cut to equal length. Experimental. | **Strength:** +0.5 to +1 is the everyday range. Past about 1.5 side effects appear (the scene restages, characters morph), and on Pika H3 Flex's 6-step schedule even sooner. The motion dial at −2 and +2 shows the extremes. **Rack = chain without holds or envelopes:** the rack applies its rows in order with exactly the patches a chain of single-dial nodes makes, so one rack is bit-for-bit the same as that chain (`tests/test_dial_rack.py`). Off rows, zero-strength rows and non-solo rows while something is soloed change nothing; a rack with nothing active returns the model untouched. **Hold rows:** toggle **H** to remove that dial's direction from the sum of the rack's active post-refiner shifts. Hold directions are orthonormalized together before projection, then the rack installs one combined post shift. Duplicate or zero directions add no constraint. Holds follow on/off and solo; their strength is ignored (even zero), and the disabled strength controls retain the value for when H is turned off. A rack containing only holds changes nothing. Pre-refiner dials cannot be hold rows; ordinary pre-refiner rows still apply as usual. Holds affect this rack's post shift, including cancellation between its rows; they do not remove upstream patches or guarantee that H3 leaves the corresponding visual/audio quality unchanged. The report gives the remaining shift and each hold's share of the original summed shift's squared norm removed, crediting overlapping holds in row order. Hold rows and explicit step envelopes cannot share a rack; use constant rows with no `envelope` field for holds. **Dial files** live in `models/loras/dials/`. The dial library is on Hugging Face: [MATLOWAI/H3-Dials](https://huggingface.co/MATLOWAI/H3-Dials). Folders starting with `_` are not listed (park retired dials there). The file format, the folder and the DIAL socket type are shared with ComfyUI-Dials, so both packs can be installed side by side (node ids differ: `MAIDial*` here, `Dials*` there) and use each other's dials. Two kinds of dial file: - **hook** (`post_refiner`, the default): added after H3's token refiner, where the DiT reads the prompt. The most faithful; use these in the rack. - **LoRA file** (`pre_refiner`): a `condition_proj` bias diff the stock *Load LoRA (model only)* node also applies. Only an approximation of the hook; above ~1 it starts to wash out the prompt. **Model families:** the nodes read the family from the MODEL input; there is nothing to choose. | family | post seam (hook) | pre seam (LoRA file) | |---|---|---| | MiniMax H3 | `token_refiner` output | `condition_proj` bias | | MOSS-Image | `text_embedder` output (after its two text blocks) | `text_embedder.proj` bias | | Krea 2 | `txtmlp` output | none: `txtfusion` has no biased projection up front | Make Dial, Dial Rack, Combine Dials, holds and step envelopes work on all three. Make Dial from Pictures / Videos and the rack's probe-bank wheel are H3 only, and say so on other models. Each dial file records its family (`model_family` metadata), and a dial made for one family fails on another with a short error. Files without the field are H3 dials. Strength ranges differ by family. On MOSS-Image (SFT DeBand, cfg 3), text dials need about ±4 for a clear effect and overcook near ±8. On Krea 2 (Raw, cfg 4), the post seam is not clean: only the "toward A" side of day/night and weather worked (to about −4), and colour grades saturate and break. **Rack presets:** use **Save preset** / **Load preset** in the rack bar. Presets live in `models/loras/dials/presets/.json`, with format `MAIDialRack preset`, version `1`, and ordered rows containing `dial`, `sha256`, `strength`, `on`, and `solo`, plus optional `hold` (absent means false), `envelope`, `blocks` and `time`. They store file references and settings, never weights. Names accept letters, digits, `_` and `-` (1–80 characters); saving requires a new name. Loading warns about missing files or changed SHA-256 hashes and keeps every row, including off/solo settings; absent files remain marked **(missing)**. Presets use saved files: save wired dials and select their files before saving a preset, and disconnect wired dials before loading one. Save under a new name to accept changed files' current hashes. ## The map: drag your prompt **Map** in the rack bar opens a square to the right of the rows: two rows (X and Y) move together and the dot is your prompt, so dragging it sets both strengths. The rows and buttons keep their size; only the map follows the node when you resize it. **Pairings** are the chips above the map: each remembers an X/Y pair of rows, clicking one switches the map to that pair (the dot jumps to their current strengths), **+ pairing** adds one and ✕ removes the active one. **Shift+drag** moves at a fifth of the speed for careful nudging, arrow keys step by 0.05 (Shift: 0.25), and a double-click puts both back to 0. Two rings come from the dials' own info (below): the inner one is each dial's useful strength, the outer one where it overcooks, sized per axis and per side, and the range (auto by default) leaves room for the largest known overcook. Axis ends show what - and + give. The readout gives both strengths, whether the dot is within useful, past useful or overcooked, how much the two dials overlap (cosine), and the combined push in units of "one dial at +1", measured from the dial files. **Sweep 3×3** queues nine renders on a grid around the dot (spacing set by *step*) and then puts the dot back. Map settings are saved with the workflow and never reach the backend; the map only edits the rows' strengths. Wired `dial_N` rows can be axes but have no file to measure. ## Dial info: description, labels, useful and overcook strengths Every Make Dial node has fields for a **description**, what **negative** and **positive** strength give (e.g. "stoic" / "lively"), the **useful** strength (where the effect is clearly there) and the **overcook** strength (where side effects take over; 0 = unknown). The negative side uses the same strengths unless **negative_useful** / **negative_overcook** say otherwise (0 = same as positive), because many dials work on one side only. **evidence** says where the numbers come from (creator, one-seed, multi-seed, multi-scene or unknown) and **pipeline** what they were measured on (e.g. "Pika H3 Flex 6-step"). They are saved inside the dial file's metadata (`dial_info`, version 2), so they travel with the `.safetensors`. In the rack, **⚙** on a row edits a dial's info, where an empty strength means unknown and **notes** records failures, and **Save to dial file** writes it back into that file: only the metadata changes, the vector stays byte-identical, and presets keep matching (they hash the vector). The description is the dial picker's tooltip. The map draws each quarter from the matching sides (right/top from +, left/bottom from −); a quarter with an unknown side shows a grey dashed ring at 1 marked *uncalibrated*, and the dot there reads "uncalibrated", never "within useful". Dials without info are all unknown. ## Example workflows ### Simple start Open [dials_simple_pika_derope.json](../examples/dials/dials_simple_pika_derope.json) ([api](../examples/dials/dials_simple_pika_derope_api.json)): core ComfyUI and MAINodes only. With KJNodes installed, [dials_simple_pika_derope_kj.json](../examples/dials/dials_simple_pika_derope_kj.json) ([api](../examples/dials/dials_simple_pika_derope_kj_api.json)) is the same front door plus KJ's two MiniMax H3 VRAM savers on the model (chunked feed-forward, head-chunked attention; same math) and KJ's Load Images From Folder in the picture-dial group. ![Simple front door](docs/simple_front_door.jpg) 1. **Render Preset** (left): pick a preset in its list. A preset is a whole render recipe and switching changes all of it together: model, LoRA, flow shifts, FL2V or Ref2V, **sampler**, the **stage-1 schedule and steps**, de-rope steps and cfg. *Pika H3 Flex* is `lcm` on Pika's six manual sigmas; *DMAD speed LoRA* is `lcm` on the `simple` schedule, 6 steps at LoRA 0.8 with audio shift 2 (the DMAD authors' sampler and shifts, the community's strength and steps); *Pika + reference pictures* is Pika with Ref2V. In our [turbo comparison](https://matlowai.github.io/flipbook/turbo.html) (12 prompts, 2 seeds) Pika H3 Flex had the cleaner audio and more detail; DMAD's audio floor ran 7-8 dB noisier. To keep your own, change the fields and **Save as** a new name. Set the sampler and schedule together with the LoRA: a distilled LoRA only works on the schedule it was distilled for. 2. **Dial Rack**: tick a dial, drag its strength (about 1 is everyday, 1.5 shows side effects). Each row shows **steps** (strength over the denoising steps: constant, early, late, bell, manual) and **clip** (strength over the clip's time; click the little curve to draw it; the day–night row ships with a ramp). **⋯** holds block strengths, dial info, the dial editor, solo and hold. 3. **Dials - H3 + De-rope** (the blueprint): prompt, width, height, length, seed. FL2V takes an optional **first frame**, Ref2V any number of references through the **H3 References** node on the canvas (up to 9 pictures, 3 videos with their soundtracks, 3 audio clips; call them ``, `