Initial release
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
# HOT-Step Community Plugins
|
||||
|
||||
Drop custom Lua plugin files here to extend the engine without rebuilding.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
plugins/
|
||||
├── solvers/ ← Custom ODE/SDE solvers
|
||||
├── schedulers/ ← Custom noise schedules
|
||||
└── guidance/ ← Custom guidance modes
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Place `.lua` files in the appropriate subdirectory
|
||||
2. Restart the engine (or the app)
|
||||
3. Your plugin appears in the UI dropdown automatically
|
||||
|
||||
The engine scans `engine/plugins/` (built-in) first, then this `plugins/` directory.
|
||||
Duplicate names are skipped with a console warning.
|
||||
|
||||
## Writing a Plugin
|
||||
|
||||
Every plugin is a single `.lua` file that returns a table with metadata and a `step()` function.
|
||||
|
||||
### Solver Example
|
||||
|
||||
```lua
|
||||
return {
|
||||
name = "my_solver",
|
||||
display = "My Custom Solver",
|
||||
type = "solver",
|
||||
nfe = 1,
|
||||
accent = "pink",
|
||||
|
||||
-- Optional user-facing parameters
|
||||
params = {
|
||||
{ key = "strength", type = "slider", label = "Strength",
|
||||
default = 0.5, min = 0, max = 1, step = 0.01 },
|
||||
},
|
||||
|
||||
step = function(x, v, t, t_next, dt, params)
|
||||
-- x: current latent (FloatArray)
|
||||
-- v: velocity prediction (FloatArray)
|
||||
-- t, t_next, dt: timestep scalars
|
||||
-- params: table of user values { strength = "0.5", ... }
|
||||
for i = 0, x:size() - 1 do
|
||||
x:set(i, x:get(i) + dt * v:get(i))
|
||||
end
|
||||
end,
|
||||
}
|
||||
```
|
||||
|
||||
### Scheduler Example
|
||||
|
||||
```lua
|
||||
return {
|
||||
name = "my_schedule",
|
||||
display = "My Schedule",
|
||||
type = "scheduler",
|
||||
|
||||
schedule = function(n_steps, params)
|
||||
-- Return a table of n_steps+1 descending floats from 1.0 to 0.0
|
||||
local ts = {}
|
||||
for i = 0, n_steps do
|
||||
ts[i + 1] = 1.0 - i / n_steps
|
||||
end
|
||||
return ts
|
||||
end,
|
||||
}
|
||||
```
|
||||
|
||||
### Guidance Example
|
||||
|
||||
```lua
|
||||
return {
|
||||
name = "my_guidance",
|
||||
display = "My Guidance",
|
||||
type = "guidance",
|
||||
|
||||
guide = function(cond, uncond, scale, t, params)
|
||||
-- cond/uncond: FloatArray (conditional/unconditional predictions)
|
||||
-- scale: guidance scale (number)
|
||||
-- t: current timestep (0→1)
|
||||
-- Return guided prediction in cond (modified in-place)
|
||||
for i = 0, cond:size() - 1 do
|
||||
local c = cond:get(i)
|
||||
local u = uncond:get(i)
|
||||
cond:set(i, u + scale * (c - u))
|
||||
end
|
||||
end,
|
||||
}
|
||||
```
|
||||
|
||||
## Full-Loop Solvers
|
||||
|
||||
For advanced solvers that need to control the entire sampling iteration
|
||||
(e.g., adaptive dispatch, velocity caching, SDE restarts), set
|
||||
`owns_loop = true` and define a `sample()` function instead of `step()`.
|
||||
|
||||
### Full-Loop Solver Example
|
||||
|
||||
```lua
|
||||
solver = {
|
||||
name = "my_sampler",
|
||||
display = "My Sampler",
|
||||
nfe = 0, -- varies per step
|
||||
order = 1,
|
||||
stateful = true,
|
||||
owns_loop = true, -- takes over the sampling loop
|
||||
|
||||
params = {
|
||||
{ key = "my_param", type = "slider", label = "My Param",
|
||||
default = 0.5, min = 0, max = 1, step = 0.01 },
|
||||
},
|
||||
}
|
||||
|
||||
function sample(xt, vt_buf, schedule, n, model_fn)
|
||||
-- xt: FloatArray [n], mutable. Contains noise initially.
|
||||
-- vt_buf: FloatArray [n], mutable. model_fn writes velocity here.
|
||||
-- schedule: Lua table {t_1, t_2, ..., t_N} (1-indexed, N = num_steps)
|
||||
-- n: total element count
|
||||
-- model_fn: function(xt_array, t_val) → writes velocity to vt_buf
|
||||
--
|
||||
-- Globals: on_step, num_steps, batch_n, n_per, params
|
||||
--
|
||||
-- Contract:
|
||||
-- 1. Call model_fn(xt, t) to evaluate the model at any timestep
|
||||
-- 2. Read velocity from vt_buf after model_fn returns
|
||||
-- 3. After each step: call on_step(step_idx, t_curr, t_next) → bool
|
||||
-- Returns true if generation was cancelled (you should return)
|
||||
-- 4. When done, xt must contain the denoised output (x0)
|
||||
|
||||
local ns = #schedule
|
||||
for i = 1, ns do
|
||||
local t_curr = schedule[i]
|
||||
|
||||
-- Evaluate model
|
||||
model_fn(xt, t_curr)
|
||||
|
||||
if i < ns then
|
||||
-- Euler step (replace with your solver logic)
|
||||
local t_next = schedule[i + 1]
|
||||
local dt = t_curr - t_next
|
||||
for j = 0, n - 1 do
|
||||
xt[j] = xt[j] - vt_buf[j] * dt
|
||||
end
|
||||
-- Report step completion (engine applies DCW, repaint, etc.)
|
||||
if on_step(i - 1, t_curr, t_next) then return end
|
||||
else
|
||||
-- Final step: predict x0
|
||||
for j = 0, n - 1 do
|
||||
xt[j] = xt[j] - vt_buf[j] * t_curr
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
> **Note:** `on_step()` applies engine corrections (DCW, repaint, guidance
|
||||
> post-step) automatically. You don't need to handle these yourself.
|
||||
|
||||
## Parameter Types
|
||||
|
||||
| Type | Fields |
|
||||
|----------|--------------------------------------------------|
|
||||
| `slider` | `key`, `label`, `default`, `min`, `max`, `step` |
|
||||
| `select` | `key`, `label`, `default`, `options` |
|
||||
| `toggle` | `key`, `label`, `default` |
|
||||
| `text` | `key`, `label`, `default`, `hint` |
|
||||
|
||||
## Safety
|
||||
|
||||
Plugins run in a sandboxed Lua environment:
|
||||
- ❌ No `os`, `io`, `debug`, `dofile`, `loadfile`
|
||||
- ✅ `math`, `string`, `table`, `require` (for companion data files)
|
||||
- ✅ Full `FloatArray` API for zero-copy memory access
|
||||
|
||||
## Sharing Plugins
|
||||
|
||||
Share your `.lua` files with other HOT-Step users! Just drop them in the right folder.
|
||||
Reference in New Issue
Block a user