Skip to content
Closed
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,7 @@ set(SOURCES_CORE
src/cpp/server/utils/version_utils.cpp
src/cpp/server/utils/wmi_helper.cpp
src/cpp/server/utils/network_beacon.cpp
src/cpp/server/utils/rocm_arch_utils.cpp
src/cpp/server/utils/tcp_jsonl_client.cpp
src/cpp/server/backends/cloud_server.cpp
src/cpp/server/backends/llamacpp_server.cpp
Expand Down
1 change: 1 addition & 0 deletions src/cpp/cli/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ set(COMMON_SOURCES
../server/utils/path_utils.cpp
../server/utils/json_utils.cpp
../server/utils/http_client.cpp
../server/utils/rocm_arch_utils.cpp
agent_launcher.cpp
agent_config_file.cpp
opencode_profile.cpp
Expand Down
135 changes: 135 additions & 0 deletions src/cpp/cli/lemonade_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
#include <sstream>
#include <nlohmann/json.hpp>

#include "lemon/utils/json_utils.h"
#include "lemon/utils/rocm_arch_utils.h"

namespace lemonade {

using json = nlohmann::json;
Expand Down Expand Up @@ -472,6 +475,138 @@ int LemonadeClient::list_models(bool show_all, const std::string& name_filter) c
}
}

int LemonadeClient::set_rocm_arch(const std::string& rocm_arch) const{
std::vector<lemon::ROCmDeviceInfo> rocm_devs;

if (rocm_arch.empty()) {
std::cerr << "Error Rocm arch value is empty" << std::endl;
return 1;
}

// Check if the rocm_arch var exists at server config.json
try {
std::string config_response = make_request("/internal/config");
auto json_config_response = json::parse(config_response);
if (!json_config_response.contains("rocm_arch")) {
std::cout << "You do NOT have the rocm_arch config var in your config.json" << std::endl;
return 0;
}
} catch (const HttpError& e) {
std::cerr << "Error checking rocm_arch config var: " << extract_server_error_message(e) << std::endl;
return 1;
} catch (const std::exception& e) {
std::cerr << "Error checking rocm_arch config var: " << e.what() << std::endl;
return 1;
}

// Check if an active gpu with Rocm arch exists at server. We only check if it is NOT auto.
if (!rocm_arch.empty() && rocm_arch != "auto") {
bool rocm_arch_found = false;
try {
std::string response = make_request("/api/v1/system-info");
auto json_response = json::parse(response);

if (json_response.contains("devices")) {
rocm_devs = lemon::ROCmArchUtils::rocm_arch_get_active_devices(json_response["devices"]);
for (const auto& rocm_dev : rocm_devs) {
if (rocm_arch == rocm_dev.name) {
rocm_arch_found = true;
break;
}
}
}
if (!rocm_arch_found) {
std::cerr << "Error there is not any active gpu with that ROCm arch " << std::endl;
return 1;
}
} catch (const HttpError& e) {
std::cerr << "Error setting ROCm arch: " << extract_server_error_message(e) << std::endl;
return 1;
} catch (const json::exception& e) {
std::cerr << "Error parsing system-info JSON: " << e.what() << std::endl;
return 1;
} catch (const std::exception& e) {
std::cerr << "Error setting ROCm arch: " << e.what() << std::endl;
return 1;
}
}

//Set the config var.
try {
json request_body = {{"rocm_arch", rocm_arch}};
std::string response = make_request("/internal/set", "POST", request_body.dump(), "application/json");

auto response_json = json::parse(response);
if (response_json.contains("status") && response_json["status"] == "success") {
std::cout << "ROCm arch setted successfully: " << rocm_arch << std::endl;
return 0;
} else {
std::cerr << "Failed to set rocm_arch config var" << std::endl;
return 1;
}
} catch (const HttpError& e) {
std::cerr << "Error setting ROCm architecture: " << extract_server_error_message(e) << std::endl;
return 1;
} catch (const json::exception& e) {
std::cerr << "Error parsing rocm_arch set response JSON: " << e.what() << std::endl;
return 1;
} catch (const std::exception& e) {
std::cerr << "Error setting ROCm architecture: " << e.what() << std::endl;
return 1;
}
}

int LemonadeClient::list_rocm_archs() const {

std::vector<lemon::ROCmDeviceInfo> rocm_devs;

// Check if rocm_archs config var exists at server.
try {
std::string config_response = make_request("/internal/config");
auto json_config_response = json::parse(config_response);
if (!json_config_response.contains("rocm_arch")) {
std::cout << "You do NOT have the rocm_arch config var in your config.json" << std::endl;
return 0;
}

} catch (const HttpError& e) {
std::cerr << "Error listing ROCm architectures: " << extract_server_error_message(e) << std::endl;
return 1;
} catch (const std::exception& e) {
std::cerr << "Error listing ROCm architectures: " << e.what() << std::endl;
return 1;
}

try {
std::cout << "List of avaible ROCm architectures." << std::endl;
std::cout << "----------------------------------------------" << std::endl;
std::string response = make_request("/api/v1/system-info");
auto json_response = json::parse(response);
if (json_response.contains("devices")) {
rocm_devs = lemon::ROCmArchUtils::rocm_arch_get_active_devices(json_response["devices"]);
}
std::cout << "architecture\t| vram (GB)\t|" << std::endl;
for (const auto& rocm_dev : rocm_devs) {

std::cout << " " << rocm_dev.name << "\t" << " " << rocm_dev.vram_gb << std::endl;
}
std::cout << "----------------------------------------------" << std::endl << std::endl;
return 0;

} catch (const HttpError& e) {
std::cerr << "Error listing gpus: " << extract_server_error_message(e) << std::endl;
//return {};
return 1;
} catch (const json::exception& e) {
std::cerr << "Error parsing gpus JSON: " << e.what() << std::endl;
return 1;
} catch (const std::exception& e) {
std::cerr << "Error listing gpus: " << e.what() << std::endl;
return 1;
}

}

// Helper function to parse SSE progress events
static std::string format_speed(double bytes_per_sec) {
if (bytes_per_sec < 1024.0) {
Expand Down
22 changes: 22 additions & 0 deletions src/cpp/cli/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ struct CliConfig {
bool codex_use_user_config = false;
std::string codex_model_provider = "lemonade";
std::string agent_args;
std::string rocm_arch = "";

// Cloud provider commands
std::string cloud_provider;
Expand Down Expand Up @@ -1106,6 +1107,16 @@ static int handle_scan_command(const CliConfig& config) {
return 0;
}

static int handle_rocm_arch_command(lemonade::LemonadeClient& client,
const CliConfig& config,
bool set_requested) {
if (set_requested) {
return client.set_rocm_arch(config.rocm_arch);
} else {
return client.list_rocm_archs();
}
}

int main(int argc, char* argv[]) {
// CLI11 configuration
CLI::App app{"Lemonade CLI - HTTP client for Lemonade Server"};
Expand Down Expand Up @@ -1143,6 +1154,14 @@ int main(int argc, char* argv[]) {
CLI::App* logs_cmd = app.add_subcommand("logs", "Open server logs in the web UI")->group("Server");
CLI::App* scan_cmd = app.add_subcommand("scan", "Scan for network beacons")->group("Server");

// List rocm arch
CLI::App* rocm_arch_cmd = app.add_subcommand("rocm-arch", "View or modify ROCm architectures")->group("Server");
// We add the subcommand set
CLI::App* rocm_arch_set_cmd = rocm_arch_cmd->add_subcommand("set", "Set ROCm architecture to use (e.g. gfx1200, gfx1150, ...)")->group("Subcommands");
rocm_arch_set_cmd->add_option("rocm_arch", config.rocm_arch, "ROCm architecture to use")->required();
rocm_arch_set_cmd->allow_extras(true);
rocm_arch_set_cmd->fallthrough(false);

// Config commands
CLI::App* config_cmd = app.add_subcommand("config", "View or modify server configuration")->group("Server");
CLI::App* config_set_cmd = config_cmd->add_subcommand("set", "Set configuration values (e.g., llamacpp.backend=rocm port=8123)")->group("Subcommands");
Expand Down Expand Up @@ -1456,6 +1475,9 @@ int main(int argc, char* argv[]) {
return handle_config_view(client);
} else if (cleanup_cmd->count() > 0) {
return client.cleanup_cache(config.dry_run);
} else if (rocm_arch_cmd->count() > 0) {
return handle_rocm_arch_command(client, config,
rocm_arch_set_cmd->count() > 0);
} else if (bench_cmd->count() > 0) {
auto bench_config = lemon_cli::build_bench_config(config.output_file, config.bench);
return lemon_cli::handle_bench_command(client, bench_config);
Expand Down
3 changes: 3 additions & 0 deletions src/cpp/include/lemon/runtime_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class RuntimeConfig {
bool enable_dgpu_gtt() const;
std::string rocm_channel() const;
std::string rocm_channel_for_recipe(const std::string& recipe) const;
std::string rocm_arch() const;

// Backend settings (nested)
json backend_config(const std::string& backend_name) const;
Expand Down Expand Up @@ -98,6 +99,8 @@ class RuntimeConfig {
const std::string& key,
const std::string& value);

static void validate_rocm_arch(const std::string& rocm_arch);

private:
// Validate a single key/value pair. Throws std::invalid_argument on failure.
void validate(const std::string& key, const json& value) const;
Expand Down
25 changes: 25 additions & 0 deletions src/cpp/include/lemon/utils/rocm_arch_utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

#include <string>
#include <nlohmann/json.hpp>

namespace lemon {

using json = nlohmann::json;

//ROCm Device information structure
struct ROCmDeviceInfo {
std::string family;
std::string name;
float vram_gb;
};

class ROCmArchUtils {

public:
static std::string rocm_arch_numeric_to_gfx(const std::string& numeric_version);
static bool rocm_arch_is_valid_gfx(const std::string& gfx_arch);
static std::vector<ROCmDeviceInfo> rocm_arch_get_active_devices(const json& json_devices);
};

} // namespace lemon

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing newline

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change done

4 changes: 4 additions & 0 deletions src/cpp/include/lemon_cli/lemonade_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ class LemonadeClient {
int install_backend(const std::string& recipe, const std::string& backend, bool force = false);
int uninstall_backend(const std::string& recipe, const std::string& backend);

// Rocm-arch selector commands
int list_rocm_archs() const;
int set_rocm_arch(const std::string& rocm_arch) const;

// Cloud provider commands. Each maps to one /v1/cloud/* or /v1/{install,
// uninstall} request. api_key is optional on install — when omitted the
// server relies on env var or a later /v1/cloud/auth POST.
Expand Down
40 changes: 40 additions & 0 deletions src/cpp/server/runtime_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "lemon/system_info.h"
#include "lemon/utils/aixlog.hpp"
#include "lemon/utils/path_utils.h"
#include "lemon/utils/rocm_arch_utils.h"
#include <algorithm>
#include <atomic>
#include <cctype>
Expand Down Expand Up @@ -132,6 +133,32 @@ void RuntimeConfig::validate_bin_path(const std::string& config_section,
// error if the tag does not exist on GitHub.
}

void RuntimeConfig::validate_rocm_arch(const std::string& rocm_arch) {
std::vector<ROCmDeviceInfo> rocm_devs;
bool rocm_arch_found = false;

// We check if rocm_arch has the correct format and if we have an active gpu with that architecture.
if (rocm_arch.empty() || rocm_arch == "auto" ) {
return ;
}
if (!lemon::ROCmArchUtils::rocm_arch_is_valid_gfx(rocm_arch)) {
throw std::invalid_argument("'rocm_arch' has NOT valid format");
}
json system_info = SystemInfoCache::get_system_info_with_cache();
if (system_info.contains("devices")) {
rocm_devs = lemon::ROCmArchUtils::rocm_arch_get_active_devices(system_info["devices"]);
for (const auto& rocm_dev : rocm_devs) {
if (rocm_arch == rocm_dev.name) {
rocm_arch_found = true;
break;
}
}
}
if (!rocm_arch_found) {
throw std::invalid_argument("There is NOT active GPU with selected 'rocm_arch'");
}
}

RuntimeConfig::RuntimeConfig(const json& config)
: config_(config) {
// Config is expected to already have defaults merged in (by ConfigFile::load).
Expand Down Expand Up @@ -232,6 +259,14 @@ std::string RuntimeConfig::rocm_channel_for_recipe(const std::string& recipe) co
return channel;
}

std::string RuntimeConfig::rocm_arch() const{
std::shared_lock lock(mutex_);
if (config_.contains("rocm_arch")){
return config_["rocm_arch"].get<std::string>();
}
return "";
}

json RuntimeConfig::backend_config(const std::string& backend_name) const {
std::shared_lock lock(mutex_);
if (config_.contains(backend_name) && config_[backend_name].is_object()) {
Expand Down Expand Up @@ -436,6 +471,11 @@ void RuntimeConfig::validate(const std::string& key, const json& value) const {
if (channel != "stable" && channel != "nightly") {
throw std::invalid_argument("'rocm_channel' must be either 'stable', or 'nightly'");
}
} else if (key == "rocm_arch") {
if (!value.is_string()) {
throw std::invalid_argument("'rocm_arch' must be a string");
}
validate_rocm_arch(value);
} else if (is_backend_name(key)) {
if (!value.is_object()) {
throw std::invalid_argument("'" + key + "' must be an object");
Expand Down
8 changes: 8 additions & 0 deletions src/cpp/server/system_info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2085,6 +2085,13 @@ std::string identify_npu_arch() {

std::string SystemInfo::get_rocm_arch() {
// Returns the ROCm architecture for the best available AMD GPU on this system
// Check if user has defined rocm_arch config var.
if (auto* cfg = RuntimeConfig::global()) {
std::string rocm_arch = cfg->rocm_arch();
if (!rocm_arch.empty() && rocm_arch != "auto") {
return cfg->rocm_arch();
}
}
// Checks iGPU first, then dGPUs. Returns empty string if no compatible GPU found.
try {
// Use cached system info to avoid re-detecting GPUs
Expand Down Expand Up @@ -2115,6 +2122,7 @@ std::string SystemInfo::get_rocm_arch() {
}

return ""; // No supported architecture found

}

static int cuda_sm_value(const std::string& arch) {
Expand Down
Loading
Loading