mirror of
https://github.com/leejet/stable-diffusion.cpp.git
synced 2026-10-19 05:59:21 +08:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1da55ebde8 | ||
|
|
fb8ee71f55 | ||
|
|
96f67fe631 | ||
|
|
203a882a28 | ||
|
|
019bcfc0ae | ||
|
|
c150a6bafe | ||
|
|
f4b5326da7 | ||
|
|
bbdcad48dd | ||
|
|
b6947b8bdc | ||
|
|
9db0db6570 | ||
|
|
d4bfcd5d78 | ||
|
|
3f8527a46c | ||
|
|
42ab1c12f1 |
@@ -560,6 +560,18 @@ jobs:
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
uses: pr-mpt/actions-commit-hash@v2
|
||||
|
||||
- name: Bundle HIP runtime libraries
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
$binPath = (rocm-sdk path --bin).Trim()
|
||||
if (-not $binPath) { throw "rocm-sdk path --bin returned empty" }
|
||||
foreach ($dll in @("amdhip64_7.dll", "rocm_kpack.dll", "amd_comgr.dll")) {
|
||||
$f = Get-ChildItem -Path $binPath -Filter $dll -ErrorAction SilentlyContinue
|
||||
if (-not $f) { throw "no match for $dll in $binPath" }
|
||||
Copy-Item $f.FullName -Destination .\build\bin\ -Force
|
||||
}
|
||||
|
||||
- name: Pack artifacts
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
run: |
|
||||
|
||||
@@ -52,6 +52,7 @@ API and command-line option may change frequently.***
|
||||
- [PiD](./docs/pid.md)
|
||||
- [LongCat Image](./docs/longcat_image.md)
|
||||
- [Z-Image](./docs/z_image.md)
|
||||
- [Z-Image L2P](./docs/z_image_l2p.md)
|
||||
- [MiniT2I](./docs/minit2i.md)
|
||||
- [SenseNova U1.5](./docs/sensenova_u1.md)
|
||||
- [Ovis-Image](./docs/ovis_image.md)
|
||||
|
||||
@@ -84,17 +84,18 @@ The floating-point output is reconstructed as
|
||||
Y[r, o] ~= A[r, o] * s_x[r] * s_w[o] + b[o]
|
||||
```
|
||||
|
||||
The packed runtime activation tensor contains the I8 activation rows and their floating-point row scales. Linear layers that share the same input and convrot group size reuse this packed tensor, avoiding repeated rotation and activation quantization within the graph.
|
||||
The packed runtime activation tensor contains the I8 activation rows and their floating-point row scales. Linear layers with an input scale of `1` that share the same input and convrot group size reuse this packed tensor, avoiding repeated rotation and activation quantization within the graph. For other input scales, activations are scaled before explicit convrot quantization, and the linear output is unscaled before adding bias.
|
||||
|
||||
## Backend support
|
||||
|
||||
- CPU provides the portable regular Hadamard, activation quantization, INT8 matrix multiplication, and scale restoration implementations.
|
||||
- NVIDIA CUDA devices with compute capability 7.5 or newer use the native accelerated path. For H256, CUDA fuses the rotation, row-wise maximum reduction, and activation quantization. It uses cuBLAS for I8 x I8 to I32 GEMM and a CUDA kernel for scale restoration and bias addition.
|
||||
- Vulkan and other GPU backends do not currently have dedicated INT8 convrot kernels. They use the backend scheduler to fall back to CPU, which is expected to be substantially slower than the CUDA path.
|
||||
- AMD HIP devices in the CDNA, RDNA3 (including RDNA3.5), and RDNA4 families use the same INT8/H256 kernels with hipBLAS for I8 x I8 to I32 GEMM. Other AMD architectures fall back to CPU for INT8 matrix multiplication.
|
||||
- Vulkan provides native H256 activation quantization and INT8 matrix multiplication when the build and device support accelerated packed INT8 dot products. Unsupported configurations and GPU backends without these kernels use the backend scheduler to fall back to CPU.
|
||||
|
||||
LoRA adapters are applied at runtime without modifying the INT8 weights. The INT8 convrot path computes the base linear output, while LoRA, LoHa, LoKr, and raw weight-difference adapters compute their output corrections from the original, unrotated activation and add them to the base output. `--lora-apply-mode auto` selects this path for models containing INT8 tensorwise weights. If `immediately` is requested, sd.cpp falls back to runtime application because merging an adapter would require dequantizing and rotating its weight update, then recalculating the per-row scales and requantizing the result.
|
||||
|
||||
The dedicated CUDA convrot activation path currently requires a group size of `256`; other supported group sizes use CPU execution.
|
||||
The dedicated CUDA and HIP convrot activation paths currently require a group size of `256`; other supported group sizes use CPU execution.
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Z-Image L2P
|
||||
|
||||
[L2P](https://nju-pcalab.github.io/projects/L2P/) (Latent-to-Pixel) transfers Z-Image-Turbo to pixel space. The VAE is replaced by 16x16 patch tokenization on RGB pixels and a small convolutional local decoder that turns the last DiT hidden states back into pixels, so no VAE is needed. The text encoder is the Qwen3-4B used by Z-Image-Turbo.
|
||||
|
||||
## Download weights
|
||||
|
||||
- Download L2P (1K)
|
||||
- safetensors: https://huggingface.co/zhen-nan/L2P/tree/main
|
||||
- Download Qwen3 4b
|
||||
- safetensors: https://huggingface.co/Comfy-Org/z_image_turbo/tree/main/split_files/text_encoders
|
||||
- gguf: https://huggingface.co/unsloth/Qwen3-4B-Instruct-2507-GGUF/tree/main
|
||||
|
||||
## Text-to-image
|
||||
|
||||
Do not pass `--vae`. Image dimensions must be multiples of 16. The reference pipeline uses 30 steps with a CFG scale of 2.0.
|
||||
|
||||
The released checkpoint keeps part of the transformer in F32. Pass `--type bf16` to load it in BF16, as the reference pipeline runs it; this reduces the diffusion model from about 18.6 GB to 11.8 GB.
|
||||
|
||||
```bash
|
||||
.\bin\Release\sd-cli.exe --diffusion-model ..\models\diffusion_models\model-1k-merge.safetensors --llm ..\models\text_encoders\qwen_3_4b.safetensors -p "an origami pig on fire in the middle of a dark room with a pentagram on the floor" --cfg-scale 2.0 --steps 30 --sampling-method euler -W 1024 -H 1024 --diffusion-fa --type bf16 -v -o z_image_l2p.png
|
||||
```
|
||||
+12
-1
@@ -716,6 +716,15 @@ int main(int argc, const char* argv[]) {
|
||||
}
|
||||
|
||||
if (cli_params.mode == CONVERT) {
|
||||
std::vector<sd_lora_t> loras;
|
||||
loras.reserve(gen_params.lora_map.size() + gen_params.high_noise_lora_map.size());
|
||||
for (const auto& [path, multiplier] : gen_params.lora_map) {
|
||||
loras.push_back({false, multiplier, path.c_str()});
|
||||
}
|
||||
for (const auto& [path, multiplier] : gen_params.high_noise_lora_map) {
|
||||
loras.push_back({true, multiplier, path.c_str()});
|
||||
}
|
||||
|
||||
bool success = convert_with_components(ctx_params.model_path.c_str(),
|
||||
ctx_params.clip_l_path.c_str(),
|
||||
ctx_params.clip_g_path.c_str(),
|
||||
@@ -726,7 +735,9 @@ int main(int argc, const char* argv[]) {
|
||||
ctx_params.wtype,
|
||||
ctx_params.tensor_type_rules.c_str(),
|
||||
cli_params.convert_name,
|
||||
ctx_params.n_threads);
|
||||
ctx_params.n_threads,
|
||||
loras.empty() ? nullptr : loras.data(),
|
||||
(int)loras.size());
|
||||
if (!success) {
|
||||
LOG_ERROR("convert '%s'/'%s' to '%s' failed",
|
||||
ctx_params.model_path.c_str(),
|
||||
|
||||
@@ -2271,14 +2271,15 @@ bool SDGenerationParams::from_json_str(
|
||||
return true;
|
||||
}
|
||||
|
||||
void SDGenerationParams::extract_and_remove_lora(const std::string& lora_model_dir) {
|
||||
bool SDGenerationParams::extract_and_remove_lora(const std::string& lora_model_dir) {
|
||||
if (lora_model_dir.empty()) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
static const std::regex re(R"(<lora:([^:>]+):([^>]+)>)");
|
||||
static const std::vector<std::string> valid_ext = {".gguf", ".safetensors", ".pt", ".ckpt"};
|
||||
std::smatch m;
|
||||
|
||||
bool missing = false;
|
||||
std::string tmp = prompt;
|
||||
|
||||
while (std::regex_search(tmp, m, re)) {
|
||||
@@ -2320,8 +2321,9 @@ void SDGenerationParams::extract_and_remove_lora(const std::string& lora_model_d
|
||||
}
|
||||
if (!found) {
|
||||
LOG_WARN("can not found lora %s", final_path.lexically_normal().string().c_str());
|
||||
tmp = m.suffix().str();
|
||||
prompt = std::regex_replace(prompt, re, "", std::regex_constants::format_first_only);
|
||||
tmp = m.suffix().str();
|
||||
prompt = std::regex_replace(prompt, re, "", std::regex_constants::format_first_only);
|
||||
missing = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -2337,6 +2339,8 @@ void SDGenerationParams::extract_and_remove_lora(const std::string& lora_model_d
|
||||
|
||||
tmp = m.suffix().str();
|
||||
}
|
||||
|
||||
return !missing;
|
||||
}
|
||||
|
||||
bool SDGenerationParams::width_and_height_are_set() const {
|
||||
@@ -2458,7 +2462,7 @@ bool SDGenerationParams::initialize_cache_params() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SDGenerationParams::resolve(const std::string& lora_model_dir, const std::string& hires_upscalers_dir, bool strict) {
|
||||
bool SDGenerationParams::resolve(const std::string& lora_model_dir, const std::string& hires_upscalers_dir, bool strict, bool validate_missing_loras) {
|
||||
vae_tiling_params.extra_tiling_args = extra_tiling_args.empty() ? nullptr : extra_tiling_args.c_str();
|
||||
|
||||
if (high_noise_sample_params.sample_steps <= 0) {
|
||||
@@ -2502,7 +2506,9 @@ bool SDGenerationParams::resolve(const std::string& lora_model_dir, const std::s
|
||||
|
||||
prompt_with_lora = prompt;
|
||||
if (!lora_model_dir.empty()) {
|
||||
extract_and_remove_lora(lora_model_dir);
|
||||
if (!extract_and_remove_lora(lora_model_dir) && validate_missing_loras) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -2630,7 +2636,7 @@ bool SDGenerationParams::resolve_and_validate(SDMode mode,
|
||||
const std::string& lora_model_dir,
|
||||
const std::string& hires_upscalers_dir,
|
||||
bool strict) {
|
||||
if (!resolve(lora_model_dir, hires_upscalers_dir, strict)) {
|
||||
if (!resolve(lora_model_dir, hires_upscalers_dir, strict, mode == CONVERT)) {
|
||||
return false;
|
||||
}
|
||||
if (!validate(mode)) {
|
||||
|
||||
@@ -313,12 +313,12 @@ struct SDGenerationParams {
|
||||
const std::function<std::string(const std::string&)>& lora_path_resolver = {});
|
||||
bool parse_image_preprocess_json(const std::string& json_str);
|
||||
bool initialize_cache_params();
|
||||
void extract_and_remove_lora(const std::string& lora_model_dir);
|
||||
bool extract_and_remove_lora(const std::string& lora_model_dir);
|
||||
bool width_and_height_are_set() const;
|
||||
void set_width_and_height_if_unset(int w, int h);
|
||||
int get_resolved_width() const;
|
||||
int get_resolved_height() const;
|
||||
bool resolve(const std::string& lora_model_dir, const std::string& hires_upscalers_dir, bool strict = false);
|
||||
bool resolve(const std::string& lora_model_dir, const std::string& hires_upscalers_dir, bool strict = false, bool validate_missing_loras = false);
|
||||
bool validate(SDMode mode);
|
||||
bool resolve_and_validate(SDMode mode,
|
||||
const std::string& lora_model_dir,
|
||||
|
||||
+18
-21
@@ -91,34 +91,34 @@ void log_print(enum sd_log_level_t level, const char* log, sd_log_level_t min_le
|
||||
switch (level) {
|
||||
case SD_LOG_DEBUG:
|
||||
tag_color = 37;
|
||||
level_str = "DEBUG";
|
||||
level_str = "D";
|
||||
break;
|
||||
case SD_LOG_VERBOSE:
|
||||
tag_color = 37;
|
||||
level_str = "VERBOSE";
|
||||
level_str = "V";
|
||||
break;
|
||||
case SD_LOG_INFO:
|
||||
tag_color = 34;
|
||||
level_str = "INFO";
|
||||
level_str = "I";
|
||||
break;
|
||||
case SD_LOG_WARN:
|
||||
tag_color = 35;
|
||||
level_str = "WARN";
|
||||
level_str = "W";
|
||||
break;
|
||||
case SD_LOG_ERROR:
|
||||
tag_color = 31;
|
||||
level_str = "ERROR";
|
||||
level_str = "E";
|
||||
break;
|
||||
default:
|
||||
tag_color = 33;
|
||||
level_str = "?????";
|
||||
level_str = "?";
|
||||
break;
|
||||
}
|
||||
|
||||
if (color) {
|
||||
fprintf(out_stream, "\033[%d;1m[%-7s]\033[0m ", tag_color, level_str);
|
||||
fprintf(out_stream, "\033[%d;1m[%s]\033[0m ", tag_color, level_str);
|
||||
} else {
|
||||
fprintf(out_stream, "[%-7s] ", level_str);
|
||||
fprintf(out_stream, "[%s] ", level_str);
|
||||
}
|
||||
fflush(out_stream);
|
||||
print_utf8(out_stream, log);
|
||||
@@ -131,18 +131,15 @@ void example_log_printf(sd_log_level_t level, const char* file, int line, const
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
|
||||
static char log_buffer[LOG_BUFFER_SIZE + 1];
|
||||
int written = snprintf(log_buffer, LOG_BUFFER_SIZE, "%s:%-4d - ", sd_basename(file).c_str(), line);
|
||||
|
||||
if (written >= 0 && written < static_cast<int>(LOG_BUFFER_SIZE)) {
|
||||
vsnprintf(log_buffer + written, LOG_BUFFER_SIZE - written, format, args);
|
||||
}
|
||||
size_t len = strlen(log_buffer);
|
||||
if (len == 0 || log_buffer[len - 1] != '\n') {
|
||||
strncat(log_buffer, "\n", LOG_BUFFER_SIZE - len);
|
||||
}
|
||||
|
||||
log_print(level, log_buffer, log_level, log_color);
|
||||
|
||||
char log_buffer[LOG_BUFFER_SIZE + 1] = {};
|
||||
vsnprintf(log_buffer, sizeof(log_buffer), format, args);
|
||||
va_end(args);
|
||||
|
||||
std::string message = log_buffer;
|
||||
while (!message.empty() && (message.back() == '\n' || message.back() == '\r')) {
|
||||
message.pop_back();
|
||||
}
|
||||
message += " - " + sd_basename(file) + ":" + std::to_string(line) + '\n';
|
||||
|
||||
log_print(level, message.c_str(), log_level, log_color);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ Current generation-related endpoints include:
|
||||
|
||||
- `POST /sdapi/v1/txt2img`
|
||||
- `POST /sdapi/v1/img2img`
|
||||
- `GET /sdapi/v1/progress`
|
||||
- `GET /sdapi/v1/loras`
|
||||
- `GET /sdapi/v1/upscalers`
|
||||
- `GET /sdapi/v1/latent-upscale-modes`
|
||||
@@ -278,6 +279,26 @@ Response fields:
|
||||
| `parameters` | `object` | Echo of the parsed outer request body |
|
||||
| `info` | `string` | Currently empty string |
|
||||
|
||||
#### `GET /sdapi/v1/progress`
|
||||
|
||||
Poll this endpoint while a synchronous SDAPI generation request is running.
|
||||
An optional `id_task` query parameter selects the task with the same `id_task`
|
||||
provided in the generation request (default: `"sdapi"`). Set
|
||||
`skip_current_image=true` or `1` to omit the preview image.
|
||||
|
||||
`current_image` contains the latest base64 JPEG latent projection, or `null`
|
||||
when unavailable. `state.sampling_step` is the positive logical step and
|
||||
`state.sampling_steps` is the actual step count for the current sampling pass,
|
||||
including schedule and img2img strength adjustments. `state.job_no` is the
|
||||
zero-based pass index; `state.job_count` includes one pass per batch image and
|
||||
an additional pass per image when highres fix is enabled. `progress` weights
|
||||
these passes equally. Sampling progress can reach `1` before final decoding
|
||||
finishes; `eta_relative` is currently always `0`.
|
||||
|
||||
When no SDAPI task is active, or `id_task` does not match, the endpoint returns
|
||||
`progress=0`, `state.job=""`, zero job/step counts, and `current_image=null`.
|
||||
Completed and failed requests do not retain an active preview.
|
||||
|
||||
#### Discovery / Compatibility Endpoints
|
||||
|
||||
Currently exposed:
|
||||
@@ -410,6 +431,20 @@ Field types:
|
||||
| `queue_position` | `integer` |
|
||||
| `result` | `object \| null` |
|
||||
| `error` | `object \| null` |
|
||||
| `preview` | `object \| null` |
|
||||
|
||||
`preview` sub-fields:
|
||||
|
||||
| Field | Type | Notes |
|
||||
| --- | --- | --- |
|
||||
| `preview.pass` | `integer` | Sampling pass number, starting at 1 within the job |
|
||||
| `preview.step` | `integer` | Positive logical sample step within this pass |
|
||||
| `preview.total_steps` | `integer` | Actual sample steps in this pass, after schedule and strength adjustments |
|
||||
| `preview.b64_json` | `string` | Base64-encoded PNG of the current preview frame |
|
||||
|
||||
The preview is updated in place as sampling progresses; poll `GET /sdcpp/v1/jobs/{id}` to retrieve the latest frame. Only the most recent preview is retained.
|
||||
|
||||
`step / total_steps` describes the current sampling pass, not overall job completion. A new batch item, high/low-noise stage, or highres pass increments `pass` and restarts `step`. Samplers with multiple denoiser evaluations per logical step may update a preview more than once at the same step. The frame and its pass/step metadata are published together.
|
||||
|
||||
### Endpoints
|
||||
|
||||
@@ -437,6 +472,7 @@ Top-level fields:
|
||||
| `loras` | `array<object>` | Available LoRA entries |
|
||||
| `upscalers` | `array<object>` | Available highres upscalers, built-in and model-backed |
|
||||
| `upscale` | `boolean` | Whether a compatible RGB ESRGAN model is available for `POST /sdcpp/v1/upscale` |
|
||||
| `preview_modes` | `array<string>` | Available preview modes, e.g. `["none", "proj", "tae", "vae"]` |
|
||||
| `limits` | `object` | Shared queue and size limits |
|
||||
|
||||
`model`
|
||||
@@ -598,6 +634,7 @@ Fields returned in `features_by_mode.img_gen`:
|
||||
- `cache`
|
||||
- `cancel_queued`
|
||||
- `cancel_generating`
|
||||
- `preview`
|
||||
|
||||
Fields returned in `features_by_mode.vid_gen`:
|
||||
|
||||
@@ -610,6 +647,7 @@ Fields returned in `features_by_mode.vid_gen`:
|
||||
- `cache`
|
||||
- `cancel_queued`
|
||||
- `cancel_generating`
|
||||
- `preview`
|
||||
|
||||
#### `POST /sdcpp/v1/img_gen`
|
||||
|
||||
@@ -782,6 +820,9 @@ Example:
|
||||
"scm_mask": "",
|
||||
"scm_policy_dynamic": true,
|
||||
|
||||
"preview": "none",
|
||||
"preview_interval": 1,
|
||||
|
||||
"output_format": "png",
|
||||
"output_compression": 100
|
||||
}
|
||||
@@ -847,6 +888,8 @@ Top-level scalar fields:
|
||||
| `control_strength` | `number` |
|
||||
| `ip_adapter_strength` | `number` |
|
||||
| `embed_image_metadata` | `boolean` |
|
||||
| `preview` | `string` |
|
||||
| `preview_interval` | `integer` |
|
||||
|
||||
Image fields:
|
||||
|
||||
@@ -934,6 +977,10 @@ When omitted, backend defaults apply to these fields:
|
||||
- `sample_params.flow_shift`
|
||||
- `sample_params.guidance.img_cfg`
|
||||
|
||||
### Preview Interval Semantics
|
||||
|
||||
`preview_interval` controls the period (in sample steps) at which preview frames are generated. The default is `1` (every step). Any non-positive values will be clamped to `1`. Note that if `preview` is set to `"none"`, no previews are produced regardless of the interval.
|
||||
|
||||
### Completion Result
|
||||
|
||||
Example completed job:
|
||||
@@ -1130,6 +1177,9 @@ Example:
|
||||
"scm_mask": "",
|
||||
"scm_policy_dynamic": true,
|
||||
|
||||
"preview": "none",
|
||||
"preview_interval": 1,
|
||||
|
||||
"output_format": "webm",
|
||||
"output_compression": 100
|
||||
}
|
||||
@@ -1181,6 +1231,8 @@ Top-level scalar fields:
|
||||
| `fps` | `integer` |
|
||||
| `moe_boundary` | `number` |
|
||||
| `vace_strength` | `number` |
|
||||
| `preview` | `string` |
|
||||
| `preview_interval` | `integer` |
|
||||
|
||||
Image and frame fields:
|
||||
|
||||
|
||||
@@ -10,6 +10,55 @@
|
||||
#include "common/media_io.h"
|
||||
#include "common/resource_owners.hpp"
|
||||
|
||||
static void preview_callback(int step, int frame_count, sd_image_t* frames, bool is_noisy, void* data) {
|
||||
(void)is_noisy;
|
||||
if (frame_count <= 0 || frames == nullptr || frames[0].data == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncGenerationJob* job = static_cast<AsyncGenerationJob*>(data);
|
||||
const sd_image_t& frame = frames[0];
|
||||
const sd_preview_info_t info = sd_get_preview_info();
|
||||
|
||||
auto image_bytes = encode_image_to_vector(EncodedImageFormat::PNG,
|
||||
frame.data,
|
||||
frame.width,
|
||||
frame.height,
|
||||
frame.channel,
|
||||
"",
|
||||
80);
|
||||
if (image_bytes.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string b64 = base64_encode(image_bytes);
|
||||
std::lock_guard<std::mutex> lock(job->preview_mutex);
|
||||
job->preview_b64 = std::move(b64);
|
||||
job->preview_step = step < 0 ? -step : step;
|
||||
job->preview_pass = info.sample_pass;
|
||||
job->preview_total_steps = info.total_steps;
|
||||
}
|
||||
|
||||
static void clear_preview_callback() {
|
||||
sd_set_preview_callback(nullptr, PREVIEW_NONE, 1, false, false, nullptr);
|
||||
}
|
||||
|
||||
static void set_preview_callback(AsyncGenerationJob& job) {
|
||||
preview_t mode = str_to_preview(job.kind == AsyncJobKind::ImgGen
|
||||
? job.img_gen.preview_mode.c_str()
|
||||
: job.vid_gen.preview_mode.c_str());
|
||||
if (mode == PREVIEW_COUNT || mode == PREVIEW_NONE) {
|
||||
return;
|
||||
}
|
||||
int interval = job.kind == AsyncJobKind::ImgGen
|
||||
? job.img_gen.preview_interval
|
||||
: job.vid_gen.preview_interval;
|
||||
if (interval <= 0) {
|
||||
interval = 1;
|
||||
}
|
||||
sd_set_preview_callback(preview_callback, mode, interval, true, false, &job);
|
||||
}
|
||||
|
||||
const char* async_job_kind_name(AsyncJobKind kind) {
|
||||
switch (kind) {
|
||||
case AsyncJobKind::ImgGen:
|
||||
@@ -161,6 +210,18 @@ json make_async_job_json(const AsyncJobManager& manager, const AsyncGenerationJo
|
||||
result["error"] = nullptr;
|
||||
}
|
||||
|
||||
if (job.status == AsyncJobStatus::Generating) {
|
||||
std::lock_guard<std::mutex> lock(job.preview_mutex);
|
||||
if (!job.preview_b64.empty()) {
|
||||
result["preview"] = {
|
||||
{"pass", job.preview_pass},
|
||||
{"step", job.preview_step},
|
||||
{"total_steps", job.preview_total_steps},
|
||||
{"b64_json", job.preview_b64},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -174,12 +235,14 @@ bool execute_img_gen_job(ServerRuntime& runtime,
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*runtime.sd_ctx_mutex);
|
||||
set_preview_callback(job);
|
||||
sd_image_t* raw_results = nullptr;
|
||||
int num_results = 0;
|
||||
if (!generate_image(runtime.sd_ctx, ¶ms, &raw_results, &num_results)) {
|
||||
raw_results = nullptr;
|
||||
num_results = 0;
|
||||
}
|
||||
clear_preview_callback();
|
||||
results.adopt(raw_results, num_results);
|
||||
}
|
||||
|
||||
@@ -247,10 +310,12 @@ bool execute_vid_gen_job(ServerRuntime& runtime,
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*runtime.sd_ctx_mutex);
|
||||
set_preview_callback(job);
|
||||
sd_image_t* raw_results = nullptr;
|
||||
if (!generate_video(runtime.sd_ctx, ¶ms, &raw_results, &num_results, &generated_audio, &output_fps)) {
|
||||
raw_results = nullptr;
|
||||
}
|
||||
clear_preview_callback();
|
||||
results.adopt(raw_results, num_results);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,11 @@ struct AsyncGenerationJob {
|
||||
int result_fps = 0;
|
||||
std::string error_code;
|
||||
std::string error_message;
|
||||
mutable std::mutex preview_mutex;
|
||||
std::string preview_b64;
|
||||
int preview_pass = 0;
|
||||
int preview_step = 0;
|
||||
int preview_total_steps = 0;
|
||||
};
|
||||
|
||||
struct AsyncJobManager {
|
||||
|
||||
@@ -301,6 +301,76 @@ static nlohmann::json prepare_info_field(const SDContextParams& ctx_params,
|
||||
return jsoninfo;
|
||||
}
|
||||
|
||||
struct SDAPIPreviewState {
|
||||
std::mutex mutex;
|
||||
std::string b64;
|
||||
std::string id_task;
|
||||
std::string job_type;
|
||||
int step = 0;
|
||||
int total = 0;
|
||||
bool active = false;
|
||||
int64_t job_count = 0;
|
||||
int job_no = 0;
|
||||
};
|
||||
|
||||
static SDAPIPreviewState g_preview;
|
||||
|
||||
static void preview_callback(int step, int frame_count, sd_image_t* frames, bool is_noisy, void* data) {
|
||||
(void)is_noisy;
|
||||
(void)data;
|
||||
if (frame_count <= 0 || frames == nullptr || frames[0].data == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sd_image_t& frame = frames[0];
|
||||
const sd_preview_info_t info = sd_get_preview_info();
|
||||
|
||||
auto image_bytes = encode_image_to_vector(EncodedImageFormat::JPEG,
|
||||
frame.data,
|
||||
frame.width,
|
||||
frame.height,
|
||||
frame.channel,
|
||||
"",
|
||||
80);
|
||||
if (image_bytes.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string b64 = base64_encode(image_bytes);
|
||||
std::lock_guard<std::mutex> lock(g_preview.mutex);
|
||||
g_preview.b64 = std::move(b64);
|
||||
g_preview.step = static_cast<int>(std::min<int64_t>(step < 0 ? -static_cast<int64_t>(step) : step,
|
||||
std::max(0, info.total_steps)));
|
||||
g_preview.total = std::max(0, info.total_steps);
|
||||
g_preview.job_no = std::max(0, info.sample_pass - 1);
|
||||
}
|
||||
|
||||
struct SDAPIPreviewGuard {
|
||||
SDAPIPreviewGuard(const std::string& id_task, const sd_img_gen_params_t& params, const std::string& job_type) {
|
||||
std::lock_guard<std::mutex> lock(g_preview.mutex);
|
||||
g_preview.id_task = id_task;
|
||||
g_preview.total = 0;
|
||||
g_preview.job_type = job_type;
|
||||
g_preview.b64.clear();
|
||||
g_preview.step = 0;
|
||||
g_preview.active = true;
|
||||
// Each batch item has a base pass and, when enabled, a hires pass.
|
||||
g_preview.job_count = static_cast<int64_t>(std::max(1, params.batch_count)) * (params.hires.enabled ? 2 : 1);
|
||||
g_preview.job_no = 0;
|
||||
sd_set_preview_callback(preview_callback, PREVIEW_PROJ, 1, true, false, nullptr);
|
||||
}
|
||||
|
||||
~SDAPIPreviewGuard() {
|
||||
sd_set_preview_callback(nullptr, PREVIEW_NONE, 1, false, false, nullptr);
|
||||
std::lock_guard<std::mutex> lock(g_preview.mutex);
|
||||
g_preview.active = false;
|
||||
g_preview.b64.clear();
|
||||
}
|
||||
|
||||
SDAPIPreviewGuard(const SDAPIPreviewGuard&) = delete;
|
||||
SDAPIPreviewGuard& operator=(const SDAPIPreviewGuard&) = delete;
|
||||
};
|
||||
|
||||
void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
|
||||
ServerRuntime* runtime = &rt;
|
||||
|
||||
@@ -317,7 +387,9 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
|
||||
return;
|
||||
}
|
||||
|
||||
json j = json::parse(req.body);
|
||||
std::string id_task = "sdapi";
|
||||
json j = json::parse(req.body);
|
||||
id_task = j.value("id_task", id_task);
|
||||
ImgGenJobRequest request;
|
||||
std::string error_message;
|
||||
if (!build_sdapi_img_gen_request(j, *runtime, img2img, request, error_message)) {
|
||||
@@ -334,6 +406,7 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*runtime->sd_ctx_mutex);
|
||||
SDAPIPreviewGuard preview_guard(id_task, img_gen_params, img2img ? "img2img" : "txt2img");
|
||||
sd_image_t* raw_results = nullptr;
|
||||
if (!generate_image(runtime->sd_ctx, &img_gen_params, &raw_results, &num_results)) {
|
||||
raw_results = nullptr;
|
||||
@@ -528,4 +601,62 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
|
||||
r["sd_model_checkpoint"] = model_path.stem();
|
||||
res.set_content(r.dump(), "application/json");
|
||||
});
|
||||
|
||||
svr.Get("/sdapi/v1/progress", [](const httplib::Request& req, httplib::Response& res) {
|
||||
bool skip_current_image = false;
|
||||
bool has_id_param = req.has_param("id_task");
|
||||
std::string req_id_task = has_id_param ? req.get_param_value("id_task") : "";
|
||||
|
||||
if (req.has_param("skip_current_image")) {
|
||||
std::string skip_str = req.get_param_value("skip_current_image");
|
||||
skip_current_image = (skip_str == "true" || skip_str == "1");
|
||||
}
|
||||
|
||||
json r;
|
||||
r["eta_relative"] = 0.0f;
|
||||
json state;
|
||||
state["skipped"] = false;
|
||||
state["interrupted"] = false;
|
||||
state["job_timestamp"] = "0";
|
||||
r["textinfo"] = nullptr;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_preview.mutex);
|
||||
|
||||
bool is_matching_task = g_preview.active && (!has_id_param || req_id_task == g_preview.id_task);
|
||||
|
||||
int step = is_matching_task ? g_preview.step : 0;
|
||||
int steps = is_matching_task ? g_preview.total : 0;
|
||||
std::string b64 = is_matching_task ? g_preview.b64 : "";
|
||||
std::string job_type = is_matching_task ? g_preview.job_type : "";
|
||||
int64_t job_count = is_matching_task ? g_preview.job_count : 0;
|
||||
int job_no = is_matching_task ? g_preview.job_no : 0;
|
||||
|
||||
float batch_progress = (steps > 0) ? (static_cast<float>(step) / static_cast<float>(steps)) : 0.0f;
|
||||
if (batch_progress > 1.0f)
|
||||
batch_progress = 1.0f;
|
||||
|
||||
float overall_progress = job_count > 0
|
||||
? (static_cast<float>(job_no) + batch_progress) / static_cast<float>(job_count)
|
||||
: 0.0f;
|
||||
if (overall_progress > 1.0f)
|
||||
overall_progress = 1.0f;
|
||||
|
||||
r["progress"] = overall_progress;
|
||||
state["job"] = job_type;
|
||||
state["sampling_step"] = step;
|
||||
state["sampling_steps"] = steps;
|
||||
state["job_count"] = job_count;
|
||||
state["job_no"] = job_no;
|
||||
r["state"] = state;
|
||||
|
||||
if (!skip_current_image && !b64.empty()) {
|
||||
r["current_image"] = b64;
|
||||
} else {
|
||||
r["current_image"] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
res.set_content(r.dump(), "application/json");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@ static json make_img_gen_features_json() {
|
||||
{"cache", true},
|
||||
{"cancel_queued", true},
|
||||
{"cancel_generating", false},
|
||||
{"preview", true},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -219,9 +220,18 @@ static json make_vid_gen_features_json() {
|
||||
{"cache", true},
|
||||
{"cancel_queued", true},
|
||||
{"cancel_generating", false},
|
||||
{"preview", true},
|
||||
};
|
||||
}
|
||||
|
||||
static json make_preview_modes_json() {
|
||||
json modes = json::array();
|
||||
for (int i = 0; i < PREVIEW_COUNT; ++i) {
|
||||
modes.push_back(sd_preview_name((preview_t)i));
|
||||
}
|
||||
return modes;
|
||||
}
|
||||
|
||||
static json make_capabilities_json(ServerRuntime& runtime) {
|
||||
refresh_lora_cache(runtime);
|
||||
refresh_upscaler_cache(runtime);
|
||||
@@ -396,6 +406,7 @@ static json make_capabilities_json(ServerRuntime& runtime) {
|
||||
result["loras"] = available_loras;
|
||||
result["upscalers"] = available_upscalers;
|
||||
result["upscale"] = have_upscaler_models;
|
||||
result["preview_modes"] = make_preview_modes_json();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -418,6 +429,8 @@ static bool parse_img_gen_request(const json& body,
|
||||
if (!assign_output_options(request, output_format, output_compression, true, error_message)) {
|
||||
return false;
|
||||
}
|
||||
request.preview_mode = body.value("preview", std::string("none"));
|
||||
request.preview_interval = body.value("preview_interval", 1);
|
||||
// Intentionally disable prompt-embedded LoRA tag parsing for server APIs.
|
||||
if (!request.gen_params.resolve_and_validate(IMG_GEN, "", runtime.ctx_params->hires_upscalers_dir, true)) {
|
||||
error_message = "invalid generation parameters";
|
||||
@@ -445,6 +458,8 @@ static bool parse_vid_gen_request(const json& body,
|
||||
if (!assign_output_options(request, output_format, output_compression, error_message)) {
|
||||
return false;
|
||||
}
|
||||
request.preview_mode = body.value("preview", std::string("none"));
|
||||
request.preview_interval = body.value("preview_interval", 1);
|
||||
// Intentionally disable prompt-embedded LoRA tag parsing for server APIs.
|
||||
if (!request.gen_params.resolve_and_validate(VID_GEN, "", runtime.ctx_params->hires_upscalers_dir, true)) {
|
||||
error_message = "invalid generation parameters";
|
||||
|
||||
@@ -66,6 +66,8 @@ struct ImgGenJobRequest {
|
||||
SDGenerationParams gen_params;
|
||||
std::string output_format = "png";
|
||||
int output_compression = 100;
|
||||
std::string preview_mode = "none";
|
||||
int preview_interval = 1;
|
||||
|
||||
sd_img_gen_params_t to_sd_img_gen_params_t() {
|
||||
return gen_params.to_sd_img_gen_params_t();
|
||||
@@ -76,6 +78,8 @@ struct VidGenJobRequest {
|
||||
SDGenerationParams gen_params;
|
||||
std::string output_format = "webm";
|
||||
int output_compression = 100;
|
||||
std::string preview_mode = "none";
|
||||
int preview_interval = 1;
|
||||
|
||||
sd_vid_gen_params_t to_sd_vid_gen_params_t() {
|
||||
return gen_params.to_sd_vid_gen_params_t();
|
||||
|
||||
+1
-1
Submodule ggml updated: 610265a4b0...89c4413f5d
@@ -461,6 +461,11 @@ struct ggml_tensor;
|
||||
typedef void (*sd_log_cb_t)(enum sd_log_level_t level, const char* text, void* data);
|
||||
typedef void (*sd_progress_cb_t)(int step, int steps, float time, void* data);
|
||||
typedef void (*sd_preview_cb_t)(int step, int frame_count, sd_image_t* frames, bool is_noisy, void* data);
|
||||
|
||||
typedef struct {
|
||||
int sample_pass;
|
||||
int total_steps;
|
||||
} sd_preview_info_t;
|
||||
typedef bool (*sd_graph_eval_callback_t)(struct ggml_tensor* t, bool ask, void* user_data);
|
||||
|
||||
SD_API void sd_set_log_callback(sd_log_cb_t sd_log_cb, void* data);
|
||||
@@ -469,6 +474,10 @@ SD_API void sd_set_progress_callback(sd_progress_cb_t cb, void* data);
|
||||
// negative interval previews only completed logical step -interval. Zero previews the final
|
||||
// completed step of the first sampling pass (base-resolution or high-noise).
|
||||
SD_API void sd_set_preview_callback(sd_preview_cb_t cb, enum preview_t mode, int interval, bool denoised, bool noisy, void* data);
|
||||
// Query from a preview callback. Passes are numbered from 1 since the last
|
||||
// sd_set_preview_callback call; total_steps is the actual count in that pass.
|
||||
// Both fields are zero before sampling starts.
|
||||
SD_API sd_preview_info_t sd_get_preview_info();
|
||||
SD_API void sd_set_backend_eval_callback(sd_graph_eval_callback_t cb, void* data);
|
||||
SD_API int32_t sd_get_num_physical_cores();
|
||||
SD_API const char* sd_get_system_info();
|
||||
@@ -601,7 +610,9 @@ SD_API bool convert_with_components(const char* model_path,
|
||||
enum sd_type_t output_type,
|
||||
const char* tensor_type_rules,
|
||||
bool convert_name,
|
||||
int n_threads);
|
||||
int n_threads,
|
||||
const sd_lora_t* loras,
|
||||
int lora_count);
|
||||
|
||||
SD_API bool preprocess_canny(sd_image_t image,
|
||||
float high_threshold,
|
||||
|
||||
@@ -1989,7 +1989,7 @@ struct LLMEmbedder : public Conditioner {
|
||||
sd_version_is_minimax_h3(version) ||
|
||||
sd_version_is_mage_flow(version)) {
|
||||
arch = LLM::LLMArch::QWEN3_VL;
|
||||
} else if (sd_version_is_z_image(version) || version == VERSION_OVIS_IMAGE || version == VERSION_FLUX2_KLEIN) {
|
||||
} else if (sd_version_is_z_image(version) || sd_version_is_z_image_l2p(version) || version == VERSION_OVIS_IMAGE || version == VERSION_FLUX2_KLEIN) {
|
||||
arch = LLM::LLMArch::QWEN3;
|
||||
}
|
||||
llm = std::make_shared<LLM::LLMRunner>(arch,
|
||||
@@ -2953,7 +2953,7 @@ struct LLMEmbedder : public Conditioner {
|
||||
prompt_attn_range.second = static_cast<int>(prompt.size());
|
||||
|
||||
prompt += "<|end|><|start|>assistant<|channel|>analysis<|message|>Need to generate one image according to the description.<|end|><|start|>assistant<|channel|>final<|message|>";
|
||||
} else if (sd_version_is_z_image(version)) {
|
||||
} else if (sd_version_is_z_image(version) || sd_version_is_z_image_l2p(version)) {
|
||||
prompt_template_encode_start_idx = 0;
|
||||
out_layers = {35}; // -2
|
||||
|
||||
|
||||
+240
-11
@@ -4,6 +4,7 @@
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <regex>
|
||||
@@ -11,11 +12,15 @@
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "core/ggml_extend_backend.h"
|
||||
#include "core/util.h"
|
||||
#include "model_io/gguf_io.h"
|
||||
#include "model_io/safetensors_io.h"
|
||||
#include "model_io/streaming_writer.h"
|
||||
#include "model_loader.h"
|
||||
#include "model_manager.h"
|
||||
#include "name_conversion.h"
|
||||
#include "runtime/imatrix.h"
|
||||
|
||||
struct TensorExportInfo {
|
||||
TensorStorage storage;
|
||||
@@ -75,9 +80,9 @@ static size_t export_tensor_nbytes(const TensorExportInfo& info) {
|
||||
return static_cast<size_t>(output_storage.nbytes());
|
||||
}
|
||||
|
||||
static TensorWritePlan tensor_write_plan_from_export_info(const TensorExportInfo& info) {
|
||||
static TensorWritePlan tensor_write_plan_from_export_info(const TensorExportInfo& info, const std::string& name) {
|
||||
TensorWritePlan plan;
|
||||
plan.name = info.storage.name;
|
||||
plan.name = name;
|
||||
plan.type = info.type;
|
||||
plan.n_dims = info.storage.n_dims;
|
||||
for (int i = 0; i < SD_MAX_DIMS; i++) {
|
||||
@@ -86,11 +91,14 @@ static TensorWritePlan tensor_write_plan_from_export_info(const TensorExportInfo
|
||||
return plan;
|
||||
}
|
||||
|
||||
static std::vector<TensorWritePlan> tensor_write_plans_from_export_infos(const std::vector<TensorExportInfo>& tensors) {
|
||||
static std::vector<TensorWritePlan> tensor_write_plans_from_export_infos(const std::vector<TensorExportInfo>& tensors,
|
||||
bool convert_name,
|
||||
SDVersion version) {
|
||||
std::vector<TensorWritePlan> plans;
|
||||
plans.reserve(tensors.size());
|
||||
for (const TensorExportInfo& info : tensors) {
|
||||
plans.push_back(tensor_write_plan_from_export_info(info));
|
||||
const std::string name = convert_name ? convert_tensor_name(info.storage.name, version) : info.storage.name;
|
||||
plans.push_back(tensor_write_plan_from_export_info(info, name));
|
||||
}
|
||||
return plans;
|
||||
}
|
||||
@@ -158,16 +166,42 @@ static bool load_tensor_for_export(ModelLoader& model_loader, TensorExportJob& j
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool export_tensor_from_memory(const TensorExportInfo& info,
|
||||
const std::map<std::string, ggml_tensor*>& mem,
|
||||
SDVersion version,
|
||||
std::vector<uint8_t>& out) {
|
||||
auto found = mem.find(info.storage.name);
|
||||
if (found == mem.end() || found->second == nullptr || found->second->data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const TensorStorage& storage = info.storage;
|
||||
out.resize(export_tensor_nbytes(info));
|
||||
if (out.size() > 0) {
|
||||
std::vector<float> imatrix = get_imatrix_collector().get_values(convert_tensor_name(info.storage.name, version));
|
||||
convert_tensor(found->second->data,
|
||||
found->second->type,
|
||||
out.data(),
|
||||
info.type,
|
||||
(int)(storage.nelements() / storage.ne[0]),
|
||||
(int)storage.ne[0],
|
||||
std::move(imatrix));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool stream_tensor_data(ModelLoader& model_loader,
|
||||
const std::string& output_path,
|
||||
const std::vector<TensorExportInfo>& tensors,
|
||||
const StreamingModelWriter& writer,
|
||||
int n_threads,
|
||||
const std::map<std::string, ggml_tensor*>* mem,
|
||||
std::string* error) {
|
||||
n_threads = n_threads > 0 ? n_threads : sd_get_num_physical_cores();
|
||||
n_threads = std::max(1, n_threads);
|
||||
LOG_INFO("streaming convert with %d threads", n_threads);
|
||||
|
||||
const SDVersion version = model_loader.get_sd_version();
|
||||
|
||||
int64_t start_time = ggml_time_ms();
|
||||
uint64_t bytes_written = 0;
|
||||
size_t tensors_written = 0;
|
||||
@@ -241,7 +275,10 @@ static bool stream_tensor_data(ModelLoader& model_loader,
|
||||
TensorExportJob job;
|
||||
job.info = tensors[tensor_index];
|
||||
try {
|
||||
job.success = load_tensor_for_export(model_loader, job);
|
||||
job.success = (mem != nullptr) && export_tensor_from_memory(job.info, *mem, version, job.data);
|
||||
if (!job.success) {
|
||||
job.success = load_tensor_for_export(model_loader, job);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
job.error = e.what();
|
||||
job.success = false;
|
||||
@@ -298,8 +335,10 @@ static bool write_model_file_streaming(ModelLoader& model_loader,
|
||||
const std::vector<TensorExportInfo>& tensors,
|
||||
StreamingModelWriter& writer,
|
||||
int n_threads,
|
||||
bool convert_name,
|
||||
const std::map<std::string, ggml_tensor*>* mem,
|
||||
std::string* error) {
|
||||
std::vector<TensorWritePlan> plans = tensor_write_plans_from_export_infos(tensors);
|
||||
std::vector<TensorWritePlan> plans = tensor_write_plans_from_export_infos(tensors, convert_name, model_loader.get_sd_version());
|
||||
if (!writer.write_metadata(output_path, plans, error)) {
|
||||
return false;
|
||||
}
|
||||
@@ -307,7 +346,7 @@ static bool write_model_file_streaming(ModelLoader& model_loader,
|
||||
return false;
|
||||
}
|
||||
model_loader.process_model_files(false, false);
|
||||
return stream_tensor_data(model_loader, output_path, tensors, writer, n_threads, error);
|
||||
return stream_tensor_data(model_loader, output_path, tensors, writer, n_threads, mem, error);
|
||||
}
|
||||
|
||||
static bool init_convert_path(ModelLoader& model_loader, const char* path, const char* prefix, bool& loaded_any) {
|
||||
@@ -326,7 +365,9 @@ static bool export_loaded_model(ModelLoader& model_loader,
|
||||
const char* output_path,
|
||||
sd_type_t output_type,
|
||||
const char* tensor_type_rules,
|
||||
int n_threads) {
|
||||
int n_threads,
|
||||
bool convert_name,
|
||||
const std::map<std::string, ggml_tensor*>* mem = nullptr) {
|
||||
ggml_type type = sd_type_to_ggml_type(output_type);
|
||||
bool output_is_safetensors = ends_with(output_path, ".safetensors");
|
||||
TensorTypeRules type_rules = parse_tensor_type_rules(tensor_type_rules);
|
||||
@@ -341,7 +382,7 @@ static bool export_loaded_model(ModelLoader& model_loader,
|
||||
} else {
|
||||
writer = std::make_unique<GGUFStreamingWriter>();
|
||||
}
|
||||
success = write_model_file_streaming(model_loader, output_path, tensors, *writer, n_threads, &error);
|
||||
success = write_model_file_streaming(model_loader, output_path, tensors, *writer, n_threads, convert_name, mem, &error);
|
||||
}
|
||||
|
||||
if (!success && !error.empty()) {
|
||||
@@ -351,6 +392,174 @@ static bool export_loaded_model(ModelLoader& model_loader,
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool has_active_loras(const sd_lora_t* loras, int lora_count) {
|
||||
if (loras == nullptr) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < lora_count; i++) {
|
||||
if (loras[i].multiplier != 0.0f) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::vector<ModelManager::LoraSpec> build_lora_specs(const sd_lora_t* loras, int lora_count) {
|
||||
std::vector<ModelManager::LoraSpec> specs;
|
||||
specs.reserve(lora_count);
|
||||
for (int i = 0; i < lora_count; i++) {
|
||||
ModelManager::LoraSpec spec;
|
||||
spec.path = loras[i].path != nullptr ? loras[i].path : "";
|
||||
spec.multiplier = loras[i].multiplier;
|
||||
spec.is_high_noise = loras[i].is_high_noise;
|
||||
spec.required = true;
|
||||
specs.push_back(std::move(spec));
|
||||
}
|
||||
return specs;
|
||||
}
|
||||
|
||||
static ModelComponent component_for_tensor_name(const std::string& name) {
|
||||
if (starts_with(name, "text_encoders.") || starts_with(name, "cond_stage_model.")) {
|
||||
return ModelComponent::Conditioner;
|
||||
}
|
||||
if (starts_with(name, "model.high_noise_diffusion_model.")) {
|
||||
return ModelComponent::HighNoiseDiffusion;
|
||||
}
|
||||
if (starts_with(name, "model.diffusion_model.")) {
|
||||
return ModelComponent::Diffusion;
|
||||
}
|
||||
if (starts_with(name, "vae.") || starts_with(name, "first_stage_model.")) {
|
||||
return ModelComponent::VAE;
|
||||
}
|
||||
return ModelComponent::Diffusion;
|
||||
}
|
||||
|
||||
static bool load_model_into_memory(ModelLoader& model_loader,
|
||||
const sd_lora_t* loras,
|
||||
int lora_count,
|
||||
int n_threads,
|
||||
ModelManager& manager,
|
||||
ggml_backend_t cpu,
|
||||
ggml_context* ctx,
|
||||
std::map<std::string, ggml_tensor*>& mem,
|
||||
std::vector<ggml_tensor*>& all_tensors,
|
||||
ggml_type output_type,
|
||||
const TensorTypeRules& type_rules) {
|
||||
const String2TensorStorage& storage_map = model_loader.get_tensor_storage_map();
|
||||
if (storage_map.empty()) {
|
||||
LOG_ERROR("no tensors to load into memory for convert");
|
||||
return false;
|
||||
}
|
||||
|
||||
const SDVersion version = model_loader.get_sd_version();
|
||||
ModelLoader merge_loader = model_loader;
|
||||
merge_loader.convert_tensors_name();
|
||||
|
||||
std::map<ModelComponent, std::map<std::string, ggml_tensor*>> groups;
|
||||
size_t total_bytes = 0;
|
||||
for (const auto& [name, storage] : storage_map) {
|
||||
ggml_type load_type = storage.type;
|
||||
const ggml_type export_type = get_export_tensor_type(model_loader, storage, output_type, type_rules);
|
||||
const bool quant_src = ggml_is_quantized(storage.type);
|
||||
const bool quant_dst = ggml_is_quantized(export_type);
|
||||
if (quant_src && quant_dst) {
|
||||
load_type = GGML_TYPE_F16;
|
||||
} else if (quant_src || quant_dst) {
|
||||
load_type = quant_src ? export_type : storage.type;
|
||||
} else if (storage.type == GGML_TYPE_F32 || export_type == GGML_TYPE_F32) {
|
||||
load_type = GGML_TYPE_F32;
|
||||
}
|
||||
ggml_tensor* tensor = ggml_new_tensor(ctx, load_type, storage.n_dims, storage.ne);
|
||||
if (tensor == nullptr) {
|
||||
LOG_ERROR("failed to create tensor '%s' for in-memory convert", name.c_str());
|
||||
return false;
|
||||
}
|
||||
const std::string merge_name = convert_tensor_name(name, version);
|
||||
auto& group = groups[component_for_tensor_name(merge_name)];
|
||||
if (!group.emplace(merge_name, tensor).second) {
|
||||
LOG_ERROR("duplicate tensor name '%s' after name conversion", merge_name.c_str());
|
||||
return false;
|
||||
}
|
||||
mem[name] = tensor;
|
||||
all_tensors.push_back(tensor);
|
||||
total_bytes += ggml_nbytes(tensor);
|
||||
}
|
||||
LOG_INFO("loading %zu tensors (%.2f GB) into memory for convert",
|
||||
all_tensors.size(), total_bytes / (1024.0 * 1024.0 * 1024.0));
|
||||
|
||||
manager.set_n_threads(n_threads);
|
||||
manager.set_enable_mmap(false);
|
||||
if (!manager.set_loader(std::move(merge_loader))) {
|
||||
LOG_ERROR("failed to set model loader for in-memory convert");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& [component, tensors] : groups) {
|
||||
if (!manager.register_param_tensors(component, tensors, ModelManager::ResidencyMode::ParamBackend, cpu, cpu)) {
|
||||
LOG_ERROR("failed to register %s tensors for in-memory convert", model_component_name(component));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ModelManager::LoraSpec> lora_specs = build_lora_specs(loras, lora_count);
|
||||
if (!manager.prepare_lora_sources(lora_specs)) {
|
||||
LOG_ERROR("failed to prepare LoRA sources for convert");
|
||||
return false;
|
||||
}
|
||||
if (!manager.set_loras(lora_specs, version)) {
|
||||
LOG_ERROR("failed to set LoRAs for convert");
|
||||
return false;
|
||||
}
|
||||
if (!manager.prepare_params(all_tensors)) {
|
||||
LOG_ERROR("failed to load model into memory for convert");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool convert_model_in_memory(ModelLoader& model_loader,
|
||||
const char* output_path,
|
||||
sd_type_t output_type,
|
||||
const char* tensor_type_rules,
|
||||
bool convert_name,
|
||||
int n_threads,
|
||||
const sd_lora_t* loras,
|
||||
int lora_count) {
|
||||
auto cpu = std::unique_ptr<ggml_backend, decltype(&ggml_backend_free)>(sd_backend_cpu_init(), ggml_backend_free);
|
||||
if (cpu == nullptr) {
|
||||
LOG_ERROR("failed to init CPU backend for in-memory convert");
|
||||
return false;
|
||||
}
|
||||
|
||||
ggml_init_params ctx_params;
|
||||
ctx_params.mem_size = model_loader.get_tensor_storage_map().size() * ggml_tensor_overhead();
|
||||
ctx_params.mem_buffer = nullptr;
|
||||
ctx_params.no_alloc = true;
|
||||
ggml_context* ctx = ggml_init(ctx_params);
|
||||
if (ctx == nullptr) {
|
||||
LOG_ERROR("ggml_init failed for in-memory convert");
|
||||
return false;
|
||||
}
|
||||
|
||||
ggml_type type = sd_type_to_ggml_type(output_type);
|
||||
TensorTypeRules type_rules = parse_tensor_type_rules(tensor_type_rules);
|
||||
|
||||
std::vector<ggml_tensor*> all_tensors;
|
||||
std::map<std::string, ggml_tensor*> mem;
|
||||
bool success = false;
|
||||
{
|
||||
// The manager owns the buffers backing `mem` and must outlive the export; it is
|
||||
// destroyed (freeing those buffers) before the tensor context is freed below.
|
||||
ModelManager manager;
|
||||
if (load_model_into_memory(model_loader, loras, lora_count, n_threads, manager, cpu.get(), ctx, mem, all_tensors, type, type_rules)) {
|
||||
success = export_loaded_model(model_loader, output_path, output_type, tensor_type_rules, n_threads, convert_name, &mem);
|
||||
}
|
||||
}
|
||||
ggml_free(ctx);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool convert_with_components(const char* model_path,
|
||||
const char* clip_l_path,
|
||||
const char* clip_g_path,
|
||||
@@ -361,10 +570,22 @@ bool convert_with_components(const char* model_path,
|
||||
sd_type_t output_type,
|
||||
const char* tensor_type_rules,
|
||||
bool convert_name,
|
||||
int n_threads) {
|
||||
int n_threads,
|
||||
const sd_lora_t* loras,
|
||||
int lora_count) {
|
||||
if (!validate_tensor_types(output_type, tensor_type_rules)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (loras != nullptr) {
|
||||
for (int i = 0; i < lora_count; i++) {
|
||||
LOG_INFO("lora %d: '%s' (multiplier %.2f%s)",
|
||||
i + 1, loras[i].path != nullptr ? loras[i].path : "",
|
||||
loras[i].multiplier,
|
||||
loras[i].is_high_noise ? ", high noise" : "");
|
||||
}
|
||||
}
|
||||
|
||||
ModelLoader model_loader;
|
||||
bool loaded_any = false;
|
||||
|
||||
@@ -382,11 +603,17 @@ bool convert_with_components(const char* model_path,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (has_active_loras(loras, lora_count)) {
|
||||
LOG_INFO("loading the full model into memory to apply LoRAs");
|
||||
return convert_model_in_memory(model_loader, output_path, output_type, tensor_type_rules, convert_name, n_threads,
|
||||
loras, lora_count);
|
||||
}
|
||||
|
||||
if (convert_name) {
|
||||
model_loader.convert_tensors_name();
|
||||
}
|
||||
|
||||
return export_loaded_model(model_loader, output_path, output_type, tensor_type_rules, n_threads);
|
||||
return export_loaded_model(model_loader, output_path, output_type, tensor_type_rules, n_threads, false, nullptr);
|
||||
}
|
||||
|
||||
bool convert(const char* input_path,
|
||||
@@ -405,5 +632,7 @@ bool convert(const char* input_path,
|
||||
output_type,
|
||||
tensor_type_rules,
|
||||
convert_name,
|
||||
0,
|
||||
nullptr,
|
||||
0);
|
||||
}
|
||||
|
||||
@@ -255,14 +255,20 @@ ggml_tensor* ggml_ext_linear_i8_tensorwise(ggml_context* ctx,
|
||||
}
|
||||
|
||||
ggml_tensor* fused_bias = scale == 1.f ? b : nullptr;
|
||||
auto mul_mat = [&](ggml_tensor* input) {
|
||||
if (input->type == GGML_TYPE_F32 && convrot_group_size > 0) {
|
||||
input = ggml_quantize_i8_convrot(ctx, input, convrot_group_size);
|
||||
}
|
||||
return ggml_mul_mat_i8_tensorwise(ctx, w, input, weight_scale, fused_bias, convrot_group_size);
|
||||
};
|
||||
if (x->ne[2] * x->ne[3] > 1024) {
|
||||
int64_t ne2 = x->ne[2];
|
||||
int64_t ne3 = x->ne[3];
|
||||
x = ggml_reshape_2d(ctx, x, x->ne[0], x->ne[1] * x->ne[2] * x->ne[3]);
|
||||
x = ggml_mul_mat_i8_tensorwise(ctx, w, x, weight_scale, fused_bias, convrot_group_size);
|
||||
x = mul_mat(x);
|
||||
x = ggml_reshape_4d(ctx, x, x->ne[0], x->ne[1] / ne2 / ne3, ne2, ne3);
|
||||
} else {
|
||||
x = ggml_mul_mat_i8_tensorwise(ctx, w, x, weight_scale, fused_bias, convrot_group_size);
|
||||
x = mul_mat(x);
|
||||
}
|
||||
|
||||
if (scale != 1.f) {
|
||||
|
||||
+28
-10
@@ -360,12 +360,13 @@ int32_t sd_get_num_physical_cores() {
|
||||
static sd_progress_cb_t sd_progress_cb = nullptr;
|
||||
void* sd_progress_cb_data = nullptr;
|
||||
|
||||
static sd_preview_cb_t sd_preview_cb = nullptr;
|
||||
static void* sd_preview_cb_data = nullptr;
|
||||
preview_t sd_preview_mode = PREVIEW_NONE;
|
||||
int sd_preview_interval = 1;
|
||||
bool sd_preview_denoised = true;
|
||||
bool sd_preview_noisy = false;
|
||||
static sd_preview_info_t sd_preview_info = {};
|
||||
static sd_preview_cb_t sd_preview_cb = nullptr;
|
||||
static void* sd_preview_cb_data = nullptr;
|
||||
preview_t sd_preview_mode = PREVIEW_NONE;
|
||||
int sd_preview_interval = 1;
|
||||
bool sd_preview_denoised = true;
|
||||
bool sd_preview_noisy = false;
|
||||
|
||||
static sd_graph_eval_callback_t sd_backend_eval_cb = nullptr;
|
||||
static void* sd_backend_eval_cb_data = nullptr;
|
||||
@@ -644,9 +645,14 @@ void* sd_log_cb_data = nullptr;
|
||||
static void sd_log_dispatch(sd_log_level_t level, const std::string& origin, const std::string& text) {
|
||||
if (sd_log_cb == nullptr)
|
||||
return;
|
||||
std::string message = origin + " - " + text;
|
||||
if (message.back() != '\n') {
|
||||
message += '\n';
|
||||
std::string message = text;
|
||||
while (!message.empty() && (message.back() == '\n' || message.back() == '\r')) {
|
||||
message.pop_back();
|
||||
}
|
||||
if (origin == "ggml") {
|
||||
message = origin + ": " + message + '\n';
|
||||
} else {
|
||||
message += " - " + origin + '\n';
|
||||
}
|
||||
sd_log_cb(level, message.c_str(), sd_log_cb_data);
|
||||
}
|
||||
@@ -656,7 +662,7 @@ void log_printf(sd_log_level_t level, const char* file, int line, const char* fo
|
||||
va_start(args, format);
|
||||
std::string message = sd_vformat(format, args);
|
||||
va_end(args);
|
||||
std::string origin = sd_format("%s:%-4d", sd_basename(file).c_str(), line);
|
||||
std::string origin = sd_format("%s:%d", sd_basename(file).c_str(), line);
|
||||
sd_log_dispatch(level, origin, message);
|
||||
}
|
||||
|
||||
@@ -691,6 +697,7 @@ void sd_set_progress_callback(sd_progress_cb_t cb, void* data) {
|
||||
sd_progress_cb_data = data;
|
||||
}
|
||||
void sd_set_preview_callback(sd_preview_cb_t cb, preview_t mode, int interval, bool denoised, bool noisy, void* data) {
|
||||
sd_preview_info = {};
|
||||
sd_preview_cb = cb;
|
||||
sd_preview_cb_data = data;
|
||||
sd_preview_mode = mode;
|
||||
@@ -704,6 +711,17 @@ void sd_set_backend_eval_callback(sd_graph_eval_callback_t cb, void* data) {
|
||||
sd_backend_eval_cb_data = data;
|
||||
}
|
||||
|
||||
void sd_begin_preview_pass(int total_steps) {
|
||||
if (sd_preview_cb != nullptr) {
|
||||
++sd_preview_info.sample_pass;
|
||||
sd_preview_info.total_steps = total_steps;
|
||||
}
|
||||
}
|
||||
|
||||
sd_preview_info_t sd_get_preview_info() {
|
||||
return sd_preview_info;
|
||||
}
|
||||
|
||||
sd_preview_cb_t sd_get_preview_callback() {
|
||||
return sd_preview_cb;
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ std::vector<std::pair<std::string, float>> split_quotation_attention(
|
||||
sd_progress_cb_t sd_get_progress_callback();
|
||||
void* sd_get_progress_callback_data();
|
||||
|
||||
void sd_begin_preview_pass(int total_steps);
|
||||
sd_preview_cb_t sd_get_preview_callback();
|
||||
void* sd_get_preview_callback_data();
|
||||
preview_t sd_get_preview_mode();
|
||||
|
||||
@@ -64,6 +64,7 @@ enum SDVersion {
|
||||
VERSION_ESRGAN,
|
||||
VERSION_PIXART,
|
||||
VERSION_MING_IMAGE,
|
||||
VERSION_Z_IMAGE_L2P,
|
||||
VERSION_COUNT,
|
||||
};
|
||||
|
||||
@@ -176,6 +177,10 @@ static inline bool sd_version_is_z_image(SDVersion version) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool sd_version_is_z_image_l2p(SDVersion version) {
|
||||
return version == VERSION_Z_IMAGE_L2P;
|
||||
}
|
||||
|
||||
static inline bool sd_version_is_llada_image(SDVersion version) {
|
||||
if (version == VERSION_LLADA_IMAGE) {
|
||||
return true;
|
||||
@@ -315,6 +320,7 @@ static inline bool sd_version_is_dit(SDVersion version) {
|
||||
version == VERSION_HIDREAM_O1 ||
|
||||
sd_version_is_anima(version) ||
|
||||
sd_version_is_z_image(version) ||
|
||||
sd_version_is_z_image_l2p(version) ||
|
||||
version == VERSION_MING_IMAGE ||
|
||||
sd_version_is_llada_image(version) ||
|
||||
sd_version_is_boogu_image(version) ||
|
||||
|
||||
@@ -205,6 +205,11 @@ public:
|
||||
force_prec_f32 = force_prec_f32_;
|
||||
}
|
||||
|
||||
bool has_quantized_weight() const {
|
||||
auto it = params.find("weight");
|
||||
return it != params.end() && it->second != nullptr && ggml_is_quantized(it->second->type);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) override {
|
||||
ggml_tensor* w = params["weight"];
|
||||
const float scale = ctx->linear_scale > 0.f ? ctx->linear_scale : this->scale;
|
||||
|
||||
@@ -1082,8 +1082,16 @@ namespace Rope {
|
||||
x = ggml_reshape_4d(ctx, x, 2, d_head / 2, L, n_head * N); // [N * n_head, L, d_head/2, 2]
|
||||
x = ggml_cont(ctx, ggml_permute(ctx, x, 3, 0, 1, 2)); // [2, N * n_head, L, d_head/2]
|
||||
} else {
|
||||
x = ggml_reshape_4d(ctx, x, d_head / 2, 2, L, n_head * N); // [N * n_head, L, 2, d_head/2]
|
||||
x = ggml_cont(ctx, ggml_ext_torch_permute(ctx, x, 0, 2, 3, 1)); // [2, N * n_head, L, d_head/2]
|
||||
auto x_0 = ggml_view_3d(ctx, x, d_head / 2, L, n_head * N, x->nb[1], x->nb[2], 0);
|
||||
auto x_1 = ggml_view_3d(ctx, x, d_head / 2, L, n_head * N, x->nb[1], x->nb[2], (d_head / 2) * x->nb[0]);
|
||||
|
||||
auto pe_2 = ggml_cont(ctx, ggml_permute(ctx, pe, 2, 3, 0, 1));
|
||||
auto cos = ggml_view_2d(ctx, pe_2, d_head / 2, L, pe_2->nb[1], 0);
|
||||
auto sin = ggml_view_2d(ctx, pe_2, d_head / 2, L, pe_2->nb[1], pe_2->nb[3]);
|
||||
|
||||
auto part_0 = ggml_sub(ctx, ggml_mul(ctx, x_0, cos), ggml_mul(ctx, x_1, sin));
|
||||
auto part_1 = ggml_add(ctx, ggml_mul(ctx, x_0, sin), ggml_mul(ctx, x_1, cos));
|
||||
return ggml_concat(ctx, part_0, part_1, 0);
|
||||
}
|
||||
|
||||
int64_t offset = x->nb[2] * x->ne[2];
|
||||
@@ -1101,10 +1109,7 @@ namespace Rope {
|
||||
auto pe_1 = ggml_view_3d(ctx, pe, pe->ne[0], pe->ne[1], pe->ne[2], pe->nb[1], pe->nb[2], offset * 1); // [L, d_head/2, 2]
|
||||
|
||||
auto x_out = ggml_add_inplace(ctx, ggml_mul(ctx, x_0, pe_0), ggml_mul(ctx, x_1, pe_1)); // [N * n_head, L, d_head/2, 2]
|
||||
if (!rope_interleaved) {
|
||||
x_out = ggml_cont(ctx, ggml_permute(ctx, x_out, 1, 0, 2, 3)); // [N * n_head, L, x, d_head/2]
|
||||
}
|
||||
x_out = ggml_reshape_3d(ctx, x_out, d_head, L, n_head * N); // [N*n_head, L, d_head]
|
||||
x_out = ggml_reshape_3d(ctx, x_out, d_head, L, n_head * N); // [N*n_head, L, d_head]
|
||||
return x_out;
|
||||
}
|
||||
|
||||
|
||||
@@ -168,13 +168,16 @@ namespace ZImage {
|
||||
int64_t N = x->ne[2];
|
||||
auto out_proj = std::dynamic_pointer_cast<Linear>(blocks[split_qkv ? "to_out.0" : "out"]);
|
||||
|
||||
if (out_proj->has_quantized_weight()) {
|
||||
out_proj->set_scale(1.f / 16.f);
|
||||
}
|
||||
|
||||
if (split_qkv) {
|
||||
auto q_proj = std::dynamic_pointer_cast<Linear>(blocks["to_q"]);
|
||||
auto k_proj = std::dynamic_pointer_cast<Linear>(blocks["to_k"]);
|
||||
auto v_proj = std::dynamic_pointer_cast<Linear>(blocks["to_v"]);
|
||||
|
||||
if (sd_backend_is(ctx->backend, "ROCm")) {
|
||||
out_proj->set_scale(1.f / 16.f);
|
||||
out_proj->set_force_prec_f32(true);
|
||||
q_proj->set_force_prec_f32(true);
|
||||
k_proj->set_force_prec_f32(true);
|
||||
@@ -197,7 +200,6 @@ namespace ZImage {
|
||||
auto qkv_proj = std::dynamic_pointer_cast<Linear>(blocks["qkv"]);
|
||||
|
||||
if (sd_backend_is(ctx->backend, "ROCm")) {
|
||||
out_proj->set_scale(1.f / 16.f);
|
||||
out_proj->set_force_prec_f32(true);
|
||||
qkv_proj->set_force_prec_f32(true);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
#ifndef __SD_MODEL_DIFFUSION_Z_IMAGE_L2P_HPP__
|
||||
#define __SD_MODEL_DIFFUSION_Z_IMAGE_L2P_HPP__
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "z_image.hpp"
|
||||
|
||||
// Ref: https://github.com/TencentYoutuResearch/T2I-L2P/blob/main/diffsynth/models/z_image_dit_L2P.py
|
||||
|
||||
namespace ZImageL2P {
|
||||
struct ZImageL2PConfig : ZImage::ZImageConfig {
|
||||
static ZImageL2PConfig detect_from_weights(const String2TensorStorage& tensors, const std::string& prefix) {
|
||||
ZImageL2PConfig config;
|
||||
static_cast<ZImage::ZImageConfig&>(config) = ZImage::ZImageConfig::detect_from_weights(tensors, prefix);
|
||||
config.in_channels = 3;
|
||||
config.out_channels = 3;
|
||||
config.patch_size = 16;
|
||||
auto x_embedder = tensors.find(prefix + ".x_embedder.weight");
|
||||
if (x_embedder != tensors.end()) {
|
||||
config.patch_size = static_cast<int>(std::lround(std::sqrt(static_cast<double>(x_embedder->second.ne[0] / config.in_channels))));
|
||||
}
|
||||
// L2P ships split to_q/to_k/to_v, which name conversion maps to qkv.weight plus
|
||||
// .1/.2 parts; the base detection only sees the q part and undercounts kv heads.
|
||||
auto k_part = tensors.find(prefix + ".layers.0.attention.qkv.weight.1");
|
||||
if (k_part != tensors.end()) {
|
||||
config.num_kv_heads = k_part->second.ne[1] / config.head_dim;
|
||||
}
|
||||
LOG_VERBOSE("z_image_l2p: patch_size = %d, in_channels = %" PRId64 ", num_heads = %" PRId64 ", num_kv_heads = %" PRId64,
|
||||
config.patch_size,
|
||||
config.in_channels,
|
||||
config.num_heads,
|
||||
config.num_kv_heads);
|
||||
return config;
|
||||
}
|
||||
};
|
||||
|
||||
// U-Net over the full-resolution noisy image. Its bottleneck sits at the DiT token
|
||||
// grid, so the number of pooling levels is tied to patch_size == 16.
|
||||
class LocalDecoder : public GGMLBlock {
|
||||
static constexpr int LEVELS = 4;
|
||||
static constexpr int64_t CHANNELS[LEVELS] = {64, 128, 256, 512};
|
||||
static constexpr int64_t BOTTLENECK_CHANNELS = 512;
|
||||
|
||||
public:
|
||||
LocalDecoder(int64_t in_channels, int64_t cond_channels) {
|
||||
int64_t prev = in_channels;
|
||||
for (int i = 0; i < LEVELS; ++i) {
|
||||
blocks["enc" + std::to_string(i + 1) + ".0"] = std::make_shared<Conv2d>(prev, CHANNELS[i], std::pair{3, 3}, std::pair{1, 1}, std::pair{1, 1});
|
||||
prev = CHANNELS[i];
|
||||
}
|
||||
blocks["bottleneck.0"] = std::make_shared<Conv2d>(prev + cond_channels, BOTTLENECK_CHANNELS, std::pair{1, 1});
|
||||
prev = BOTTLENECK_CHANNELS;
|
||||
for (int i = LEVELS - 1; i >= 0; --i) {
|
||||
const std::string level = std::to_string(i + 1);
|
||||
const int64_t out = i == 0 ? CHANNELS[0] : CHANNELS[i - 1];
|
||||
blocks["up" + level + ".1"] = std::make_shared<Conv2d>(prev, prev, std::pair{3, 3}, std::pair{1, 1}, std::pair{1, 1});
|
||||
blocks["dec" + level + ".0"] = std::make_shared<Conv2d>(prev + CHANNELS[i], out, std::pair{3, 3}, std::pair{1, 1}, std::pair{1, 1});
|
||||
prev = out;
|
||||
}
|
||||
blocks["out_conv"] = std::make_shared<Conv2d>(prev, in_channels, std::pair{1, 1});
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* cond) {
|
||||
// x: [N, C, H, W]
|
||||
// cond: [N, cond_channels, H / 16, W / 16]
|
||||
// return: [N, C, H, W]
|
||||
auto gctx = ctx->ggml_ctx;
|
||||
auto conv = [&](const std::string& name, ggml_tensor* h) {
|
||||
return std::dynamic_pointer_cast<Conv2d>(blocks[name])->forward(ctx, h);
|
||||
};
|
||||
|
||||
std::vector<ggml_tensor*> skips;
|
||||
auto h = x;
|
||||
for (int i = 0; i < LEVELS; ++i) {
|
||||
h = ggml_silu(gctx, conv("enc" + std::to_string(i + 1) + ".0", h));
|
||||
skips.push_back(h);
|
||||
h = ggml_pool_2d(gctx, h, GGML_OP_POOL_MAX, 2, 2, 2, 2, 0, 0);
|
||||
}
|
||||
|
||||
h = ggml_concat(gctx, h, cond, 2);
|
||||
h = ggml_silu(gctx, conv("bottleneck.0", h));
|
||||
|
||||
for (int i = LEVELS - 1; i >= 0; --i) {
|
||||
const std::string level = std::to_string(i + 1);
|
||||
h = ggml_upscale(gctx, h, 2, GGML_SCALE_MODE_NEAREST);
|
||||
h = conv("up" + level + ".1", h);
|
||||
h = ggml_concat(gctx, h, skips[i], 2);
|
||||
h = ggml_silu(gctx, conv("dec" + level + ".0", h));
|
||||
}
|
||||
return conv("out_conv", h);
|
||||
}
|
||||
};
|
||||
|
||||
class ZImageL2PModel : public GGMLBlock {
|
||||
ZImageL2PConfig config;
|
||||
|
||||
void init_params(ggml_context* ctx, const String2TensorStorage& tensor_storage_map = {}, const std::string prefix = "") override {
|
||||
params["cap_pad_token"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, config.hidden_size);
|
||||
params["x_pad_token"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, config.hidden_size);
|
||||
}
|
||||
|
||||
public:
|
||||
explicit ZImageL2PModel(const ZImageL2PConfig& config)
|
||||
: config(config) {
|
||||
blocks["x_embedder"] = std::make_shared<Linear>(config.patch_size * config.patch_size * config.in_channels, config.hidden_size);
|
||||
blocks["t_embedder"] = std::make_shared<TimestepEmbedder>(MIN(config.hidden_size, 1024), 256, 256);
|
||||
blocks["cap_embedder.0"] = std::make_shared<RMSNorm>(config.cap_feat_dim, config.norm_eps);
|
||||
blocks["cap_embedder.1"] = std::make_shared<Linear>(config.cap_feat_dim, config.hidden_size);
|
||||
auto add_blocks = [&](const std::string& prefix, int64_t count, bool modulation) {
|
||||
for (int64_t i = 0; i < count; ++i) {
|
||||
blocks[prefix + std::to_string(i)] = std::make_shared<ZImage::JointTransformerBlock>(
|
||||
static_cast<int>(i), config.hidden_size, config.head_dim, config.num_heads,
|
||||
config.num_kv_heads, config.multiple_of, config.ffn_dim_multiplier,
|
||||
config.norm_eps, config.qk_norm, modulation, true, false, 1e-5f);
|
||||
}
|
||||
};
|
||||
add_blocks("noise_refiner.", config.num_refiner_layers, true);
|
||||
add_blocks("context_refiner.", config.num_refiner_layers, false);
|
||||
add_blocks("layers.", config.num_layers, true);
|
||||
blocks["local_decoder"] = std::make_shared<LocalDecoder>(config.in_channels, config.hidden_size);
|
||||
}
|
||||
|
||||
ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x, ggml_tensor* timestep, ggml_tensor* context, ggml_tensor* pe) {
|
||||
// x: [N, C, H, W], H and W are multiples of patch_size
|
||||
// context: [N, L, cap_feat_dim]
|
||||
// return: [N, C, H, W]
|
||||
auto gctx = ctx->ggml_ctx;
|
||||
const int64_t W = x->ne[0];
|
||||
const int64_t H = x->ne[1];
|
||||
const int64_t N = x->ne[3];
|
||||
const int64_t w = W / config.patch_size;
|
||||
const int64_t h = H / config.patch_size;
|
||||
const int64_t n_img = w * h;
|
||||
const int64_t n_txt = context->ne[1];
|
||||
|
||||
auto img = DiT::patchify(gctx, x, config.patch_size, config.patch_size, false);
|
||||
img = std::dynamic_pointer_cast<Linear>(blocks["x_embedder"])->forward(ctx, img);
|
||||
auto txt = std::dynamic_pointer_cast<RMSNorm>(blocks["cap_embedder.0"])->forward(ctx, context);
|
||||
txt = std::dynamic_pointer_cast<Linear>(blocks["cap_embedder.1"])->forward(ctx, txt);
|
||||
auto t = std::dynamic_pointer_cast<TimestepEmbedder>(blocks["t_embedder"])->forward(ctx, timestep);
|
||||
sd::ggml_graph_cut::mark_graph_cut(txt, "z_image_l2p.prelude", "txt");
|
||||
sd::ggml_graph_cut::mark_graph_cut(img, "z_image_l2p.prelude", "img");
|
||||
sd::ggml_graph_cut::mark_graph_cut(t, "z_image_l2p.prelude", "t_emb");
|
||||
|
||||
const int64_t n_txt_pad = Rope::bound_mod(static_cast<int>(n_txt), ZImage::SEQ_MULTI_OF);
|
||||
if (n_txt_pad > 0) {
|
||||
auto pad = params["cap_pad_token"];
|
||||
txt = ggml_concat(gctx, txt, ggml_repeat_4d(gctx, pad, pad->ne[0], n_txt_pad, N, 1), 1);
|
||||
}
|
||||
const int64_t n_img_pad = Rope::bound_mod(static_cast<int>(n_img), ZImage::SEQ_MULTI_OF);
|
||||
if (n_img_pad > 0) {
|
||||
auto pad = params["x_pad_token"];
|
||||
img = ggml_concat(gctx, img, ggml_repeat_4d(gctx, pad, pad->ne[0], n_img_pad, N, 1), 1);
|
||||
}
|
||||
GGML_ASSERT(txt->ne[1] + img->ne[1] == pe->ne[3]);
|
||||
|
||||
auto txt_pe = ggml_ext_slice(gctx, pe, 3, 0, txt->ne[1]);
|
||||
auto img_pe = ggml_ext_slice(gctx, pe, 3, txt->ne[1], pe->ne[3]);
|
||||
for (int64_t i = 0; i < config.num_refiner_layers; ++i) {
|
||||
txt = std::dynamic_pointer_cast<ZImage::JointTransformerBlock>(blocks["context_refiner." + std::to_string(i)])->forward(ctx, txt, txt_pe);
|
||||
sd::ggml_graph_cut::mark_graph_cut(txt, "z_image_l2p.context_refiner." + std::to_string(i), "txt");
|
||||
}
|
||||
for (int64_t i = 0; i < config.num_refiner_layers; ++i) {
|
||||
img = std::dynamic_pointer_cast<ZImage::JointTransformerBlock>(blocks["noise_refiner." + std::to_string(i)])->forward(ctx, img, img_pe, nullptr, t);
|
||||
sd::ggml_graph_cut::mark_graph_cut(img, "z_image_l2p.noise_refiner." + std::to_string(i), "img");
|
||||
}
|
||||
|
||||
auto txt_img = ggml_concat(gctx, txt, img, 1);
|
||||
for (int64_t i = 0; i < config.num_layers; ++i) {
|
||||
txt_img = std::dynamic_pointer_cast<ZImage::JointTransformerBlock>(blocks["layers." + std::to_string(i)])->forward(ctx, txt_img, pe, nullptr, t);
|
||||
sd::ggml_graph_cut::mark_graph_cut(txt_img, "z_image_l2p.layers." + std::to_string(i), "txt_img");
|
||||
}
|
||||
|
||||
// The local decoder consumes the raw last-layer hidden states: there is no final
|
||||
// norm or adaLN modulation in front of it, unlike Z-Image's final_layer.
|
||||
const int64_t img_start = n_txt + n_txt_pad;
|
||||
auto feat = ggml_ext_slice(gctx, txt_img, 1, img_start, img_start + n_img); // [N, h*w, hidden_size]
|
||||
feat = ggml_reshape_4d(gctx, feat, config.hidden_size, w, h, N); // [N, h, w, hidden_size]
|
||||
feat = ggml_cont(gctx, ggml_permute(gctx, feat, 2, 0, 1, 3)); // [N, hidden_size, h, w]
|
||||
auto out = std::dynamic_pointer_cast<LocalDecoder>(blocks["local_decoder"])->forward(ctx, x, feat); // [N, C, H, W]
|
||||
return ggml_ext_scale(gctx, out, -1.f);
|
||||
}
|
||||
};
|
||||
|
||||
struct ZImageL2PRunner : public DiffusionModelRunner {
|
||||
ZImageL2PConfig config;
|
||||
ZImageL2PModel model;
|
||||
std::vector<float> pe_vec;
|
||||
|
||||
ZImageL2PRunner(ggml_backend_t backend,
|
||||
const String2TensorStorage& tensors,
|
||||
const std::string& prefix,
|
||||
std::shared_ptr<RunnerWeightManager> weight_manager = nullptr)
|
||||
: DiffusionModelRunner(backend, prefix, weight_manager),
|
||||
config(ZImageL2PConfig::detect_from_weights(tensors, prefix)),
|
||||
model(config) {
|
||||
model.init(params_ctx, tensors, prefix);
|
||||
}
|
||||
|
||||
std::string get_desc() override { return "z_image_l2p"; }
|
||||
|
||||
void get_param_tensors(std::map<std::string, ggml_tensor*>& tensors, const std::string& prefix) override {
|
||||
model.get_param_tensors(tensors, prefix);
|
||||
}
|
||||
|
||||
sd::Tensor<float> compute(int n_threads, const DiffusionParams& inputs) override {
|
||||
GGML_ASSERT(inputs.x != nullptr);
|
||||
GGML_ASSERT(inputs.timesteps != nullptr);
|
||||
if (inputs.ref_latents != nullptr && !inputs.ref_latents->empty()) {
|
||||
LOG_ERROR("Z-Image L2P reference-image conditioning is not supported");
|
||||
return {};
|
||||
}
|
||||
if (inputs.context == nullptr || inputs.context->empty()) {
|
||||
LOG_ERROR("Z-Image L2P requires a text condition");
|
||||
return {};
|
||||
}
|
||||
auto graph = [&]() {
|
||||
auto gf = new_graph_custom(ZImage::Z_IMAGE_GRAPH_SIZE);
|
||||
auto x = make_input(*inputs.x);
|
||||
auto t = make_input(*inputs.timesteps);
|
||||
auto context = make_input(*inputs.context);
|
||||
GGML_ASSERT(x->ne[3] == 1);
|
||||
GGML_ASSERT(x->ne[0] % config.patch_size == 0 && x->ne[1] % config.patch_size == 0);
|
||||
pe_vec = finish_rope_pe(Rope::gen_z_image_pe(static_cast<int>(x->ne[1]),
|
||||
static_cast<int>(x->ne[0]),
|
||||
config.patch_size,
|
||||
static_cast<int>(x->ne[3]),
|
||||
static_cast<int>(context->ne[1]),
|
||||
ZImage::SEQ_MULTI_OF,
|
||||
{},
|
||||
Rope::RefIndexMode::FIXED,
|
||||
config.theta,
|
||||
config.axes_dim));
|
||||
int pos_len = static_cast<int>(pe_vec.size() / config.axes_dim_sum / 2);
|
||||
auto pe = ggml_new_tensor_4d(compute_ctx, GGML_TYPE_F32, 2, 2, config.axes_dim_sum / 2, pos_len);
|
||||
set_backend_tensor_data(pe, pe_vec.data());
|
||||
auto ctx = get_context();
|
||||
auto out = model.forward(&ctx, x, t, context, pe);
|
||||
ggml_build_forward_expand(gf, out);
|
||||
return gf;
|
||||
};
|
||||
return restore_trailing_singleton_dims(GGMLRunner::compute(graph, n_threads, false), inputs.x->dim());
|
||||
}
|
||||
};
|
||||
} // namespace ZImageL2P
|
||||
|
||||
#endif // __SD_MODEL_DIFFUSION_Z_IMAGE_L2P_HPP__
|
||||
@@ -180,7 +180,7 @@ public:
|
||||
scale_factor = 16;
|
||||
} else if (sd_version_uses_flux2_vae(version)) {
|
||||
scale_factor = 16;
|
||||
} else if (version == VERSION_CHROMA_RADIANCE || version == VERSION_HIDREAM_O1 || sd_version_is_minit2i(version) || sd_version_is_sensenova_u1(version)) {
|
||||
} else if (version == VERSION_CHROMA_RADIANCE || version == VERSION_HIDREAM_O1 || sd_version_is_minit2i(version) || sd_version_is_sensenova_u1(version) || sd_version_is_z_image_l2p(version)) {
|
||||
scale_factor = 1;
|
||||
}
|
||||
return scale_factor;
|
||||
|
||||
@@ -115,7 +115,7 @@ void convert_tensor(void* src,
|
||||
ggml_type dst_type,
|
||||
int nrows,
|
||||
int n_per_row,
|
||||
std::vector<float> imatrix = {}) {
|
||||
std::vector<float> imatrix) {
|
||||
int n = nrows * n_per_row;
|
||||
if (src_type == dst_type) {
|
||||
size_t nbytes = n * ggml_type_size(src_type) / ggml_blck_size(src_type);
|
||||
@@ -527,6 +527,9 @@ SDVersion ModelLoader::get_sd_version() const {
|
||||
if (tensor_storage_map.find("text_encoders.llm.connector.layers.0.self_attn.q_proj.weight") != tensor_storage_map.end()) {
|
||||
return VERSION_MING_IMAGE;
|
||||
}
|
||||
if (tensor_storage_map.find("model.diffusion_model.local_decoder.out_conv.weight") != tensor_storage_map.end()) {
|
||||
return VERSION_Z_IMAGE_L2P;
|
||||
}
|
||||
return VERSION_Z_IMAGE;
|
||||
}
|
||||
if (tensor_storage.name.find("double_stream_layers.0.img_instruct_attn.processor.img_to_q.weight") != std::string::npos) {
|
||||
|
||||
@@ -13,6 +13,14 @@
|
||||
|
||||
TensorTypeRules parse_tensor_type_rules(const std::string& tensor_type_rules);
|
||||
|
||||
void convert_tensor(void* src,
|
||||
ggml_type src_type,
|
||||
void* dst,
|
||||
ggml_type dst_type,
|
||||
int nrows,
|
||||
int n_per_row,
|
||||
std::vector<float> imatrix = {});
|
||||
|
||||
class MmapWrapper;
|
||||
|
||||
struct ModelFileData {
|
||||
|
||||
@@ -824,6 +824,7 @@ std::string convert_diffusers_dit_to_original_lumina2(std::string name) {
|
||||
if (z_image_name_map.empty()) {
|
||||
z_image_name_map["all_x_embedder.2-1."] = "x_embedder.";
|
||||
z_image_name_map["all_final_layer.2-1."] = "final_layer.";
|
||||
z_image_name_map["all_x_embedder.16-1."] = "x_embedder.";
|
||||
|
||||
// --- transformer blocks ---
|
||||
auto add_attention_map = [&](const std::string& prefix, int num) {
|
||||
@@ -1036,7 +1037,7 @@ std::string convert_diffusion_model_name(std::string name, std::string prefix, S
|
||||
name = convert_hunyuan_video_to_original_flux(name);
|
||||
} else if (version == VERSION_MING_IMAGE) {
|
||||
name = convert_ming_image_dit_name(name);
|
||||
} else if (sd_version_is_z_image(version)) {
|
||||
} else if (sd_version_is_z_image(version) || sd_version_is_z_image_l2p(version)) {
|
||||
name = convert_diffusers_dit_to_original_lumina2(name);
|
||||
} else if (sd_version_is_llada_image(version)) {
|
||||
name = convert_diffusers_dit_to_original_llada_image(name);
|
||||
|
||||
@@ -107,6 +107,7 @@ const char* model_version_to_str[] = {
|
||||
"ESRGAN",
|
||||
"PixArt",
|
||||
"Ming-Image",
|
||||
"Z-Image L2P",
|
||||
};
|
||||
|
||||
static_assert(VERSION_COUNT == sizeof(model_version_to_str) / sizeof(model_version_to_str[0]),
|
||||
@@ -1376,6 +1377,7 @@ bool StableDiffusionGGML::build_denoiser() {
|
||||
sd_version_is_anima(version) ||
|
||||
sd_version_is_ernie_image(version) ||
|
||||
sd_version_is_z_image(version) ||
|
||||
sd_version_is_z_image_l2p(version) ||
|
||||
version == VERSION_MING_IMAGE ||
|
||||
sd_version_is_llada_image(version) ||
|
||||
sd_version_is_boogu_image(version) ||
|
||||
@@ -2172,7 +2174,7 @@ std::vector<float> StableDiffusionGGML::prepare_sample_timesteps(float sigma,
|
||||
if (version == VERSION_HIDREAM_O1) {
|
||||
return std::vector<float>{1.0f - (t / static_cast<float>(TIMESTEPS))};
|
||||
}
|
||||
if (sd_version_is_z_image(version) || sd_version_is_ideogram4(version) || version == VERSION_MING_IMAGE) {
|
||||
if (sd_version_is_z_image(version) || sd_version_is_z_image_l2p(version) || sd_version_is_ideogram4(version) || version == VERSION_MING_IMAGE) {
|
||||
return std::vector<float>{1000.f - t};
|
||||
}
|
||||
return std::vector<float>{t};
|
||||
@@ -2371,7 +2373,8 @@ sd::Tensor<float> StableDiffusionGGML::sample(const std::shared_ptr<DiffusionMod
|
||||
noise *= eta;
|
||||
}
|
||||
|
||||
int64_t last_progress_us = ggml_time_us();
|
||||
int64_t last_progress_us = ggml_time_us();
|
||||
sd_begin_preview_pass(static_cast<int>(steps));
|
||||
SamplePreviewContext preview = prepare_sample_preview_context();
|
||||
|
||||
sd::Tensor<float> processed_init_latent = denoiser->process_latent_in(init_latent);
|
||||
@@ -2762,6 +2765,8 @@ int StableDiffusionGGML::get_diffusion_model_down_factor() {
|
||||
if (sd_version_is_dit(version)) {
|
||||
if (sd_version_is_sensenova_u1(version)) {
|
||||
down_factor = 32;
|
||||
} else if (sd_version_is_z_image_l2p(version)) {
|
||||
down_factor = 16;
|
||||
} else if (version == VERSION_QWEN_IMAGE_2_1 || version == VERSION_MING_IMAGE || sd_version_is_wan(version) || sd_version_is_lingbot_video(version) || sd_version_is_minimax_h3(version) || sd_version_is_pixart(version)) {
|
||||
down_factor = 2;
|
||||
} else {
|
||||
@@ -2792,6 +2797,8 @@ int StableDiffusionGGML::get_latent_channel() {
|
||||
latent_channel = 3;
|
||||
} else if (sd_version_is_sensenova_u1(version)) {
|
||||
latent_channel = 3;
|
||||
} else if (sd_version_is_z_image_l2p(version)) {
|
||||
latent_channel = 3;
|
||||
} else if (sd_version_is_pid(version)) {
|
||||
latent_channel = 3;
|
||||
} else if (sd_version_is_sefi_image(version)) {
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include "model/diffusion/unet.hpp"
|
||||
#include "model/diffusion/wan.hpp"
|
||||
#include "model/diffusion/z_image.hpp"
|
||||
#include "model/diffusion/z_image_l2p.hpp"
|
||||
#include "model/vae/auto_encoder_kl.hpp"
|
||||
#include "model/vae/hunyuan_vae.hpp"
|
||||
#include "model/vae/ltx_audio_vae.hpp"
|
||||
@@ -393,6 +394,18 @@ namespace sd::model_builders {
|
||||
"model.diffusion_model",
|
||||
version,
|
||||
weight_manager);
|
||||
} else if (sd_version_is_z_image_l2p(version)) {
|
||||
result.conditioner = std::make_shared<LLMEmbedder>(ctx.backends.runtime_backend(SDBackendModule::TE),
|
||||
tensor_storage_map,
|
||||
version,
|
||||
"",
|
||||
false,
|
||||
weight_manager,
|
||||
tokenizers);
|
||||
result.diffusion = std::make_shared<ZImageL2P::ZImageL2PRunner>(ctx.backends.runtime_backend(SDBackendModule::DIFFUSION),
|
||||
tensor_storage_map,
|
||||
"model.diffusion_model",
|
||||
weight_manager);
|
||||
} else if (sd_version_is_llada_image(version)) {
|
||||
result.conditioner = std::make_shared<LLaDAImageEmbedder>(ctx.backends.runtime_backend(SDBackendModule::TE),
|
||||
tensor_storage_map,
|
||||
@@ -613,7 +626,7 @@ namespace sd::model_builders {
|
||||
}
|
||||
};
|
||||
|
||||
if (version == VERSION_CHROMA_RADIANCE || version == VERSION_HIDREAM_O1 || sd_version_is_minit2i(version) || sd_version_is_sensenova_u1(version)) {
|
||||
if (version == VERSION_CHROMA_RADIANCE || version == VERSION_HIDREAM_O1 || sd_version_is_minit2i(version) || sd_version_is_sensenova_u1(version) || sd_version_is_z_image_l2p(version)) {
|
||||
LOG_INFO("using FakeVAE");
|
||||
result.vae = std::make_shared<FakeVAE>(version,
|
||||
ctx.backends.runtime_backend(SDBackendModule::VAE),
|
||||
|
||||
+12
-10
@@ -375,17 +375,19 @@ namespace sd::pipeline {
|
||||
total_steps = sample_steps + std::max(0, high_noise_sample_steps);
|
||||
|
||||
if (sample_params->custom_sigmas_count > 0) {
|
||||
sigmas = std::vector<float>(sample_params->custom_sigmas,
|
||||
sigmas = std::vector<float>(sample_params->custom_sigmas,
|
||||
sample_params->custom_sigmas + sample_params->custom_sigmas_count);
|
||||
total_steps = static_cast<int>(sigmas.size()) - 1;
|
||||
LOG_WARN("total_steps != custom_sigmas_count - 1, set total_steps to %d", total_steps);
|
||||
if (sample_steps >= total_steps) {
|
||||
sample_steps = total_steps;
|
||||
LOG_WARN("total_steps != custom_sigmas_count - 1, set sample_steps to %d", sample_steps);
|
||||
}
|
||||
if (high_noise_sample_steps > 0) {
|
||||
high_noise_sample_steps = total_steps - sample_steps;
|
||||
LOG_WARN("total_steps != custom_sigmas_count - 1, set high_noise_sample_steps to %d", high_noise_sample_steps);
|
||||
if (total_steps != sample_params->custom_sigmas_count - 1) {
|
||||
total_steps = sample_params->custom_sigmas_count - 1;
|
||||
LOG_WARN("total_steps != custom_sigmas_count - 1, set total_steps to %d", total_steps);
|
||||
if ((sample_steps > total_steps) || (high_noise_sample_steps <= 0 && sample_steps != total_steps)) {
|
||||
sample_steps = total_steps;
|
||||
LOG_WARN("total_steps != custom_sigmas_count - 1, set sample_steps to %d", sample_steps);
|
||||
}
|
||||
if (high_noise_sample_steps > 0) {
|
||||
high_noise_sample_steps = total_steps - sample_steps;
|
||||
LOG_WARN("total_steps != custom_sigmas_count - 1, set high_noise_sample_steps to %d", high_noise_sample_steps);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
scheduler_t scheduler = resolve_scheduler(sd,
|
||||
|
||||
+105
-165
@@ -8,54 +8,51 @@
|
||||
#include "core/util.h"
|
||||
#include "ggml.h"
|
||||
|
||||
static void sd_tiling_calc_tiles(int& num_tiles_dim,
|
||||
float& tile_overlap_factor_dim,
|
||||
int small_dim,
|
||||
int tile_size,
|
||||
const float tile_overlap_factor,
|
||||
bool circular) {
|
||||
int tile_overlap = static_cast<int>(tile_size * tile_overlap_factor);
|
||||
int non_tile_overlap = tile_size - tile_overlap;
|
||||
struct TileSpan {
|
||||
int offset;
|
||||
int overlap_before;
|
||||
int overlap_after;
|
||||
};
|
||||
|
||||
if (circular) {
|
||||
// circular means the last and first tile are overlapping (wraping around)
|
||||
num_tiles_dim = small_dim / non_tile_overlap;
|
||||
|
||||
if (num_tiles_dim < 1) {
|
||||
num_tiles_dim = 1;
|
||||
}
|
||||
|
||||
tile_overlap_factor_dim = (tile_size - small_dim / num_tiles_dim) / (float)tile_size;
|
||||
|
||||
// if single tile and tile_overlap_factor is not 0, add one to ensure we have at least two overlapping tiles
|
||||
if (num_tiles_dim == 1 && tile_overlap_factor_dim > 0) {
|
||||
num_tiles_dim++;
|
||||
tile_overlap_factor_dim = 0.5;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
// else, non-circular means the last and first tile are not overlapping
|
||||
|
||||
num_tiles_dim = (small_dim - tile_overlap) / non_tile_overlap;
|
||||
int overshoot_dim = ((num_tiles_dim + 1) * non_tile_overlap + tile_overlap) % small_dim;
|
||||
|
||||
if ((overshoot_dim != non_tile_overlap) && (overshoot_dim <= num_tiles_dim * (tile_size / 2 - tile_overlap))) {
|
||||
// if tiles don't fit perfectly using the desired overlap
|
||||
// and there is enough room to squeeze an extra tile without overlap becoming >0.5
|
||||
num_tiles_dim++;
|
||||
static std::vector<TileSpan> sd_tiling_plan_axis(int dimension,
|
||||
int tile_size,
|
||||
float target_overlap,
|
||||
bool circular) {
|
||||
if (tile_size >= dimension) {
|
||||
return {{0, 0, 0}};
|
||||
}
|
||||
|
||||
tile_overlap_factor_dim = (float)(tile_size * num_tiles_dim - small_dim) / (float)(tile_size * (num_tiles_dim - 1));
|
||||
if (num_tiles_dim <= 2) {
|
||||
if (small_dim <= tile_size) {
|
||||
num_tiles_dim = 1;
|
||||
tile_overlap_factor_dim = 0;
|
||||
} else {
|
||||
num_tiles_dim = 2;
|
||||
tile_overlap_factor_dim = (2 * tile_size - small_dim) / (float)tile_size;
|
||||
}
|
||||
const int extent = circular ? dimension : dimension - tile_size;
|
||||
int intervals = extent;
|
||||
if (tile_size > 1) {
|
||||
const float target = extent / ((1.0f - target_overlap) * tile_size);
|
||||
// Adjacent tiles must overlap, but three tiles must not cover the same point.
|
||||
const int min_intervals = 1 + (extent - 1) / (tile_size - 1);
|
||||
const int max_intervals = std::max(1, static_cast<int>(2LL * extent / tile_size));
|
||||
const int lower = std::clamp(static_cast<int>(std::floor(target)), min_intervals, max_intervals);
|
||||
const int upper = std::clamp(static_cast<int>(std::ceil(target)), min_intervals, max_intervals);
|
||||
auto error = [&](int count) {
|
||||
return std::abs(1.0f / count - 1.0f / target);
|
||||
};
|
||||
intervals = error(upper) < error(lower) ? upper : lower;
|
||||
}
|
||||
|
||||
const int num_tiles = circular ? intervals : intervals + 1;
|
||||
auto position = [&](int index) -> int {
|
||||
return static_cast<int>(int64_t(index) * extent / intervals);
|
||||
};
|
||||
std::vector<TileSpan> tiles;
|
||||
tiles.reserve(num_tiles);
|
||||
for (int i = 0; i < num_tiles; ++i) {
|
||||
const int offset = position(i);
|
||||
// Keep wrapped neighbors in the same unwrapped coordinate space.
|
||||
const int previous = i > 0 ? position(i - 1) : position(num_tiles - 1) - dimension;
|
||||
const int next = i + 1 < num_tiles ? position(i + 1) : dimension;
|
||||
const int before = i > 0 || circular ? previous + tile_size - offset : 0;
|
||||
const int after = i + 1 < num_tiles || circular ? offset + tile_size - next : 0;
|
||||
tiles.push_back({offset, before, after});
|
||||
}
|
||||
return tiles;
|
||||
}
|
||||
|
||||
static int64_t sd_tensor_plane_size(const sd::Tensor<float>& tensor) {
|
||||
@@ -90,49 +87,50 @@ static void sd_tensor_merge_2d(const sd::Tensor<float>& input,
|
||||
sd::Tensor<float>* output,
|
||||
int x,
|
||||
int y,
|
||||
int overlap_x,
|
||||
int overlap_y,
|
||||
bool circular_x,
|
||||
bool circular_y,
|
||||
int x_skip,
|
||||
int y_skip) {
|
||||
int overlap_left,
|
||||
int overlap_right,
|
||||
int overlap_top,
|
||||
int overlap_bottom) {
|
||||
GGML_ASSERT(output != nullptr);
|
||||
int64_t width = input.shape()[0];
|
||||
int64_t height = input.shape()[1];
|
||||
int64_t img_width = output->shape()[0];
|
||||
int64_t img_height = output->shape()[1];
|
||||
int64_t input_plane = sd_tensor_plane_size(input);
|
||||
int64_t output_plane = sd_tensor_plane_size(*output);
|
||||
int64_t plane_count = input.numel() / input_plane;
|
||||
GGML_ASSERT(output->numel() / output_plane == plane_count);
|
||||
|
||||
// unclamped -> expects x in the range [0-1]
|
||||
int64_t in_width = input.shape()[0];
|
||||
int64_t in_height = input.shape()[1];
|
||||
int64_t out_width = output->shape()[0];
|
||||
int64_t out_height = output->shape()[1];
|
||||
int64_t in_size = sd_tensor_plane_size(input);
|
||||
int64_t out_size = sd_tensor_plane_size(*output);
|
||||
int64_t plane_count = input.numel() / in_size;
|
||||
|
||||
GGML_ASSERT(output->numel() == plane_count * out_size);
|
||||
GGML_ASSERT(x >= 0 && y >= 0);
|
||||
GGML_ASSERT(x < out_width && in_width <= out_width);
|
||||
GGML_ASSERT(y < out_height && in_height <= out_height);
|
||||
GGML_ASSERT(overlap_left >= 0 && overlap_right >= 0);
|
||||
GGML_ASSERT(overlap_top >= 0 && overlap_bottom >= 0);
|
||||
|
||||
auto smootherstep_f32 = [](const float x) -> float {
|
||||
GGML_ASSERT(x >= 0.f && x <= 1.f);
|
||||
return x * x * x * (x * (6.0f * x - 15.0f) + 10.0f);
|
||||
};
|
||||
|
||||
for (int iy = y_skip; iy < height; iy++) {
|
||||
for (int ix = x_skip; ix < width; ix++) {
|
||||
int64_t src_xy = ix + width * iy;
|
||||
int64_t ox = (x + ix) % img_width;
|
||||
int64_t oy = (y + iy) % img_height;
|
||||
int64_t dst_xy = ox + img_width * oy;
|
||||
for (int64_t plane = 0; plane < plane_count; ++plane) {
|
||||
float new_value = input[plane * input_plane + src_xy];
|
||||
if (overlap_x > 0 || overlap_y > 0) {
|
||||
float old_value = (*output)[plane * output_plane + dst_xy];
|
||||
const float x_f_0 = (circular_x || (overlap_x > 0 && x > 0)) ? (ix - x_skip) / float(overlap_x) : 1.f;
|
||||
const float x_f_1 = (circular_x || (overlap_x > 0 && x < (img_width - width))) ? (width - ix) / float(overlap_x) : 1.f;
|
||||
const float y_f_0 = (circular_y || (overlap_y > 0 && y > 0)) ? (iy - y_skip) / float(overlap_y) : 1.f;
|
||||
const float y_f_1 = (circular_y || (overlap_y > 0 && y < (img_height - height))) ? (height - iy) / float(overlap_y) : 1.f;
|
||||
const float x_f = std::min(std::min(x_f_0, x_f_1), 1.f);
|
||||
const float y_f = std::min(std::min(y_f_0, y_f_1), 1.f);
|
||||
(*output)[plane * output_plane + dst_xy] =
|
||||
old_value + new_value * smootherstep_f32(y_f) * smootherstep_f32(x_f);
|
||||
} else {
|
||||
(*output)[plane * output_plane + dst_xy] = new_value;
|
||||
for (int64_t plane = 0; plane < plane_count; ++plane) {
|
||||
for (int iy = 0; iy < in_height; ++iy) {
|
||||
float y_f = 1.0f;
|
||||
if (iy < overlap_top) {
|
||||
y_f = static_cast<float>(iy) / overlap_top;
|
||||
}
|
||||
if (iy >= in_height - overlap_bottom) {
|
||||
y_f = static_cast<float>(in_height - iy) / overlap_bottom;
|
||||
}
|
||||
const float y_weight = smootherstep_f32(std::clamp(y_f, 0.0f, 1.0f));
|
||||
for (int ix = 0; ix < in_width; ++ix) {
|
||||
float x_f = 1.0f;
|
||||
if (ix < overlap_left) {
|
||||
x_f = static_cast<float>(ix) / overlap_left;
|
||||
}
|
||||
if (ix >= in_width - overlap_right) {
|
||||
x_f = static_cast<float>(in_width - ix) / overlap_right;
|
||||
}
|
||||
float x_weight = smootherstep_f32(std::clamp(x_f, 0.0f, 1.0f));
|
||||
(*output)[plane * out_size + out_width * ((y + iy) % out_height) + ((x + ix) % out_width)] += x_weight * y_weight * input[plane * in_size + in_width * iy + ix];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,111 +156,53 @@ sd::Tensor<float> process_tiles_2d(const sd::Tensor<float>& input,
|
||||
GGML_ASSERT(((input_width / output_width) == scale) ||
|
||||
((output_width / input_width) == scale));
|
||||
|
||||
int small_width = output_width;
|
||||
int small_height = output_height;
|
||||
bool decode = output_width > input_width;
|
||||
if (decode) {
|
||||
small_width = input_width;
|
||||
small_height = input_height;
|
||||
}
|
||||
bool decode = output_width > input_width; // scale up
|
||||
int small_width = decode ? input_width : output_width;
|
||||
int small_height = decode ? input_height : output_height;
|
||||
int scale_in = decode ? 1 : scale;
|
||||
int scale_out = decode ? scale : 1;
|
||||
|
||||
int num_tiles_x;
|
||||
float tile_overlap_factor_x;
|
||||
sd_tiling_calc_tiles(num_tiles_x, tile_overlap_factor_x, small_width, p_tile_size_w, tile_overlap_factor, circular_x);
|
||||
const auto tiles_x = sd_tiling_plan_axis(small_width, p_tile_size_w, tile_overlap_factor, circular_x);
|
||||
const auto tiles_y = sd_tiling_plan_axis(small_height, p_tile_size_h, tile_overlap_factor, circular_y);
|
||||
|
||||
int num_tiles_y;
|
||||
float tile_overlap_factor_y;
|
||||
sd_tiling_calc_tiles(num_tiles_y, tile_overlap_factor_y, small_height, p_tile_size_h, tile_overlap_factor, circular_y);
|
||||
int tile_width = std::min(p_tile_size_w, small_width);
|
||||
int tile_height = std::min(p_tile_size_h, small_height);
|
||||
int input_tile_width = tile_width * scale_in;
|
||||
int input_tile_height = tile_height * scale_in;
|
||||
int output_tile_width = tile_width * scale_out;
|
||||
int output_tile_height = tile_height * scale_out;
|
||||
|
||||
int tile_overlap_x = static_cast<int32_t>(p_tile_size_w * tile_overlap_factor_x);
|
||||
int non_tile_overlap_x = p_tile_size_w - tile_overlap_x;
|
||||
int tile_overlap_y = static_cast<int32_t>(p_tile_size_h * tile_overlap_factor_y);
|
||||
int non_tile_overlap_y = p_tile_size_h - tile_overlap_y;
|
||||
int tile_size_w = p_tile_size_w < small_width ? p_tile_size_w : small_width;
|
||||
int tile_size_h = p_tile_size_h < small_height ? p_tile_size_h : small_height;
|
||||
int input_tile_size_w = tile_size_w;
|
||||
int input_tile_size_h = tile_size_h;
|
||||
int output_tile_size_w = tile_size_w;
|
||||
int output_tile_size_h = tile_size_h;
|
||||
if (decode) {
|
||||
output_tile_size_w *= scale;
|
||||
output_tile_size_h *= scale;
|
||||
} else {
|
||||
input_tile_size_w *= scale;
|
||||
input_tile_size_h *= scale;
|
||||
}
|
||||
|
||||
int num_tiles = num_tiles_x * num_tiles_y;
|
||||
int tile_count = 1;
|
||||
bool last_y = false;
|
||||
bool last_x = false;
|
||||
float last_time = 0.0f;
|
||||
const int num_tiles = static_cast<int>(tiles_x.size() * tiles_y.size());
|
||||
int tile_count = 0;
|
||||
if (!silent) {
|
||||
LOG_VERBOSE("num tiles : %d, %d ", num_tiles_x, num_tiles_y);
|
||||
LOG_VERBOSE("optimal overlap : %f, %f (targeting %f)", tile_overlap_factor_x, tile_overlap_factor_y, tile_overlap_factor);
|
||||
LOG_VERBOSE("num tiles : %d, %d ", static_cast<int>(tiles_x.size()), static_cast<int>(tiles_y.size()));
|
||||
LOG_VERBOSE("processing %i tiles", num_tiles);
|
||||
pretty_progress(0, num_tiles, 0.0f);
|
||||
}
|
||||
for (int y = 0; y < small_height && !last_y; y += non_tile_overlap_y) {
|
||||
int dy = 0;
|
||||
if (!circular_y && y + tile_size_h >= small_height) {
|
||||
int original_y = y;
|
||||
y = small_height - tile_size_h;
|
||||
dy = original_y - y;
|
||||
if (decode) {
|
||||
dy *= scale;
|
||||
}
|
||||
last_y = true;
|
||||
}
|
||||
for (int x = 0; x < small_width && !last_x; x += non_tile_overlap_x) {
|
||||
int dx = 0;
|
||||
if (!circular_x && x + tile_size_w >= small_width) {
|
||||
int original_x = x;
|
||||
x = small_width - tile_size_w;
|
||||
dx = original_x - x;
|
||||
if (decode) {
|
||||
dx *= scale;
|
||||
}
|
||||
last_x = true;
|
||||
}
|
||||
|
||||
int x_in = decode ? x : scale * x;
|
||||
int y_in = decode ? y : scale * y;
|
||||
int x_out = decode ? x * scale : x;
|
||||
int y_out = decode ? y * scale : y;
|
||||
|
||||
int overlap_x_out = decode ? tile_overlap_x * scale : tile_overlap_x;
|
||||
int overlap_y_out = decode ? tile_overlap_y * scale : tile_overlap_y;
|
||||
|
||||
for (const auto& y : tiles_y) {
|
||||
for (const auto& x : tiles_x) {
|
||||
int64_t t1 = ggml_time_ms();
|
||||
auto input_tile = sd_tensor_split_2d(input, input_tile_size_w, input_tile_size_h, x_in, y_in);
|
||||
auto input_tile = sd_tensor_split_2d(input, input_tile_width, input_tile_height, x.offset * scale_in, y.offset * scale_in);
|
||||
auto output_tile = on_processing(input_tile);
|
||||
if (output_tile.empty()) {
|
||||
return {};
|
||||
}
|
||||
GGML_ASSERT(output_tile.shape()[0] == output_tile_size_w && output_tile.shape()[1] == output_tile_size_h);
|
||||
GGML_ASSERT(output_tile.shape()[0] == output_tile_width && output_tile.shape()[1] == output_tile_height);
|
||||
if (output.empty()) {
|
||||
std::vector<int64_t> output_shape = output_tile.shape();
|
||||
output_shape[0] = output_width;
|
||||
output_shape[1] = output_height;
|
||||
output = sd::Tensor<float>::zeros(std::move(output_shape));
|
||||
}
|
||||
sd_tensor_merge_2d(output_tile, &output, x_out, y_out, overlap_x_out, overlap_y_out, circular_x, circular_y, dx, dy);
|
||||
|
||||
sd_tensor_merge_2d(output_tile, &output,
|
||||
x.offset * scale_out, y.offset * scale_out,
|
||||
x.overlap_before * scale_out, x.overlap_after * scale_out,
|
||||
y.overlap_before * scale_out, y.overlap_after * scale_out);
|
||||
++tile_count;
|
||||
if (!silent) {
|
||||
int64_t t2 = ggml_time_ms();
|
||||
last_time = (t2 - t1) / 1000.0f;
|
||||
pretty_progress(tile_count, num_tiles, last_time);
|
||||
pretty_progress(tile_count, num_tiles, (ggml_time_ms() - t1) / 1000.0f);
|
||||
}
|
||||
tile_count++;
|
||||
}
|
||||
last_x = false;
|
||||
}
|
||||
if (!silent && tile_count < num_tiles) {
|
||||
pretty_progress(num_tiles, num_tiles, last_time);
|
||||
}
|
||||
if (output.empty()) {
|
||||
return {};
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user