863 lines
31 KiB
C
863 lines
31 KiB
C
/*
|
|
* Iris Sampling Implementation
|
|
*
|
|
* Rectified Flow sampling for image generation.
|
|
* Uses Euler method for ODE integration.
|
|
*/
|
|
|
|
#include "iris.h"
|
|
#include "iris_kernels.h"
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
|
|
#ifdef USE_METAL
|
|
#include "iris_metal.h"
|
|
#endif
|
|
|
|
/* Timing utilities for performance analysis - use wall-clock time */
|
|
static double get_time_ms(void) {
|
|
struct timeval tv;
|
|
gettimeofday(&tv, NULL);
|
|
return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
|
|
}
|
|
|
|
/* Cumulative timing for denoising breakdown */
|
|
double iris_timing_transformer_total = 0.0;
|
|
double iris_timing_transformer_double = 0.0;
|
|
double iris_timing_transformer_single = 0.0;
|
|
double iris_timing_transformer_final = 0.0;
|
|
|
|
void iris_reset_timing(void) {
|
|
iris_timing_transformer_total = 0.0;
|
|
iris_timing_transformer_double = 0.0;
|
|
iris_timing_transformer_single = 0.0;
|
|
iris_timing_transformer_final = 0.0;
|
|
}
|
|
|
|
/* ========================================================================
|
|
* Timestep Schedules
|
|
* ======================================================================== */
|
|
|
|
/*
|
|
* Linear timestep schedule from 1.0 to 0.0
|
|
* Returns array of num_steps+1 values: [1.0, ..., 0.0]
|
|
*/
|
|
float *iris_schedule_linear(int num_steps) {
|
|
float *schedule = (float *)malloc((num_steps + 1) * sizeof(float));
|
|
for (int i = 0; i <= num_steps; i++) {
|
|
schedule[i] = 1.0f - (float)i / (float)num_steps;
|
|
}
|
|
return schedule;
|
|
}
|
|
|
|
/*
|
|
* Power schedule: denser steps at the start (high noise), sparser at the end.
|
|
* schedule[i] = 1 - (i/n)^alpha
|
|
* alpha=1.0 is linear, alpha=2.0 is quadratic, etc.
|
|
*/
|
|
float *iris_schedule_power(int num_steps, float alpha) {
|
|
float *schedule = (float *)malloc((num_steps + 1) * sizeof(float));
|
|
for (int i = 0; i <= num_steps; i++) {
|
|
float t = (float)i / (float)num_steps;
|
|
schedule[i] = 1.0f - powf(t, alpha);
|
|
}
|
|
return schedule;
|
|
}
|
|
|
|
/*
|
|
* Shifted sigmoid schedule (better for flow matching)
|
|
* shift controls where the inflection point is
|
|
*/
|
|
float *iris_schedule_sigmoid(int num_steps, float shift) {
|
|
float *schedule = (float *)malloc((num_steps + 1) * sizeof(float));
|
|
|
|
for (int i = 0; i <= num_steps; i++) {
|
|
float t = (float)i / (float)num_steps;
|
|
/* Shifted sigmoid: more steps at the end */
|
|
float x = (t - 0.5f) * 10.0f + shift;
|
|
schedule[i] = 1.0f - 1.0f / (1.0f + expf(-x));
|
|
}
|
|
|
|
/* Ensure endpoints */
|
|
schedule[0] = 1.0f;
|
|
schedule[num_steps] = 0.0f;
|
|
|
|
return schedule;
|
|
}
|
|
|
|
/*
|
|
* Resolution-dependent schedule (as used in FLUX.2)
|
|
* Higher resolutions use more steps at the start
|
|
*/
|
|
float *iris_schedule_resolution(int num_steps, int height, int width) {
|
|
float *schedule = (float *)malloc((num_steps + 1) * sizeof(float));
|
|
|
|
/* Compute shift based on resolution */
|
|
int pixels = height * width;
|
|
float shift = 0.0f;
|
|
if (pixels >= 1024 * 1024) {
|
|
shift = 1.0f; /* High res: more early steps */
|
|
}
|
|
else if (pixels >= 512 * 512) {
|
|
shift = 0.5f;
|
|
}
|
|
|
|
for (int i = 0; i <= num_steps; i++) {
|
|
float t = (float)i / (float)num_steps;
|
|
/* Apply shift */
|
|
t = powf(t, 1.0f + shift * 0.5f);
|
|
schedule[i] = 1.0f - t;
|
|
}
|
|
|
|
return schedule;
|
|
}
|
|
|
|
/*
|
|
* FLUX.2 official schedule with empirical mu calculation
|
|
* Matches Python's get_schedule() function from official flux2 code
|
|
*/
|
|
/* Compute the empirical shift parameter mu for the resolution-dependent
|
|
* noise schedule. The constants a1, b1, a2, b2 are fitted from the Flux
|
|
* training distribution and control how the SNR schedule adapts to different
|
|
* image resolutions. Higher resolution images need more denoising steps
|
|
* at high noise levels. Interpolates between two linear fits based on
|
|
* step count, with a cutoff at 4300 tokens. */
|
|
static float compute_empirical_mu(int image_seq_len, int num_steps) {
|
|
const float a1 = 8.73809524e-05f, b1 = 1.89833333f;
|
|
const float a2 = 0.00016927f, b2 = 0.45666666f;
|
|
|
|
if (image_seq_len > 4300) {
|
|
return a2 * image_seq_len + b2;
|
|
}
|
|
|
|
float m_200 = a2 * image_seq_len + b2;
|
|
float m_10 = a1 * image_seq_len + b1;
|
|
|
|
float a = (m_200 - m_10) / 190.0f;
|
|
float b = m_200 - 200.0f * a;
|
|
return a * num_steps + b;
|
|
}
|
|
|
|
/* Apply exponential SNR (Signal-to-Noise Ratio) shift to a timestep.
|
|
* Maps t in [0,1] through t/(t + (1-t)*exp(-mu)), shifting the schedule
|
|
* toward more time spent at higher noise levels. The boundary guards
|
|
* (t<=0, t>=1) prevent division by zero. */
|
|
static float generalized_time_snr_shift(float t, float mu, float sigma) {
|
|
/* t / (1 - t) with exp(mu) shift */
|
|
if (t <= 0.0f)
|
|
return 0.0f;
|
|
if (t >= 1.0f)
|
|
return 1.0f;
|
|
return expf(mu) / (expf(mu) + powf(1.0f / t - 1.0f, sigma));
|
|
}
|
|
|
|
float *iris_schedule_flux(int num_steps, int image_seq_len) {
|
|
float *schedule = (float *)malloc((num_steps + 1) * sizeof(float));
|
|
float mu = compute_empirical_mu(image_seq_len, num_steps);
|
|
|
|
for (int i = 0; i <= num_steps; i++) {
|
|
float t = 1.0f - (float)i / (float)num_steps; /* Linear from 1 to 0 */
|
|
schedule[i] = generalized_time_snr_shift(t, mu, 1.0f);
|
|
}
|
|
|
|
return schedule;
|
|
}
|
|
|
|
/* ========================================================================
|
|
* Euler Sampler for Rectified Flow
|
|
* ======================================================================== */
|
|
|
|
/*
|
|
* Single Euler step:
|
|
* z_next = z_t + (t_next - t_curr) * v(z_t, t_curr)
|
|
*
|
|
* Where v is the velocity predicted by the model.
|
|
* In rectified flow: v = (z_data - z_noise) at timestep t
|
|
*/
|
|
|
|
typedef struct iris_transformer_flux iris_transformer_flux_t;
|
|
typedef struct iris_vae iris_vae_t;
|
|
|
|
/* Free cached mmap weights after denoising */
|
|
extern void iris_transformer_free_mmap_cache_flux(iris_transformer_flux_t *tf);
|
|
|
|
/* Forward declarations */
|
|
extern float *iris_transformer_forward_flux(iris_transformer_flux_t *tf, const float *img_latent, int img_h, int img_w, const float *txt_emb, int txt_seq,
|
|
float timestep);
|
|
|
|
/* Forward declaration for in-context conditioning (img2img) */
|
|
extern float *iris_transformer_forward_refs_flux(iris_transformer_flux_t *tf, const float *img_latent, int img_h, int img_w, const float *ref_latent, int ref_h,
|
|
int ref_w, int t_offset, const float *txt_emb, int txt_seq, float timestep);
|
|
|
|
/* Forward declaration for multi-reference conditioning */
|
|
typedef struct {
|
|
const float *latent; /* Reference latent in NCHW format */
|
|
int h, w; /* Latent dimensions */
|
|
int t_offset; /* RoPE T coordinate (10, 20, 30, ...) */
|
|
} iris_ref_t;
|
|
|
|
extern float *iris_transformer_forward_multirefs_flux(iris_transformer_flux_t *tf, const float *img_latent, int img_h, int img_w, const iris_ref_t *refs,
|
|
int num_refs, const float *txt_emb, int txt_seq, float timestep);
|
|
|
|
/* VAE decode for step image callback */
|
|
extern iris_image *iris_vae_decode(iris_vae_t *vae, const float *latent, int batch, int latent_h, int latent_w);
|
|
extern void iris_image_free(iris_image *img);
|
|
|
|
/* Euler ODE sampler for Flux distilled models (txt2img). Iterates
|
|
* z_{n+1} = z_n + dt * v(z_n, t) where v is the velocity predicted by
|
|
* the transformer. The schedule provides sigma values from 1 to 0;
|
|
* the timestep passed to the transformer equals sigma, which it scales
|
|
* internally by 1000 for the sinusoidal embedding. */
|
|
float *iris_sample_euler_flux(void *transformer, void *text_encoder, float *z, int batch, int channels, int h, int w, const float *text_emb, int text_seq,
|
|
const float *schedule, int num_steps, void (*progress_callback)(int step, int total)) {
|
|
(void)text_encoder; /* Reserved for future use */
|
|
iris_transformer_flux_t *tf = (iris_transformer_flux_t *)transformer;
|
|
int latent_size = batch * channels * h * w;
|
|
|
|
/* Working buffers */
|
|
float *z_curr = (float *)malloc(latent_size * sizeof(float));
|
|
float *v_cond = NULL;
|
|
|
|
iris_copy(z_curr, z, latent_size);
|
|
|
|
/* Reset timing counters */
|
|
iris_reset_timing();
|
|
double total_denoising_start = get_time_ms();
|
|
double step_times[IRIS_MAX_STEPS];
|
|
|
|
for (int step = 0; step < num_steps; step++) {
|
|
float t_curr = schedule[step];
|
|
float t_next = schedule[step + 1];
|
|
float dt = t_next - t_curr; /* Negative for denoising */
|
|
|
|
double step_start = get_time_ms();
|
|
|
|
/* Notify step start */
|
|
if (iris_step_callback)
|
|
iris_step_callback(step + 1, num_steps);
|
|
|
|
/* Predict velocity with conditioning */
|
|
v_cond = iris_transformer_forward_flux(tf, z_curr, h, w, text_emb, text_seq, t_curr);
|
|
|
|
/* Euler step: z_next = z_curr + dt * v */
|
|
iris_axpy(z_curr, dt, v_cond, latent_size);
|
|
|
|
free(v_cond);
|
|
|
|
step_times[step] = get_time_ms() - step_start;
|
|
|
|
if (progress_callback) {
|
|
progress_callback(step + 1, num_steps);
|
|
}
|
|
|
|
/* Step image callback - decode and display intermediate result */
|
|
if (iris_step_image_callback && iris_step_image_vae && step + 1 < num_steps) {
|
|
iris_image *img = iris_vae_decode((iris_vae_t *)iris_step_image_vae, z_curr, 1, h, w);
|
|
if (img) {
|
|
iris_step_image_callback(step + 1, num_steps, img);
|
|
iris_image_free(img);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Print timing summary */
|
|
if (iris_verbose) {
|
|
double total_denoising = get_time_ms() - total_denoising_start;
|
|
fprintf(stderr, "\nDenoising timing breakdown:\n");
|
|
for (int step = 0; step < num_steps; step++) {
|
|
fprintf(stderr, " Step %d: %.1f ms\n", step + 1, step_times[step]);
|
|
}
|
|
fprintf(stderr, " Total denoising: %.1f ms (%.2f s)\n", total_denoising, total_denoising / 1000.0);
|
|
if (iris_timing_transformer_double > 0 || iris_timing_transformer_single > 0) {
|
|
fprintf(stderr, " Transformer breakdown:\n");
|
|
fprintf(stderr, " Double blocks: %.1f ms (%.1f%%)\n", iris_timing_transformer_double,
|
|
100.0 * iris_timing_transformer_double / iris_timing_transformer_total);
|
|
fprintf(stderr, " Single blocks: %.1f ms (%.1f%%)\n", iris_timing_transformer_single,
|
|
100.0 * iris_timing_transformer_single / iris_timing_transformer_total);
|
|
fprintf(stderr, " Final layer: %.1f ms (%.1f%%)\n", iris_timing_transformer_final,
|
|
100.0 * iris_timing_transformer_final / iris_timing_transformer_total);
|
|
fprintf(stderr, " Total: %.1f ms\n", iris_timing_transformer_total);
|
|
/* Print fine-grained single block profile if available */
|
|
extern void iris_print_blas_profile(void);
|
|
iris_print_blas_profile();
|
|
}
|
|
}
|
|
|
|
iris_transformer_free_mmap_cache_flux(tf);
|
|
return z_curr;
|
|
}
|
|
|
|
/* Euler sampler for single-reference img2img. The reference image is
|
|
* VAE-encoded and concatenated with the noised target as extra tokens.
|
|
* A RoPE T offset (default 10) distinguishes reference from target in
|
|
* the positional encoding. The transformer attends to both via joint
|
|
* attention, implementing in-context conditioning. */
|
|
float *iris_sample_euler_refs_flux(void *transformer, void *text_encoder, float *z, int batch, int channels, int h, int w, const float *ref_latent, int ref_h,
|
|
int ref_w, int t_offset, const float *text_emb, int text_seq, const float *schedule, int num_steps,
|
|
void (*progress_callback)(int step, int total)) {
|
|
(void)text_encoder; /* Reserved for future use */
|
|
iris_transformer_flux_t *tf = (iris_transformer_flux_t *)transformer;
|
|
int latent_size = batch * channels * h * w;
|
|
|
|
/* Working buffer */
|
|
float *z_curr = (float *)malloc(latent_size * sizeof(float));
|
|
iris_copy(z_curr, z, latent_size);
|
|
|
|
/* Reset timing counters */
|
|
iris_reset_timing();
|
|
double total_denoising_start = get_time_ms();
|
|
double step_times[IRIS_MAX_STEPS];
|
|
|
|
for (int step = 0; step < num_steps; step++) {
|
|
float t_curr = schedule[step];
|
|
float t_next = schedule[step + 1];
|
|
float dt = t_next - t_curr;
|
|
|
|
double step_start = get_time_ms();
|
|
|
|
/* Notify step start */
|
|
if (iris_step_callback)
|
|
iris_step_callback(step + 1, num_steps);
|
|
|
|
/* Predict velocity with reference image conditioning */
|
|
float *v = iris_transformer_forward_refs_flux(tf, z_curr, h, w, ref_latent, ref_h, ref_w, t_offset, text_emb, text_seq, t_curr);
|
|
|
|
/* Euler step: z_next = z_curr + dt * v */
|
|
iris_axpy(z_curr, dt, v, latent_size);
|
|
|
|
free(v);
|
|
|
|
step_times[step] = get_time_ms() - step_start;
|
|
|
|
if (progress_callback) {
|
|
progress_callback(step + 1, num_steps);
|
|
}
|
|
|
|
/* Step image callback - decode and display intermediate result */
|
|
if (iris_step_image_callback && iris_step_image_vae && step + 1 < num_steps) {
|
|
iris_image *img = iris_vae_decode((iris_vae_t *)iris_step_image_vae, z_curr, 1, h, w);
|
|
if (img) {
|
|
iris_step_image_callback(step + 1, num_steps, img);
|
|
iris_image_free(img);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Print timing summary */
|
|
if (iris_verbose) {
|
|
double total_denoising = get_time_ms() - total_denoising_start;
|
|
fprintf(stderr, "\nDenoising timing breakdown (img2img with refs):\n");
|
|
for (int step = 0; step < num_steps; step++) {
|
|
fprintf(stderr, " Step %d: %.1f ms\n", step + 1, step_times[step]);
|
|
}
|
|
fprintf(stderr, " Total denoising: %.1f ms (%.2f s)\n", total_denoising, total_denoising / 1000.0);
|
|
}
|
|
|
|
iris_transformer_free_mmap_cache_flux(tf);
|
|
return z_curr;
|
|
}
|
|
|
|
/*
|
|
* Sample using Euler method with multiple reference images.
|
|
* Each reference gets a different T offset in RoPE (10, 20, 30, ...).
|
|
*/
|
|
float *iris_sample_euler_multirefs_flux(void *transformer, void *text_encoder, float *z, int batch, int channels, int h, int w, const iris_ref_t *refs,
|
|
int num_refs, const float *text_emb, int text_seq, const float *schedule, int num_steps,
|
|
void (*progress_callback)(int step, int total)) {
|
|
(void)text_encoder;
|
|
iris_transformer_flux_t *tf = (iris_transformer_flux_t *)transformer;
|
|
int latent_size = batch * channels * h * w;
|
|
|
|
float *z_curr = (float *)malloc(latent_size * sizeof(float));
|
|
iris_copy(z_curr, z, latent_size);
|
|
|
|
iris_reset_timing();
|
|
double total_denoising_start = get_time_ms();
|
|
double step_times[IRIS_MAX_STEPS];
|
|
|
|
for (int step = 0; step < num_steps; step++) {
|
|
float t_curr = schedule[step];
|
|
float t_next = schedule[step + 1];
|
|
float dt = t_next - t_curr;
|
|
|
|
double step_start = get_time_ms();
|
|
|
|
if (iris_step_callback)
|
|
iris_step_callback(step + 1, num_steps);
|
|
|
|
/* Predict velocity with multiple reference images */
|
|
float *v = iris_transformer_forward_multirefs_flux(tf, z_curr, h, w, refs, num_refs, text_emb, text_seq, t_curr);
|
|
|
|
/* Euler step */
|
|
iris_axpy(z_curr, dt, v, latent_size);
|
|
free(v);
|
|
|
|
step_times[step] = get_time_ms() - step_start;
|
|
|
|
if (progress_callback)
|
|
progress_callback(step + 1, num_steps);
|
|
|
|
if (iris_step_image_callback && iris_step_image_vae && step + 1 < num_steps) {
|
|
iris_image *img = iris_vae_decode((iris_vae_t *)iris_step_image_vae, z_curr, 1, h, w);
|
|
if (img) {
|
|
iris_step_image_callback(step + 1, num_steps, img);
|
|
iris_image_free(img);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (iris_verbose) {
|
|
double total_denoising = get_time_ms() - total_denoising_start;
|
|
fprintf(stderr, "\nDenoising timing breakdown (multi-ref, %d refs):\n", num_refs);
|
|
for (int step = 0; step < num_steps; step++) {
|
|
fprintf(stderr, " Step %d: %.1f ms\n", step + 1, step_times[step]);
|
|
}
|
|
fprintf(stderr, " Total denoising: %.1f ms (%.2f s)\n", total_denoising, total_denoising / 1000.0);
|
|
}
|
|
|
|
iris_transformer_free_mmap_cache_flux(tf);
|
|
return z_curr;
|
|
}
|
|
|
|
/* ========================================================================
|
|
* CFG (Classifier-Free Guidance) Samplers for Base Model
|
|
*
|
|
* These run the transformer twice per step: once with empty text (uncond)
|
|
* and once with the real prompt (cond), then combine:
|
|
* v = v_uncond + guidance_scale * (v_cond - v_uncond)
|
|
* ======================================================================== */
|
|
|
|
/* Euler sampler with Classifier-Free Guidance for Flux base models.
|
|
* Each step runs the transformer twice: once with empty prompt (unconditional)
|
|
* and once with real prompt (conditional). Combined as
|
|
* v = v_uncond + guidance_scale * (v_cond - v_uncond), which steers
|
|
* generation toward the prompt at the cost of 2x compute per step. */
|
|
float *iris_sample_euler_cfg_flux(void *transformer, void *text_encoder, float *z, int batch, int channels, int h, int w, const float *text_emb_cond,
|
|
int text_seq_cond, const float *text_emb_uncond, int text_seq_uncond, float guidance_scale, const float *schedule,
|
|
int num_steps, void (*progress_callback)(int step, int total)) {
|
|
(void)text_encoder;
|
|
iris_transformer_flux_t *tf = (iris_transformer_flux_t *)transformer;
|
|
int latent_size = batch * channels * h * w;
|
|
|
|
float *z_curr = (float *)malloc(latent_size * sizeof(float));
|
|
iris_copy(z_curr, z, latent_size);
|
|
|
|
iris_reset_timing();
|
|
double total_denoising_start = get_time_ms();
|
|
double step_times[IRIS_MAX_STEPS];
|
|
|
|
for (int step = 0; step < num_steps; step++) {
|
|
float t_curr = schedule[step];
|
|
float t_next = schedule[step + 1];
|
|
float dt = t_next - t_curr;
|
|
|
|
double step_start = get_time_ms();
|
|
|
|
if (iris_step_callback)
|
|
iris_step_callback(step + 1, num_steps);
|
|
|
|
/* Unconditioned prediction */
|
|
float *v_uncond = iris_transformer_forward_flux(tf, z_curr, h, w, text_emb_uncond, text_seq_uncond, t_curr);
|
|
|
|
/* Conditioned prediction */
|
|
float *v_cond = iris_transformer_forward_flux(tf, z_curr, h, w, text_emb_cond, text_seq_cond, t_curr);
|
|
|
|
/* CFG combine: v = v_uncond + scale * (v_cond - v_uncond) */
|
|
for (int i = 0; i < latent_size; i++) {
|
|
float v = v_uncond[i] + guidance_scale * (v_cond[i] - v_uncond[i]);
|
|
z_curr[i] += dt * v;
|
|
}
|
|
|
|
free(v_uncond);
|
|
free(v_cond);
|
|
|
|
step_times[step] = get_time_ms() - step_start;
|
|
|
|
if (progress_callback)
|
|
progress_callback(step + 1, num_steps);
|
|
|
|
if (iris_step_image_callback && iris_step_image_vae && step + 1 < num_steps) {
|
|
iris_image *img = iris_vae_decode((iris_vae_t *)iris_step_image_vae, z_curr, 1, h, w);
|
|
if (img) {
|
|
iris_step_image_callback(step + 1, num_steps, img);
|
|
iris_image_free(img);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (iris_verbose) {
|
|
double total_denoising = get_time_ms() - total_denoising_start;
|
|
fprintf(stderr, "\nDenoising timing breakdown (CFG, guidance=%.1f):\n", guidance_scale);
|
|
for (int step = 0; step < num_steps; step++) {
|
|
fprintf(stderr, " Step %d: %.1f ms\n", step + 1, step_times[step]);
|
|
}
|
|
fprintf(stderr, " Total denoising: %.1f ms (%.2f s)\n", total_denoising, total_denoising / 1000.0);
|
|
}
|
|
|
|
iris_transformer_free_mmap_cache_flux(tf);
|
|
return z_curr;
|
|
}
|
|
|
|
/*
|
|
* Euler sampler with CFG and single reference image (img2img).
|
|
*/
|
|
float *iris_sample_euler_cfg_refs_flux(void *transformer, void *text_encoder, float *z, int batch, int channels, int h, int w, const float *ref_latent,
|
|
int ref_h, int ref_w, int t_offset, const float *text_emb_cond, int text_seq_cond, const float *text_emb_uncond,
|
|
int text_seq_uncond, float guidance_scale, const float *schedule, int num_steps,
|
|
void (*progress_callback)(int step, int total)) {
|
|
(void)text_encoder;
|
|
iris_transformer_flux_t *tf = (iris_transformer_flux_t *)transformer;
|
|
int latent_size = batch * channels * h * w;
|
|
|
|
float *z_curr = (float *)malloc(latent_size * sizeof(float));
|
|
iris_copy(z_curr, z, latent_size);
|
|
|
|
iris_reset_timing();
|
|
double total_denoising_start = get_time_ms();
|
|
double step_times[IRIS_MAX_STEPS];
|
|
|
|
for (int step = 0; step < num_steps; step++) {
|
|
float t_curr = schedule[step];
|
|
float t_next = schedule[step + 1];
|
|
float dt = t_next - t_curr;
|
|
|
|
double step_start = get_time_ms();
|
|
|
|
if (iris_step_callback)
|
|
iris_step_callback(step + 1, num_steps);
|
|
|
|
/* Unconditioned prediction (with ref) */
|
|
float *v_uncond = iris_transformer_forward_refs_flux(tf, z_curr, h, w, ref_latent, ref_h, ref_w, t_offset, text_emb_uncond, text_seq_uncond, t_curr);
|
|
|
|
/* Conditioned prediction (with ref) */
|
|
float *v_cond = iris_transformer_forward_refs_flux(tf, z_curr, h, w, ref_latent, ref_h, ref_w, t_offset, text_emb_cond, text_seq_cond, t_curr);
|
|
|
|
/* CFG combine */
|
|
for (int i = 0; i < latent_size; i++) {
|
|
float v = v_uncond[i] + guidance_scale * (v_cond[i] - v_uncond[i]);
|
|
z_curr[i] += dt * v;
|
|
}
|
|
|
|
free(v_uncond);
|
|
free(v_cond);
|
|
|
|
step_times[step] = get_time_ms() - step_start;
|
|
|
|
if (progress_callback)
|
|
progress_callback(step + 1, num_steps);
|
|
|
|
if (iris_step_image_callback && iris_step_image_vae && step + 1 < num_steps) {
|
|
iris_image *img = iris_vae_decode((iris_vae_t *)iris_step_image_vae, z_curr, 1, h, w);
|
|
if (img) {
|
|
iris_step_image_callback(step + 1, num_steps, img);
|
|
iris_image_free(img);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (iris_verbose) {
|
|
double total_denoising = get_time_ms() - total_denoising_start;
|
|
fprintf(stderr, "\nDenoising timing breakdown (CFG img2img, guidance=%.1f):\n", guidance_scale);
|
|
for (int step = 0; step < num_steps; step++) {
|
|
fprintf(stderr, " Step %d: %.1f ms\n", step + 1, step_times[step]);
|
|
}
|
|
fprintf(stderr, " Total denoising: %.1f ms (%.2f s)\n", total_denoising, total_denoising / 1000.0);
|
|
}
|
|
|
|
iris_transformer_free_mmap_cache_flux(tf);
|
|
return z_curr;
|
|
}
|
|
|
|
/*
|
|
* Euler sampler with CFG and multiple reference images.
|
|
*/
|
|
float *iris_sample_euler_cfg_multirefs_flux(void *transformer, void *text_encoder, float *z, int batch, int channels, int h, int w, const iris_ref_t *refs,
|
|
int num_refs, const float *text_emb_cond, int text_seq_cond, const float *text_emb_uncond, int text_seq_uncond,
|
|
float guidance_scale, const float *schedule, int num_steps, void (*progress_callback)(int step, int total)) {
|
|
(void)text_encoder;
|
|
iris_transformer_flux_t *tf = (iris_transformer_flux_t *)transformer;
|
|
int latent_size = batch * channels * h * w;
|
|
|
|
float *z_curr = (float *)malloc(latent_size * sizeof(float));
|
|
iris_copy(z_curr, z, latent_size);
|
|
|
|
iris_reset_timing();
|
|
double total_denoising_start = get_time_ms();
|
|
double step_times[IRIS_MAX_STEPS];
|
|
|
|
for (int step = 0; step < num_steps; step++) {
|
|
float t_curr = schedule[step];
|
|
float t_next = schedule[step + 1];
|
|
float dt = t_next - t_curr;
|
|
|
|
double step_start = get_time_ms();
|
|
|
|
if (iris_step_callback)
|
|
iris_step_callback(step + 1, num_steps);
|
|
|
|
/* Unconditioned prediction (with refs) */
|
|
float *v_uncond = iris_transformer_forward_multirefs_flux(tf, z_curr, h, w, refs, num_refs, text_emb_uncond, text_seq_uncond, t_curr);
|
|
|
|
/* Conditioned prediction (with refs) */
|
|
float *v_cond = iris_transformer_forward_multirefs_flux(tf, z_curr, h, w, refs, num_refs, text_emb_cond, text_seq_cond, t_curr);
|
|
|
|
/* CFG combine */
|
|
for (int i = 0; i < latent_size; i++) {
|
|
float v = v_uncond[i] + guidance_scale * (v_cond[i] - v_uncond[i]);
|
|
z_curr[i] += dt * v;
|
|
}
|
|
|
|
free(v_uncond);
|
|
free(v_cond);
|
|
|
|
step_times[step] = get_time_ms() - step_start;
|
|
|
|
if (progress_callback)
|
|
progress_callback(step + 1, num_steps);
|
|
|
|
if (iris_step_image_callback && iris_step_image_vae && step + 1 < num_steps) {
|
|
iris_image *img = iris_vae_decode((iris_vae_t *)iris_step_image_vae, z_curr, 1, h, w);
|
|
if (img) {
|
|
iris_step_image_callback(step + 1, num_steps, img);
|
|
iris_image_free(img);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (iris_verbose) {
|
|
double total_denoising = get_time_ms() - total_denoising_start;
|
|
fprintf(stderr, "\nDenoising timing breakdown (CFG multi-ref, %d refs, guidance=%.1f):\n", num_refs, guidance_scale);
|
|
for (int step = 0; step < num_steps; step++) {
|
|
fprintf(stderr, " Step %d: %.1f ms\n", step + 1, step_times[step]);
|
|
}
|
|
fprintf(stderr, " Total denoising: %.1f ms (%.2f s)\n", total_denoising, total_denoising / 1000.0);
|
|
}
|
|
|
|
iris_transformer_free_mmap_cache_flux(tf);
|
|
return z_curr;
|
|
}
|
|
|
|
/*
|
|
* Sample using Euler method with stochastic noise injection.
|
|
* This can help with diversity and quality.
|
|
*/
|
|
float *iris_sample_euler_ancestral(void *transformer, float *z, int batch, int channels, int h, int w, const float *text_emb, int text_seq,
|
|
const float *schedule, int num_steps, float eta, void (*progress_callback)(int step, int total)) {
|
|
iris_transformer_flux_t *tf = (iris_transformer_flux_t *)transformer;
|
|
int latent_size = batch * channels * h * w;
|
|
|
|
float *z_curr = (float *)malloc(latent_size * sizeof(float));
|
|
float *noise = (float *)malloc(latent_size * sizeof(float));
|
|
|
|
iris_copy(z_curr, z, latent_size);
|
|
|
|
for (int step = 0; step < num_steps; step++) {
|
|
float t_curr = schedule[step];
|
|
float t_next = schedule[step + 1];
|
|
float dt = t_next - t_curr;
|
|
|
|
/* Predict velocity */
|
|
float *v = iris_transformer_forward_flux(tf, z_curr, h, w, text_emb, text_seq, t_curr);
|
|
|
|
/* Euler step */
|
|
iris_axpy(z_curr, dt, v, latent_size);
|
|
|
|
/* Add noise (ancestral sampling) */
|
|
if (eta > 0 && step < num_steps - 1) {
|
|
float sigma = eta * sqrtf(fabsf(dt));
|
|
iris_randn(noise, latent_size);
|
|
iris_axpy(z_curr, sigma, noise, latent_size);
|
|
}
|
|
|
|
free(v);
|
|
|
|
if (progress_callback) {
|
|
progress_callback(step + 1, num_steps);
|
|
}
|
|
|
|
/* Step image callback - decode and display intermediate result */
|
|
if (iris_step_image_callback && iris_step_image_vae && step + 1 < num_steps) {
|
|
iris_image *img = iris_vae_decode((iris_vae_t *)iris_step_image_vae, z_curr, 1, h, w);
|
|
if (img) {
|
|
iris_step_image_callback(step + 1, num_steps, img);
|
|
iris_image_free(img);
|
|
}
|
|
}
|
|
}
|
|
|
|
free(noise);
|
|
iris_transformer_free_mmap_cache_flux(tf);
|
|
return z_curr;
|
|
}
|
|
|
|
/* ========================================================================
|
|
* Heun Sampler (2nd order)
|
|
* ======================================================================== */
|
|
|
|
/*
|
|
* Heun's method (improved Euler):
|
|
* 1. Predict: z_pred = z_t + dt * v(z_t, t)
|
|
* 2. Correct: z_next = z_t + dt/2 * (v(z_t, t) + v(z_pred, t+dt))
|
|
*/
|
|
float *iris_sample_heun(void *transformer, float *z, int batch, int channels, int h, int w, const float *text_emb, int text_seq, const float *schedule,
|
|
int num_steps, void (*progress_callback)(int step, int total)) {
|
|
iris_transformer_flux_t *tf = (iris_transformer_flux_t *)transformer;
|
|
int latent_size = batch * channels * h * w;
|
|
|
|
float *z_curr = (float *)malloc(latent_size * sizeof(float));
|
|
float *z_pred = (float *)malloc(latent_size * sizeof(float));
|
|
|
|
iris_copy(z_curr, z, latent_size);
|
|
|
|
for (int step = 0; step < num_steps; step++) {
|
|
float t_curr = schedule[step];
|
|
float t_next = schedule[step + 1];
|
|
float dt = t_next - t_curr;
|
|
|
|
/* First velocity estimate */
|
|
float *v1 = iris_transformer_forward_flux(tf, z_curr, h, w, text_emb, text_seq, t_curr);
|
|
|
|
/* Predict next state */
|
|
iris_copy(z_pred, z_curr, latent_size);
|
|
iris_axpy(z_pred, dt, v1, latent_size);
|
|
|
|
/* Second velocity estimate (only if not last step) */
|
|
if (step < num_steps - 1) {
|
|
float *v2 = iris_transformer_forward_flux(tf, z_pred, h, w, text_emb, text_seq, t_next);
|
|
|
|
/* Heun correction: z_next = z_curr + dt/2 * (v1 + v2) */
|
|
for (int i = 0; i < latent_size; i++) {
|
|
z_curr[i] += 0.5f * dt * (v1[i] + v2[i]);
|
|
}
|
|
|
|
free(v2);
|
|
}
|
|
else {
|
|
/* Last step: just use Euler */
|
|
iris_axpy(z_curr, dt, v1, latent_size);
|
|
}
|
|
|
|
free(v1);
|
|
|
|
if (progress_callback) {
|
|
progress_callback(step + 1, num_steps);
|
|
}
|
|
|
|
/* Step image callback - decode and display intermediate result */
|
|
if (iris_step_image_callback && iris_step_image_vae && step + 1 < num_steps) {
|
|
iris_image *img = iris_vae_decode((iris_vae_t *)iris_step_image_vae, z_curr, 1, h, w);
|
|
if (img) {
|
|
iris_step_image_callback(step + 1, num_steps, img);
|
|
iris_image_free(img);
|
|
}
|
|
}
|
|
}
|
|
|
|
free(z_pred);
|
|
iris_transformer_free_mmap_cache_flux(tf);
|
|
return z_curr;
|
|
}
|
|
|
|
/* ========================================================================
|
|
* Latent Noise Initialization
|
|
* ======================================================================== */
|
|
|
|
/* Generate initial noise for the denoising process. Uses a fixed 112x112
|
|
* noise patch tiled/cropped to the target size, ensuring seed-reproducible
|
|
* results regardless of output resolution. Without this, the same seed
|
|
* would produce different images at different sizes. For targets at or
|
|
* above 112x112 latent dims, noise is generated directly. */
|
|
#define NOISE_MAX_LATENT_DIM 112 /* 1792/16 = 112 */
|
|
|
|
float *iris_init_noise(int batch, int channels, int h, int w, int64_t seed) {
|
|
int target_size = batch * channels * h * w;
|
|
float *noise = (float *)malloc(target_size * sizeof(float));
|
|
|
|
if (seed >= 0) {
|
|
iris_rng_seed((uint64_t)seed);
|
|
}
|
|
|
|
/* If target is max size or larger, just generate directly */
|
|
if (h >= NOISE_MAX_LATENT_DIM && w >= NOISE_MAX_LATENT_DIM) {
|
|
iris_randn(noise, target_size);
|
|
return noise;
|
|
}
|
|
|
|
/* Generate noise at max latent size */
|
|
int max_h = NOISE_MAX_LATENT_DIM;
|
|
int max_w = NOISE_MAX_LATENT_DIM;
|
|
int max_size = batch * channels * max_h * max_w;
|
|
float *max_noise = (float *)malloc(max_size * sizeof(float));
|
|
iris_randn(max_noise, max_size);
|
|
|
|
/* Subsample to target size using nearest-neighbor */
|
|
for (int b = 0; b < batch; b++) {
|
|
for (int c = 0; c < channels; c++) {
|
|
for (int ty = 0; ty < h; ty++) {
|
|
for (int tx = 0; tx < w; tx++) {
|
|
/* Map target position to source position */
|
|
int sy = ty * max_h / h;
|
|
int sx = tx * max_w / w;
|
|
|
|
int src_idx = ((b * channels + c) * max_h + sy) * max_w + sx;
|
|
int dst_idx = ((b * channels + c) * h + ty) * w + tx;
|
|
noise[dst_idx] = max_noise[src_idx];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
free(max_noise);
|
|
return noise;
|
|
}
|
|
|
|
/* ========================================================================
|
|
* Full Generation Pipeline
|
|
* ======================================================================== */
|
|
|
|
/* Complete text-to-image pipeline in latent space. Orchestrates:
|
|
* text encoding -> noise initialization -> schedule computation -> Euler
|
|
* sampling -> returns denoised latent (caller handles VAE decode).
|
|
* Routes to the appropriate sampler variant based on model type. */
|
|
typedef struct iris_ctx iris_ctx;
|
|
|
|
/* Forward declaration */
|
|
extern iris_ctx *iris_get_ctx(void);
|
|
|
|
float *iris_generate_latent(void *ctx_ptr, const float *text_emb, int text_seq, int height, int width, int num_steps, int64_t seed,
|
|
void (*progress_callback)(int step, int total)) {
|
|
/* Compute latent dimensions */
|
|
int latent_h = height / 16;
|
|
int latent_w = width / 16;
|
|
int channels = IRIS_LATENT_CHANNELS;
|
|
|
|
/* Initialize noise */
|
|
float *z = iris_init_noise(1, channels, latent_h, latent_w, seed);
|
|
|
|
/* Get schedule (4 steps for klein distilled) */
|
|
float *schedule = iris_schedule_linear(num_steps);
|
|
|
|
/* Sample (FLUX.2-klein is guidance-distilled, no CFG needed) */
|
|
float *latent = iris_sample_euler_flux(ctx_ptr, NULL, z, 1, channels, latent_h, latent_w, text_emb, text_seq, schedule, num_steps, progress_callback);
|
|
|
|
free(z);
|
|
free(schedule);
|
|
|
|
return latent;
|
|
}
|
|
|
|
/* ========================================================================
|
|
* Legacy Progress Callback (for backwards compatibility)
|
|
* ======================================================================== */
|
|
|
|
/* Legacy callback for step-level progress (called from sampling loop) */
|
|
void (*iris_progress_callback)(int, int) = NULL;
|
|
|
|
void iris_set_progress_callback(void (*callback)(int, int)) {
|
|
iris_progress_callback = callback;
|
|
}
|