Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions src/graph_export/image_generation_graph_cli_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,19 +230,22 @@ void ImageGenerationGraphCLIParser::prepare(ServerSettingsImpl& serverSettings,

LoraAdapterSettings adapter;
adapter.alias = alias;
// Parse optional :alpha suffix
// Parse optional :alpha suffix. Only a colon in the final path segment can be an
// alpha delimiter, which keeps the scheme colon of "https://...", the port colon of
// "https://host:8080/file.safetensors" and a Windows drive letter ("C:\\...") from
// being misread as one. A "//" check is no longer needed: everything after the last
// separator is free of path separators, so alphaStr can never start with one.
const size_t lastSeparator = source.find_last_of("/\\");
const size_t segmentStart = (lastSeparator == std::string::npos) ? 0 : lastSeparator + 1;
auto lastColon = source.rfind(':');
if (lastColon != std::string::npos && lastColon > 1) {
if (lastColon != std::string::npos && lastColon > 1 && lastColon >= segmentStart) {
std::string alphaStr = source.substr(lastColon + 1);
// Skip protocol colons (https:// or http://)
if (alphaStr.substr(0, 2) != "//") {
auto alpha = ovms::stof(alphaStr);
if (!alpha.has_value()) {
throw std::invalid_argument("Invalid alpha value '" + alphaStr + "' in --source_loras entry: '" + entry + "'");
}
adapter.alpha = alpha.value();
source = source.substr(0, lastColon);
auto alpha = ovms::stof(alphaStr);
if (!alpha.has_value()) {
throw std::invalid_argument("Invalid alpha value '" + alphaStr + "' in --source_loras entry: '" + entry + "'");
}
adapter.alpha = alpha.value();
source = source.substr(0, lastColon);
}
// Detect source type
if (source.substr(0, 8) == "https://" || source.substr(0, 7) == "http://") {
Expand Down
31 changes: 31 additions & 0 deletions src/test/lora_graph_export_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,37 @@ TEST(ImageGenCLILoraParsingTest, UrlLoraWithoutAlphaPreservesDefault) {
EXPECT_FALSE(graphSettings.loraAdapters[0].alpha.has_value());
}

// A URL may carry an explicit port. Without an alpha suffix the port colon is the last
// colon in the entry, so it must not be taken for an alpha delimiter.
TEST(ImageGenCLILoraParsingTest, UrlLoraWithPortWithoutAlpha) {
ovms::ServerSettingsImpl serverSettings;
serverSettings.serverMode = ovms::HF_PULL_MODE;
ovms::HFSettingsImpl hfSettings;
hfSettings.sourceLoras = "pokemon=https://registry.internal:8080/loras/weights.safetensors";
ovms::ImageGenerationGraphCLIParser parser;
parser.prepare(serverSettings, hfSettings, "test_model");
auto& graphSettings = std::get<ovms::ImageGenerationGraphSettingsImpl>(hfSettings.graphSettings);
ASSERT_EQ(graphSettings.loraAdapters.size(), 1);
EXPECT_EQ(graphSettings.loraAdapters[0].sourceType, ovms::LoraSourceType::DIRECT_URL);
EXPECT_EQ(graphSettings.loraAdapters[0].sourceLora, "https://registry.internal:8080/loras/weights.safetensors");
EXPECT_EQ(graphSettings.loraAdapters[0].safetensorsFile.value(), "weights.safetensors");
EXPECT_FALSE(graphSettings.loraAdapters[0].alpha.has_value());
}

TEST(ImageGenCLILoraParsingTest, UrlLoraWithPortAndAlpha) {
ovms::ServerSettingsImpl serverSettings;
serverSettings.serverMode = ovms::HF_PULL_MODE;
ovms::HFSettingsImpl hfSettings;
hfSettings.sourceLoras = "pokemon=https://registry.internal:8080/loras/weights.safetensors:0.45";
ovms::ImageGenerationGraphCLIParser parser;
parser.prepare(serverSettings, hfSettings, "test_model");
auto& graphSettings = std::get<ovms::ImageGenerationGraphSettingsImpl>(hfSettings.graphSettings);
ASSERT_EQ(graphSettings.loraAdapters.size(), 1);
EXPECT_EQ(graphSettings.loraAdapters[0].sourceLora, "https://registry.internal:8080/loras/weights.safetensors");
ASSERT_TRUE(graphSettings.loraAdapters[0].alpha.has_value());
EXPECT_FLOAT_EQ(graphSettings.loraAdapters[0].alpha.value(), 0.45f);
}

TEST(ImageGenCLILoraParsingTest, NPURejectsMultiLoraWithoutComposites) {
ovms::ServerSettingsImpl serverSettings;
serverSettings.serverMode = ovms::HF_PULL_MODE;
Expand Down