mirror of
https://github.com/leejet/stable-diffusion.cpp.git
synced 2026-10-15 05:19:28 +08:00
Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e16d26a96a | ||
|
|
a4a966990d | ||
|
|
a1ded76da5 | ||
|
|
8cc5ad8f1c | ||
|
|
4a892293ac | ||
|
|
fd43b542a6 | ||
|
|
0963357214 | ||
|
|
1da55ebde8 | ||
|
|
fb8ee71f55 | ||
|
|
96f67fe631 | ||
|
|
203a882a28 | ||
|
|
019bcfc0ae | ||
|
|
c150a6bafe | ||
|
|
f4b5326da7 |
+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);
|
||||
}
|
||||
|
||||
+68
-1
@@ -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:
|
||||
@@ -1048,7 +1095,8 @@ Response fields:
|
||||
Compared with `img_gen`, the `vid_gen` request body:
|
||||
|
||||
- `vid_gen` is a single video sequence job, so `batch_count` is not part of the request schema
|
||||
- `ref_images`, `mask_image`, `control_image`, `control_strength`, `ip_adapter_image`, `ip_adapter_strength`, and `embed_image_metadata` are not part of the request schema
|
||||
- `mask_image`, `control_image`, `control_strength`, `ip_adapter_image`, `ip_adapter_strength`, and `embed_image_metadata` are not part of the request schema
|
||||
- `ref_images` is accepted for MiniMax-H3 Ref2VA conditioning; other video model families currently ignore it
|
||||
- `vid_gen` adds `end_image`, `control_frames`, `high_noise_sample_params`, `video_frames`, `fps`, `moe_boundary`, and `vace_strength`
|
||||
|
||||
Example:
|
||||
@@ -1069,6 +1117,7 @@ Example:
|
||||
|
||||
"init_image": null,
|
||||
"end_image": null,
|
||||
"ref_images": [],
|
||||
"control_frames": [],
|
||||
|
||||
"sample_params": {
|
||||
@@ -1130,11 +1179,25 @@ Example:
|
||||
"scm_mask": "",
|
||||
"scm_policy_dynamic": true,
|
||||
|
||||
"preview": "none",
|
||||
"preview_interval": 1,
|
||||
|
||||
"output_format": "webm",
|
||||
"output_compression": 100
|
||||
}
|
||||
```
|
||||
|
||||
### Reference Image Rules
|
||||
|
||||
- `ref_images` contains reference images for MiniMax-H3 Ref2VA conditioning.
|
||||
- Images retain request order and correspond to `<Picture 1>`, `<Picture 2>`, and so on in the prompt.
|
||||
- For MiniMax-H3, non-empty `ref_images` cannot be combined with `init_image` or `end_image`. A conflicting API request fails during generation; the WebUI checks this before submission.
|
||||
- MiniMax-H3 does not support `control_frames`; leave that array empty.
|
||||
- Other video model families currently ignore `ref_images`.
|
||||
- `features_by_mode.vid_gen` does not currently advertise `ref_images`; its absence is not an indication that MiniMax-H3 reference images are unsupported.
|
||||
|
||||
See [MiniMax-H3 reference conditioning](../../docs/minimax_h3.md#reference-to-audio-video-conditioning) for model requirements and prompt examples.
|
||||
|
||||
### LoRA Rules
|
||||
|
||||
- The server only accepts explicit LoRA entries from the `lora` field.
|
||||
@@ -1152,6 +1215,7 @@ Channel expectations:
|
||||
|
||||
- `init_image`: 3 channels
|
||||
- `end_image`: 3 channels
|
||||
- `ref_images[]`: decoded with native channels, then converted to RGB by MiniMax-H3
|
||||
- `control_frames[]`: 3 channels
|
||||
|
||||
Frame ordering rules:
|
||||
@@ -1181,6 +1245,8 @@ Top-level scalar fields:
|
||||
| `fps` | `integer` |
|
||||
| `moe_boundary` | `number` |
|
||||
| `vace_strength` | `number` |
|
||||
| `preview` | `string` |
|
||||
| `preview_interval` | `integer` |
|
||||
|
||||
Image and frame fields:
|
||||
|
||||
@@ -1188,6 +1254,7 @@ Image and frame fields:
|
||||
| --- | --- |
|
||||
| `init_image` | `string \| null` |
|
||||
| `end_image` | `string \| null` |
|
||||
| `ref_images` | `array<string>` |
|
||||
| `control_frames` | `array<string>` |
|
||||
|
||||
LoRA 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 {
|
||||
|
||||
+1
-1
Submodule examples/server/frontend updated: dd74a8e808...8e7c115059
@@ -75,6 +75,8 @@ int main(int argc, const char** argv) {
|
||||
}
|
||||
SDSvrParams svr_params;
|
||||
SDContextParams ctx_params;
|
||||
// Preserve the CLI default while disabling cwd scans for sd-server.
|
||||
ctx_params.lora_model_dir.clear();
|
||||
SDGenerationParams default_gen_params;
|
||||
|
||||
sd_set_log_callback(sd_log_cb, (void*)&svr_params);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -254,6 +254,12 @@ std::string SDSvrParams::to_string() const {
|
||||
void refresh_lora_cache(ServerRuntime& rt) {
|
||||
std::vector<LoraEntry> new_cache;
|
||||
|
||||
if (rt.ctx_params->lora_model_dir.empty()) {
|
||||
std::lock_guard<std::mutex> lock(*rt.lora_mutex);
|
||||
rt.lora_cache->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
fs::path lora_dir = rt.ctx_params->lora_model_dir;
|
||||
std::error_code ec;
|
||||
if (fs::exists(lora_dir, ec) && !ec && fs::is_directory(lora_dir, ec) && !ec) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -399,7 +399,7 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner {
|
||||
ss << "['" << item.first << "', " << item.second << "], ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
|
||||
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
|
||||
}
|
||||
|
||||
auto on_new_token_cb = [&](std::string& str, std::vector<int32_t>& bpe_tokens) -> bool {
|
||||
@@ -817,7 +817,7 @@ struct SD3CLIPEmbedder : public Conditioner {
|
||||
ss << "['" << item.first << "', " << item.second << "], ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
|
||||
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
|
||||
}
|
||||
|
||||
auto on_new_token_cb = [&](std::string& str, std::vector<int32_t>& bpe_tokens) -> bool {
|
||||
@@ -909,6 +909,31 @@ struct SD3CLIPEmbedder : public Conditioner {
|
||||
|
||||
size_t chunk_count = std::max(std::max(clip_l_tokens.size(), clip_g_tokens.size()), t5_tokens.size()) / chunk_len;
|
||||
|
||||
// Each active encoder must cover every chunk of the longest token sequence.
|
||||
Tokenizer* tokenizers[] = {
|
||||
clip_l ? clip_l_tokenizer.get() : nullptr,
|
||||
clip_g ? clip_g_tokenizer.get() : nullptr,
|
||||
t5 ? &t5_tokenizer : nullptr,
|
||||
};
|
||||
const size_t target_length = chunk_count * chunk_len;
|
||||
|
||||
for (size_t encoder_idx = 0; encoder_idx < 3; encoder_idx++) {
|
||||
auto& tokens = token_and_weights[encoder_idx].first;
|
||||
auto& weights = token_and_weights[encoder_idx].second;
|
||||
if (!tokenizers[encoder_idx] || tokens.size() >= target_length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<int> empty_tokens;
|
||||
std::vector<float> empty_weights;
|
||||
tokenizers[encoder_idx]->pad_tokens(empty_tokens, &empty_weights, nullptr, chunk_len, chunk_len, false);
|
||||
|
||||
while (tokens.size() < target_length) {
|
||||
tokens.insert(tokens.end(), empty_tokens.begin(), empty_tokens.end());
|
||||
weights.insert(weights.end(), empty_weights.begin(), empty_weights.end());
|
||||
}
|
||||
}
|
||||
|
||||
for (int chunk_idx = 0; chunk_idx < chunk_count; chunk_idx++) {
|
||||
// clip_l
|
||||
sd::Tensor<float> chunk_hidden_states_l;
|
||||
@@ -1207,7 +1232,7 @@ struct FluxCLIPEmbedder : public Conditioner {
|
||||
ss << "['" << item.first << "', " << item.second << "], ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
|
||||
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
|
||||
}
|
||||
|
||||
auto on_new_token_cb = [&](std::string& str, std::vector<int32_t>& bpe_tokens) -> bool {
|
||||
@@ -1484,7 +1509,7 @@ struct T5CLIPEmbedder : public Conditioner {
|
||||
ss << "['" << item.first << "', " << item.second << "], ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
|
||||
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
|
||||
}
|
||||
|
||||
auto on_new_token_cb = [&](std::string& str, std::vector<int32_t>& bpe_tokens) -> bool {
|
||||
@@ -1873,7 +1898,7 @@ struct AnimaConditioner : public Conditioner {
|
||||
ss << "['" << item.first << "', " << item.second << "], ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
|
||||
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
|
||||
}
|
||||
|
||||
std::vector<int> qwen_tokens;
|
||||
@@ -2144,7 +2169,7 @@ struct LLMEmbedder : public Conditioner {
|
||||
ss << "['" << item.first << "', " << item.second << "], ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("parse '%s' to %s", text.c_str(), ss.str().c_str());
|
||||
LOG_VERBOSE("parse '%s' to %s", escape_newlines(text).c_str(), escape_newlines(ss.str()).c_str());
|
||||
}
|
||||
|
||||
std::vector<int> tokens;
|
||||
|
||||
+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);
|
||||
}
|
||||
|
||||
+11
-11
@@ -223,13 +223,13 @@ ggml_tensor* ggml_ext_linear(ggml_context* ctx,
|
||||
x = ggml_reshape_2d(ctx, x, x->ne[0], x->ne[1] * x->ne[2] * x->ne[3]);
|
||||
x = ggml_mul_mat(ctx, w, x);
|
||||
if (force_prec_f32) {
|
||||
ggml_mul_mat_set_prec(x, GGML_PREC_F32);
|
||||
ggml_prec_set_acc(x, GGML_PREC_F32);
|
||||
}
|
||||
x = ggml_reshape_4d(ctx, x, x->ne[0], x->ne[1] / ne2 / ne3, ne2, ne3);
|
||||
} else {
|
||||
x = ggml_mul_mat(ctx, w, x);
|
||||
if (force_prec_f32) {
|
||||
ggml_mul_mat_set_prec(x, GGML_PREC_F32);
|
||||
ggml_prec_set_acc(x, GGML_PREC_F32);
|
||||
}
|
||||
}
|
||||
if (scale != 1.f) {
|
||||
@@ -488,7 +488,7 @@ ggml_tensor* ggml_ext_conv_3d(ggml_context* ctx,
|
||||
x = ggml_mul_mat(ctx,
|
||||
ggml_reshape_2d(ctx, im2col, im2col->ne[0], im2col->ne[3] * im2col->ne[2] * im2col->ne[1]),
|
||||
ggml_reshape_2d(ctx, w, w->ne[0] * w->ne[1] * w->ne[2] * IC, OC));
|
||||
ggml_mul_mat_set_prec(x, GGML_PREC_F32);
|
||||
ggml_prec_set_acc(x, GGML_PREC_F32);
|
||||
|
||||
int64_t OD = im2col->ne[3] / N;
|
||||
x = ggml_reshape_4d(ctx, x, im2col->ne[1] * im2col->ne[2], OD, N, OC);
|
||||
@@ -683,8 +683,8 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
v_in->type == GGML_TYPE_F32 && sd_backend_supports_cuda_mma(backend);
|
||||
if (pad_head) {
|
||||
// CUDA FA MMA starts at 64 channels; keep the original head's attention scale.
|
||||
q_in = ggml_pad(ctx, q_in, 64 - d_head, 0, 0, 0);
|
||||
k_in = ggml_pad(ctx, k_in, 64 - d_head, 0, 0, 0);
|
||||
q_in = ggml_pad(ctx, q_in, static_cast<int>(64 - d_head), 0, 0, 0);
|
||||
k_in = ggml_pad(ctx, k_in, static_cast<int>(64 - d_head), 0, 0, 0);
|
||||
}
|
||||
if (kv_scale != 1.0f) {
|
||||
k_in = ggml_ext_scale(ctx, k_in, kv_scale);
|
||||
@@ -694,7 +694,7 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
v_in = ggml_ext_cont(ctx, ggml_permute(ctx, v_in, 0, 2, 1, 3));
|
||||
v_in = ggml_reshape_3d(ctx, v_in, d_head, L_k, n_kv_head * N);
|
||||
if (pad_head) {
|
||||
v_in = ggml_pad(ctx, v_in, 64 - d_head, 0, 0, 0);
|
||||
v_in = ggml_pad(ctx, v_in, static_cast<int>(64 - d_head), 0, 0, 0);
|
||||
}
|
||||
if (kv_scale != 1.0f) {
|
||||
v_in = ggml_ext_scale(ctx, v_in, kv_scale);
|
||||
@@ -721,7 +721,7 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
if (!ggml_backend_supports_op(backend, out)) {
|
||||
return nullptr;
|
||||
}
|
||||
ggml_flash_attn_ext_set_prec(out, GGML_PREC_F32);
|
||||
ggml_prec_set_acc(out, GGML_PREC_F32);
|
||||
if (kv_scale != 1.0f) {
|
||||
out = ggml_ext_scale(ctx, out, 1.0f / kv_scale);
|
||||
}
|
||||
@@ -742,9 +742,9 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
}
|
||||
if (padded_head != d_head) {
|
||||
// Keep the original head's softmax scale when padding for the CUDA kernel.
|
||||
q_in = ggml_pad(ctx, q_in, padded_head - d_head, 0, 0, 0);
|
||||
k_in = ggml_pad(ctx, k_in, padded_head - d_head, 0, 0, 0);
|
||||
v_in = ggml_pad(ctx, v_in, padded_head - d_head, 0, 0, 0);
|
||||
q_in = ggml_pad(ctx, q_in, static_cast<int>(padded_head - d_head), 0, 0, 0);
|
||||
k_in = ggml_pad(ctx, k_in, static_cast<int>(padded_head - d_head), 0, 0, 0);
|
||||
v_in = ggml_pad(ctx, v_in, static_cast<int>(padded_head - d_head), 0, 0, 0);
|
||||
}
|
||||
if (kv_scale != 1.0f) {
|
||||
k_in = ggml_ext_scale(ctx, k_in, kv_scale);
|
||||
@@ -797,7 +797,7 @@ ggml_tensor* ggml_ext_attention_ext(ggml_context* ctx,
|
||||
v = ggml_reshape_3d(ctx, v, L_k, d_head, n_kv_head * N); // [N * n_kv_head, d_head, L_k]
|
||||
|
||||
auto kq = ggml_mul_mat(ctx, k, q); // [N * n_head, L_q, L_k]
|
||||
ggml_mul_mat_set_prec(kq, GGML_PREC_F32);
|
||||
ggml_prec_set_acc(kq, GGML_PREC_F32);
|
||||
kq = ggml_scale_inplace(ctx, kq, scale);
|
||||
if (mask) {
|
||||
kq = ggml_add_inplace(ctx, kq, mask);
|
||||
|
||||
+43
-10
@@ -62,6 +62,21 @@ void replace_all_chars(std::string& str, char target, char replacement) {
|
||||
}
|
||||
}
|
||||
|
||||
std::string escape_newlines(const std::string& text) {
|
||||
std::string escaped;
|
||||
escaped.reserve(text.size());
|
||||
for (char ch : text) {
|
||||
if (ch == '\n') {
|
||||
escaped += "\\n";
|
||||
} else if (ch == '\r') {
|
||||
escaped += "\\r";
|
||||
} else {
|
||||
escaped += ch;
|
||||
}
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
|
||||
static std::string sd_vformat(const char* fmt, va_list ap) {
|
||||
char small[128];
|
||||
va_list ap2;
|
||||
@@ -360,12 +375,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 +660,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 +677,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 +712,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 +726,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;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ bool contains(const std::string& str, const std::string& substr);
|
||||
std::string sd_format(const char* fmt, ...);
|
||||
|
||||
void replace_all_chars(std::string& str, char target, char replacement);
|
||||
std::string escape_newlines(const std::string& text);
|
||||
|
||||
int round_up_to(int value, int base);
|
||||
|
||||
@@ -101,6 +102,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();
|
||||
|
||||
@@ -633,8 +633,11 @@ namespace MiniMaxH3VAE {
|
||||
auto plan = make_vae_temporal_tile_plan(input.shape()[2], {17, 0});
|
||||
auto result = process_vae_temporal_tiles(input, plan, [&](const sd::Tensor<float>& chunk, const VAETemporalTile& tile) {
|
||||
SD_UNUSED(tile);
|
||||
return VAE::encode(n_threads, chunk, tiling, circular_x, circular_y);
|
||||
// keep the runner alive across chunks; ending it here would
|
||||
// evict and reload the encoder weights every chunk
|
||||
return VAE::encode(n_threads, chunk, tiling, circular_x, circular_y, false);
|
||||
});
|
||||
runner_end();
|
||||
if (result.empty()) {
|
||||
return {};
|
||||
}
|
||||
@@ -695,13 +698,16 @@ namespace MiniMaxH3VAE {
|
||||
{static_cast<int>(tokens_per_chunk + token_overlap), static_cast<int>(token_overlap)});
|
||||
GGML_ASSERT(plan.tiles.size() == static_cast<size_t>(num_chunks));
|
||||
auto result = process_vae_temporal_tiles(input, plan, [&](const sd::Tensor<float>& chunk, const VAETemporalTile& tile) {
|
||||
// keep the runner alive across chunks; ending it here would
|
||||
// evict and reload the decoder weights every chunk
|
||||
auto decoded = VAE::decode(n_threads,
|
||||
chunk,
|
||||
tiling,
|
||||
true,
|
||||
circular_x,
|
||||
circular_y,
|
||||
silent);
|
||||
silent,
|
||||
false);
|
||||
if (decoded.empty()) {
|
||||
return sd::Tensor<float>();
|
||||
}
|
||||
@@ -728,6 +734,7 @@ namespace MiniMaxH3VAE {
|
||||
}
|
||||
return first;
|
||||
});
|
||||
runner_end();
|
||||
if (result.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
+26
-2
@@ -241,6 +241,15 @@ public:
|
||||
sd_tiling_params_t tiling_params,
|
||||
bool circular_x = false,
|
||||
bool circular_y = false) {
|
||||
return encode(n_threads, x, tiling_params, circular_x, circular_y, true);
|
||||
}
|
||||
|
||||
sd::Tensor<float> encode(int n_threads,
|
||||
const sd::Tensor<float>& x,
|
||||
sd_tiling_params_t tiling_params,
|
||||
bool circular_x,
|
||||
bool circular_y,
|
||||
bool auto_end) {
|
||||
int64_t t0 = ggml_time_ms();
|
||||
tiling_params = resolve_tiling_params(tiling_params);
|
||||
sd::Tensor<float> input = x;
|
||||
@@ -280,7 +289,9 @@ public:
|
||||
tiling_params);
|
||||
}
|
||||
|
||||
runner_end();
|
||||
if (auto_end) {
|
||||
runner_end();
|
||||
}
|
||||
|
||||
if (output.empty()) {
|
||||
LOG_ERROR("vae encode compute failed");
|
||||
@@ -298,6 +309,17 @@ public:
|
||||
bool circular_x = false,
|
||||
bool circular_y = false,
|
||||
bool silent = false) {
|
||||
return decode(n_threads, x, tiling_params, decode_video, circular_x, circular_y, silent, true);
|
||||
}
|
||||
|
||||
sd::Tensor<float> decode(int n_threads,
|
||||
const sd::Tensor<float>& x,
|
||||
sd_tiling_params_t tiling_params,
|
||||
bool decode_video,
|
||||
bool circular_x,
|
||||
bool circular_y,
|
||||
bool silent,
|
||||
bool auto_end) {
|
||||
int64_t t0 = ggml_time_ms();
|
||||
tiling_params = resolve_tiling_params(tiling_params);
|
||||
sd::Tensor<float> input = x;
|
||||
@@ -338,7 +360,9 @@ public:
|
||||
tiling_params);
|
||||
}
|
||||
|
||||
runner_end();
|
||||
if (auto_end) {
|
||||
runner_end();
|
||||
}
|
||||
|
||||
if (output.empty()) {
|
||||
LOG_ERROR("vae decode compute failed");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -2373,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);
|
||||
|
||||
+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;
|
||||
}
|
||||
|
||||
@@ -242,7 +242,7 @@ bool BPETokenizer::encode(const std::string& text, std::vector<int>& result, on_
|
||||
ss << "\"" << token << "\", ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("split prompt \"%s\" to %zu tokens %s", text.c_str(), bpe_tokens.size(), ss.str().c_str());
|
||||
LOG_VERBOSE("split prompt \"%s\" to %zu tokens %s", escape_newlines(text).c_str(), bpe_tokens.size(), escape_newlines(ss.str()).c_str());
|
||||
result = std::move(bpe_tokens);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -735,7 +735,7 @@ bool HFTokenizer::encode(const std::string& text, std::vector<int>& tokens, on_n
|
||||
}
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("split prompt \"%s\" to %zu tokens %s", text.c_str(), result.size(), ss.str().c_str());
|
||||
LOG_VERBOSE("split prompt \"%s\" to %zu tokens %s", escape_newlines(text).c_str(), result.size(), escape_newlines(ss.str()).c_str());
|
||||
tokens = std::move(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -343,7 +343,7 @@ bool T5UniGramTokenizer::encode(const std::string& input, std::vector<int>& resu
|
||||
ss << "\"" << token_str << "\", ";
|
||||
}
|
||||
ss << "]";
|
||||
LOG_VERBOSE("split prompt \"%s\" to tokens %s", input.c_str(), ss.str().c_str());
|
||||
LOG_VERBOSE("split prompt \"%s\" to tokens %s", escape_newlines(input).c_str(), escape_newlines(ss.str()).c_str());
|
||||
|
||||
result = std::move(tokens);
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user