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
19 changes: 18 additions & 1 deletion src/app/src/renderer/ModelOptionsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,19 @@ const ModelOptionsModal: React.FC<SettingsModalProps> = ({ isOpen, onCancel, onS
return opt?.useDefault ?? true;
};

// Check if a boolean option is forced by the selected backend (e.g. Vulkan forces --diffusion-fa)
const isOptionForcedByBackend = (key: string): boolean => {
if (modelInfo?.recipe !== 'sd-cpp') return false;
const backend = (options as unknown as Record<string, { value: string }>)['sdcppBackend']?.value ?? '';
const forcedFlags: Record<string, string[]> = {
diffusionFa: ['vulkan', 'cuda'],
diffusionConvDirect: [],
vaeConvDirect: [],
};
const forcedList = forcedFlags[key] ?? [];
return forcedList.includes(backend);
};

const renderContextSizeField = (key: string) => {
const def = getOptionDefinition(key);
if (!def || def.type !== 'numeric') return null;
Expand Down Expand Up @@ -593,6 +606,7 @@ const ModelOptionsModal: React.FC<SettingsModalProps> = ({ isOpen, onCancel, onS

const value = getOptionValue<boolean>(key);
const useDefault = getOptionUseDefault(key);
const forced = isOptionForcedByBackend(key);
if (value === undefined) return null;

return (
Expand All @@ -615,9 +629,12 @@ const ModelOptionsModal: React.FC<SettingsModalProps> = ({ isOpen, onCancel, onS
<input
type="checkbox"
checked={value}
onChange={(e) => handleBooleanChange(key, e.target.checked)}
onChange={(e) => !forced && handleBooleanChange(key, e.target.checked)}
disabled={forced}
className="settings-checkbox"
title={forced ? 'This option is locked by the selected backend' : ''}
/>
{forced && <span className="settings-locked-indicator" title="Locked by backend">🔒</span>}
<div className="settings-checkbox-content">
<span className="settings-description">
{def.description}
Expand Down
26 changes: 25 additions & 1 deletion src/app/src/renderer/recipes/recipeOptionsConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ export interface StableDiffusionOptions {
cfgScale: NumericOption;
width: NumericOption;
height: NumericOption;
diffusionFa: BooleanOption;
diffusionConvDirect: BooleanOption;
vaeConvDirect: BooleanOption;
mergeArgs: BooleanOption;
pinned: BooleanOption;
saveOptions: BooleanOption;
Expand Down Expand Up @@ -300,6 +303,24 @@ export const OPTION_DEFINITIONS: Record<string, OptionDef> = {
label: 'Height',
description: 'Image height in pixels',
},
diffusionFa: {
type: 'boolean',
default: false,
label: 'Diffusion FA',
description: 'Enable fused-attention for stable-diffusion.cpp',
},
diffusionConvDirect: {
type: 'boolean',
default: false,
label: 'Diffusion Conv Direct',
description: 'Enable direct convolution for stable-diffusion.cpp',
},
vaeConvDirect: {
type: 'boolean',
default: false,
label: 'VAE Conv Direct',
description: 'Enable direct VAE convolution for stable-diffusion.cpp',
},

// Audio-generation backends
thinksoundBackend: {
Expand Down Expand Up @@ -368,7 +389,7 @@ export const RECIPE_OPTIONS_MAP: Record<RecipeName, string[]> = {
'moonshine': ['moonshineArgs', 'mergeArgs', 'pinned', 'saveOptions'],
'flm': ['ctxSize', 'mergeArgs', 'pinned', 'saveOptions'],
'ryzenai-llm': ['ctxSize', 'pinned', 'saveOptions'],
'sd-cpp': ['sdcppBackend', 'steps', 'cfgScale', 'width', 'height', 'mergeArgs', 'pinned', 'saveOptions'],
'sd-cpp': ['sdcppBackend', 'steps', 'cfgScale', 'width', 'height', 'diffusionFa', 'diffusionConvDirect', 'vaeConvDirect', 'mergeArgs', 'pinned', 'saveOptions'],
'vllm': ['ctxSize', 'vllmBackend', 'vllmArgs', 'mergeArgs', 'pinned', 'saveOptions'],
'thinksound': ['thinksoundBackend', 'pinned', 'saveOptions'],
'acestep': ['acestepBackend', 'pinned', 'saveOptions'],
Expand Down Expand Up @@ -406,6 +427,9 @@ const FRONTEND_TO_API_MAP: Record<string, string> = {
whispercppArgs: 'whispercpp_args',
moonshineArgs: 'moonshine_args',
sdcppBackend: 'sd-cpp_backend',
diffusionFa: 'diffusion_fa',
diffusionConvDirect: 'diffusion_conv_direct',
vaeConvDirect: 'vae_conv_direct',
thinksoundBackend: 'thinksound_backend',
acestepBackend: 'acestep_backend',
trellisBackend: 'trellis_backend',
Expand Down
12 changes: 12 additions & 0 deletions src/app/styles/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -5528,6 +5528,18 @@ input::-webkit-calendar-picker-indicator {
flex-shrink: 0;
}

.settings-checkbox:disabled {
cursor: not-allowed;
opacity: 0.5;
}

.settings-locked-indicator {
font-size: 12px;
color: var(--text-tertiary);
margin-top: 2px;
flex-shrink: 0;
}

.settings-checkbox-content {
display: flex;
flex-direction: column;
Expand Down
5 changes: 4 additions & 1 deletion src/cpp/include/lemon/backends/sdcpp/sdcpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ inline const BackendDescriptor descriptor = {
"SD.cpp backend to use", "Stable Diffusion Options"},
{"sdcpp_args", "--sdcpp-args", "", "ARGS",
"Custom arguments to pass to sd-server (must not conflict with managed args)", "Stable Diffusion Options"},
{"diffusion_fa", "--diffusion-fa", false, "BOOL", "Enable fused-attention", "Stable Diffusion.cpp Options"},
{"diffusion_conv_direct", "--diffusion-conv-direct", false, "BOOL", "Enable direct convolution", "Stable Diffusion.cpp Options"},
{"vae_conv_direct", "--vae-conv-direct", false, "BOOL", "Enable VAE direct convolution", "Stable Diffusion.cpp Options"},
// Image generation defaults (recipe-level only, not CLI flags).
{"steps", "", 20, "SIZE", "Number of diffusion steps", "Stable Diffusion Options"},
{"cfg_scale", "", 7.0, "SIZE", "Classifier-free guidance scale", "Stable Diffusion Options"},
Expand Down Expand Up @@ -57,7 +60,7 @@ inline const BackendDescriptor descriptor = {
/*takes_args*/ true,
/*arg_variants*/ {"cpu", "rocm", "vulkan", "cuda"},
/*bin_variants*/ {"cpu", "rocm", "vulkan", "cuda"},
/*config_extra*/ {{"steps", 20}, {"cfg_scale", 7.0}, {"width", 512}, {"height", 512}},
/*config_extra*/ {{"steps", 20}, {"cfg_scale", 7.0}, {"width", 512}, {"height", 512}, {"diffusion_fa", false}, {"diffusion_conv_direct", false}, {"vae_conv_direct", false}},
};

} // namespace sdcpp
Expand Down
4 changes: 4 additions & 0 deletions src/cpp/include/lemon/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,10 @@ class Server {
// Extract load-level options from an inference request body. Currently only ctx_size
// is forwarded; request-scoped fields are excluded so they cannot leak into recipe options.
static json extract_auto_load_options(const json& request);

// Merge sd-cpp diffusion flags into sdcpp_args, combining recipe-level args
// with per-request boolean flags. Returns modified request_options.
json build_sdcpp_args_merge(const json& request_options, const ModelInfo& info);
};

} // namespace lemon
71 changes: 69 additions & 2 deletions src/cpp/server/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <cstring>
#include "lemon/utils/image_sniff.h"
#include "lemon/utils/json_utils.h"
#include "lemon/utils/custom_args.h"
#include "lemon/utils/path_utils.h"
#include "lemon/streaming_proxy.h"
#include "lemon/logging_config.h"
Expand Down Expand Up @@ -2266,6 +2267,69 @@ nlohmann::json Server::extract_auto_load_options(const json& request) {
return result;
}

// Merge sd-cpp boolean options (those with CLI flags) into the sdcpp_args string.
// Reads the backend descriptor to discover which boolean options should become
// flags in the args string, so new options don't require code changes here.
// Also respects backend-forced flags (e.g. Vulkan forces --diffusion-fa) so
// user-unchecked boxes don't remove flags the backend requires.
nlohmann::json Server::build_sdcpp_args_merge(const nlohmann::json& request_options,
const ModelInfo& info) {
if (info.recipe != "sd-cpp") return request_options;

std::string recipe_args = info.recipe_options.get_option("sdcpp_args");
std::string backend = request_options.value("sd-cpp_backend", "");
if (backend.empty()) backend = info.recipe_options.get_option("sdcpp_backend");

const auto* desc = lemon::backends::descriptor_for(info.recipe);
if (!desc) return request_options;

// Build a set of CLI flag strings to filter out
std::set<std::string> known_flags;
for (const auto& opt : desc->options) {
if (opt.type_name == "BOOL" && !opt.cli_flag.empty()) {
known_flags.insert(opt.cli_flag);
}
}

// Determine which flags are forced by the backend
auto is_backend_forced = [](const std::string& b, const std::string& flag) {
if (flag == "--diffusion-fa" && (b == "vulkan" || b == "cuda")) return true;
return false;
};

// Parse existing args, keeping only non-boolean-option flags
auto all_tokens = lemon::utils::parse_custom_args(recipe_args, true);
std::vector<std::string> other_args;
for (const auto& token : all_tokens) {
if (known_flags.find(token) == known_flags.end()) {
other_args.push_back(token);
}
}

// Rebuild: other args first, then enabled boolean flags
std::vector<std::string> merged_list = other_args;
for (const auto& opt : desc->options) {
if (opt.type_name == "BOOL" && !opt.cli_flag.empty()) {
bool enabled = request_options.value(opt.name, false);
// Backend-forced flags stay on regardless of user choice
if (is_backend_forced(backend, opt.cli_flag)) enabled = true;
if (enabled) merged_list.push_back(opt.cli_flag);
}
}

std::string merged;
for (const auto& token : merged_list) {
if (!merged.empty()) merged += " ";
merged += token;
}

json result = request_options;
if (!merged.empty()) {
result["sdcpp_args"] = merged;
}
return result;
}

void Server::auto_load_model_if_needed(
const std::string& requested_model,
const json& request_options,
Expand Down Expand Up @@ -2332,8 +2396,9 @@ void Server::auto_load_model_if_needed(
// Load model with do_not_upgrade=true, applying per-request options on first load.
// For FLM models: FastFlowLMServer will handle download internally if needed
// For non-FLM models: Model should already be cached at this point
json merged_options = build_sdcpp_args_merge(request_options, info);
router_->load_model(requested_model, info,
RecipeOptions(info.recipe, request_options), true,
RecipeOptions(info.recipe, merged_options), true,
/*allow_reload_on_option_change=*/false,
/*pinned=*/std::nullopt,
load_purpose);
Expand Down Expand Up @@ -5273,7 +5338,9 @@ void Server::handle_load(const httplib::Request& req, httplib::Response& res) {
auto info = model_manager_->get_model_info(model_name);

// Extract optional per-model settings (defaults to -1 / empty = use Router defaults)
RecipeOptions options = RecipeOptions(info.recipe, request_json);
json loadOptions = request_json;
loadOptions = build_sdcpp_args_merge(loadOptions, info);
RecipeOptions options = RecipeOptions(info.recipe, loadOptions);
bool save_options = request_json.value("save_options", false);
std::optional<bool> pinned_opt = std::nullopt;
if (request_json.contains("pinned") && request_json["pinned"].is_boolean()) {
Expand Down