The equivalent of SDImg2ImgPipeline for DDPO: modifying DDPOTrainer to support image inputs in addition to text prompts
Project description
imageddpo
The equivalent of SDImg2ImgPipeline for DDPO: modifying DDPOTrainer to support image inputs in addition to text prompts.
All credit goes to the DDPO Implementation from HuggingFace TRL (now deprecated) and the DDPO paper.
I also found Dr. Tanishq Abraham's blog to be incredibly helpful.
Installation
Install from source
git clone https://github.com/hectorastrom/imageddpo.git
cd imageddpo
pip install -e .
Install as a package
pip install imageddpo
Example Usage
For a complete example, please refer to the gaussian glasses repo and website.
There, you will see:
- How a distributed training loop is set up in
rl/rl_trainer.py - How a reward function can be defined in
rl/reward.py - How ImageDDPOTrainer can be used for image decorruption or revealing camouflaged animals.
Birds-Eye Mechanics of Trainer (DDPO and ImageDDPO)
- Initialize trainer with pipeline, scheduler, reward, config.
- Sample trajectories by running the diffusion process and recording actions + log-probs.
- Decode final latents to images.
- Compute rewards for each sample, only at the final x_0 state.
- Normalize advantages globally or per-prompt.
- Compute PPO loss using replayed log-probs + current policy.
- Update UNet via LoRA, keeping scheduler + VAE fixed.
- Repeat for many epochs (sample → PPO → update).
Conceptual Changes from DDPO
- Context
c = (text_prompt, input_image) - Inital state is no longer pure noise, but some noisy version of
input_image - Timestep is no longer from
t=1000 -> t=0, but fromt=(1000 * noise_strength) -> t=0- e.g. if
noise_strength=0.4we're denoising fromt=400 -> t=0
- e.g. if
Implementation Changes from DDPO
- New pipeline subclass
Img2ImgDDPOStableDiffusionPipeline(DDPOStableDiffusionPipeline)- Image encoder + partial forward noising to x_{s*t} + denoising back to x_0
- Key hyperparam is
noise_strength := sranging from[0, 1]
- New prompt function yielding
(init_images, text_prompts, metadata)- NOTE: order of prompts & images is backwards in my implementation
- New reward function (depending on use case) accepts
(init_images, text_prompts,metadatas)
Hacks, Tips, and Patches to Make This Work
- accelerate
save_statemonkey patch- Possible origin: version mismatch between accelerate & old TRL library
- Separate debug_hook to log val images during training
- Disabling CFG on null prompt (a common prompt when using images)
- Incorporating some image hash / id to fix per_prompt stat tracking
- To use LoRA, just enable
use_lorain I2I pipeline (which inherits fromDefaultDDPOStableDiffusionPipeline) which uses default settings on UNet- Namely,
r=4, lora_alpha=4, init_lora_weights="gaussian", target_modules=["to_k", "to_q", "to_v", "to_out.0"]
- Namely,
Todo
[x] Confirm each fundamental implementation change is implemented [x] Check files for redundancy or error-prone rewriting (e.g. rewriting denoising instead of using an Img2Img pipeline) [x] Remove unclear or ambiguous sections - possible relics from stiching things together [ ] Point to example usage and blog in diffusion-lens repo
More Detailed Diffs from DDPOTrainer (courtesy of GPT5.1)
High-level conceptual changes (DDPOTrainer → ImageDDPOTrainer)
-
Treat the input image as part of the environment state
prompt_fnwas extended to return(prompt, image, metadata)instead of just text.- The image is encoded through the VAE to latents and scaled, so the initial state for RL is
x_0derived from the image, not pure Gaussian noise.
-
**Switch from full text-to-image sampling to image-to * Runs only a suffix of the diffusion schedule starting from a chosen noise level.ne` were introduced that:
-
Accept pre-computed latents from the VAE as input.
-
Run only a suffix of the diffus * Run only a suffix of the diffusion schedule, starting from a chosen noise level instead of from pure noise.via noise_strength / starting_step_ratio**
-
noise_strengthin the trainer determines:- Which timestep
t_startyou add noise at when constructingx_tfrom the encoded image. - What fraction of the scheduler timetable is actually executed via
starting_step_ratio.
- Which timestep
-
This couples “how corrupted the image is when the policy starts acting” with “how many denoising actions occur,” making the MDP horizon explicit and tunable.
-
-
Generate trajectories (x_t, x_{t−1}) + log-probs instead of just final images
-
The pipeline was modified to:
- Store all latents
x_talong the denoising path, and - Call a DDPO-compatible
scheduler_stepthat returns both new latents and a per-step log-prob.
- Store all latents
-
_generate_samplesnow returns:latents[:, :-1],next_latents[:, 1:]and alignedtimesteps,- Matching the exact set of steps actually executed (no padding to the full schedule).
-
-
Extend the sampling–reward interface to operate on images
- In addition to prompts, the trainer passes decoded images plus metadata out to the reward function.
compute_rewardsnow effectively receives “(generated image, original prompt, original image, metadata)” tuples so downstream vision models can score the image-conditioned generations.
-
Keep DDPO’s RL machinery unchanged but re-wired to the image pipeline
- PPO-style pieces (advantages, clipping, KL implicitly via log-probs, etc.) remain as in
DDPOTrainer. - What changed is only how samples are generated and structured: the optimizer still sees
(timesteps, latents, next_latents, log_probs, advantages)but these now correspond to image-conditioned rollouts instead of purely text-conditioned ones.
- PPO-style pieces (advantages, clipping, KL implicitly via log-probs, etc.) remain as in
-
Support both “image + prompt” and “image-only” conditioning
-
The pipeline handles:
- Standard CFG when
guidance_scale > 1(uncond + text embeddings). - Pure image-conditioning when
guidance_scale <= 1by running only the unconditional embedding path (no extra CFG forward pass), so the policy can optimize purely w.r.t. the image.
- Standard CFG when
-
-
Adjust training step semantics to keep logging and epochs meaningful
-
ImageDDPOTrainer.stepwas overridden to:- Log rewards at the time of sampling, and
- Advance
global_stepby the number of collected samples so WandB x-axes reflect “data processed” rather than only “optimizer steps,” while still delegating actual weight updates to the original DDPO training loop.
-
Small implementation nuances and patches that made it actually run
-
Custom, device-safe
_get_varianceandscheduler_step-
The stock TRL
_get_varianceassumesalphas_cumprodlives on CPU and indexes withtimestep.cpu(), which clashed with Accelerate moving the scheduler to CUDA. -
A custom
_get_varianceandscheduler_stepwere implemented that:- Normalize timesteps to 1-D tensors on a single “home” device,
- Move
alphas_cumprodandfinal_alpha_cumprodonto that same device beforegather, and - Compute the Gaussian log-prob in a way that tolerates scalar and batched timesteps.
-
-
Deterministic vs stochastic scheduler steps
-
The scheduler step explicitly branches:
- Deterministic DDIM updates when
eta == 0or variance is effectively zero (log-prob set to zero because there is no stochastic action), - Stochastic DDIM when
eta > 0, where the Gaussian log-prob ofx_{t−1}is computed.
- Deterministic DDIM updates when
-
This is important for DDPO, since only stochastic steps should contribute meaningful policy log-probs.
-
-
Log-prob reduction uses mean over pixels, not sum
- As in TRL, per-step log-probs are averaged over all non-batch dimensions instead of summed.
- This keeps magnitudes stable across image sizes and matches DDPO’s existing PPO hyperparameters, avoiding having to retune clip ranges and advantage scaling.
-
Strict latent contract for I2I (no text-only fallback)
- Conceptually,
ImageDDPOTraineralways expects image-derived latents. - The “if latents is None, sample pure noise” branch can be removed or turned into an explicit error, to prevent accidental text-only usage and enforce the image-conditioning contract.
- Conceptually,
-
CFG and unconditional path details
- The “no CFG” path reuses the unconditional embedding (what was called
negative_prompt_embeds) and runs the UNet only once per step, avoiding wasted compute when there is no textual guidance. - The CFG path matches DDPO / diffusers behavior and still supports
guidance_rescale.
- The “no CFG” path reuses the unconditional embedding (what was called
-
Timesteps alignment with the truncated schedule
- Because only a truncated suffix of the scheduler is executed, timesteps must match the number of actual denoising steps.
- The trainer reconstructs
timestepsfromscheduler.timestepsafter the I2I call, ensuring thattimesteps.shape[1] == log_probs.shape[1]and corresponds to the exact steps where actions were taken.
-
Global step accounting for logging
global_stepis incremented by the number of samples collected per epoch (batch size × num batches × num processes) on top of inner training increments.- This is a logging-level tweak so reward curves and training metrics align in WandB, without changing the RL math itself.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file imageddpo-0.1.0.tar.gz.
File metadata
- Download URL: imageddpo-0.1.0.tar.gz
- Upload date:
- Size: 15.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
be94fb9f8f5b17f0df0c6e906d4c1f860ed26eeda94fa0a510882d9f1ebe2c88
|
|
| MD5 |
a10f48eaac81acb0db12e7ed36c38daf
|
|
| BLAKE2b-256 |
8e5e26ce456e0db4fb272f8c24910f42c1ab67df7d0ba69f40f92c72a203c4c2
|
File details
Details for the file imageddpo-0.1.0-py3-none-any.whl.
File metadata
- Download URL: imageddpo-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
428da204b1e605c1f9a686fb5cdc616d562e0a531cf47e6eb12a6fb91e0d0976
|
|
| MD5 |
b21d186daf90d830afea8d975d662c27
|
|
| BLAKE2b-256 |
8bc7c28bfde6fd3b5ccdf6febdaabfaa9cadb6172cd4b287ca1df652b337f388
|