Commit 7bcd4ce for stable-diffusion.cpp
commit 7bcd4ce8ce69378115c176d46d4f30aed80c005a
Author: Zihao <Louis20120127@gmail.com>
Date: Sun Nov 8 18:23:11 2026 +1100
fix: clean LoRA multiplier and prompt spacing in image metadata (#2283)
diff --git a/examples/common/generation_metadata.cpp b/examples/common/generation_metadata.cpp
index 989e914..05205ff 100644
--- a/examples/common/generation_metadata.cpp
+++ b/examples/common/generation_metadata.cpp
@@ -1,5 +1,6 @@
#include "generation_metadata.h"
+#include <cctype>
#include <filesystem>
#include <map>
@@ -70,6 +71,7 @@ std::string generation_info_to_webui(const std::string& text) {
const auto& sampling = info.at("sample_params");
std::string result = info.value("prompt", "");
if (info.contains("loras")) {
+ bool prompt_trimmed = false;
for (const auto& lora : info.at("loras")) {
// WebUI tags cannot restrict a LoRA to the high-noise model.
if (lora.value("is_high_noise", false))
@@ -83,8 +85,15 @@ std::string generation_info_to_webui(const std::string& text) {
name.erase(extension);
if (name.empty() || name.find_first_of(":<>\r\n") != std::string::npos)
continue;
- if (!result.empty())
- result += " ";
+ if (!result.empty()) {
+ if (!prompt_trimmed) {
+ while (!result.empty() && std::isspace(static_cast<unsigned char>(result.back())))
+ result.pop_back();
+ prompt_trimmed = true;
+ }
+ if (!result.empty())
+ result += " ";
+ }
result += "<lora:" + name + ":" + lora.at("multiplier").dump() + ">";
}
}
diff --git a/src/generation_info.cpp b/src/generation_info.cpp
index cf40bc2..0d48291 100644
--- a/src/generation_info.cpp
+++ b/src/generation_info.cpp
@@ -1,6 +1,7 @@
#include "generation_info.h"
#include <cmath>
+#include <cstdio>
#include <cstdlib>
#include <cstring>
@@ -14,6 +15,21 @@ namespace sd {
return value ? value : "";
}
+ static double shortest_f32_as_double(float value) {
+ if (!std::isfinite(value)) {
+ return static_cast<double>(value);
+ }
+ char buf[64];
+ buf[0] = '\0';
+ for (int prec = 1; prec <= 9; ++prec) {
+ std::snprintf(buf, sizeof(buf), "%.*g", prec, static_cast<double>(value));
+ if (std::strtof(buf, nullptr) == value) {
+ break;
+ }
+ }
+ return std::strtod(buf, nullptr);
+ }
+
static json sampling_info(const sd_sample_params_t& p, bool high_noise = false) {
const auto& g = p.guidance;
json info = {{"sample_steps", p.sample_steps}, {"guidance", {{"txt_cfg", g.txt_cfg}, {"distilled_guidance", g.distilled_guidance}}}};
@@ -117,8 +133,13 @@ namespace sd {
}
if (p.lora_count > 0) {
info["loras"] = json::array();
- for (uint32_t i = 0; i < p.lora_count; ++i)
- info["loras"].push_back({{"path", text(p.loras[i].path)}, {"multiplier", p.loras[i].multiplier}, {"is_high_noise", p.loras[i].is_high_noise}});
+ for (uint32_t i = 0; i < p.lora_count; ++i) {
+ // nlohmann::json promotes float to double, so dump the shortest
+ // decimal that round-trips the float32 (e.g. 0.85f stays 0.85
+ // instead of 0.8500000238418579) to keep replay exact.
+ const double multiplier = shortest_f32_as_double(p.loras[i].multiplier);
+ info["loras"].push_back({{"path", text(p.loras[i].path)}, {"multiplier", multiplier}, {"is_high_noise", p.loras[i].is_high_noise}});
+ }
}
return info;
}