Skip to content

Commit 522a64a

Browse files
committed
feat(sdk): add LoRA adapter registry and management API
Full LoRA support across the SDK stack: C++ (runanywhere-commons): - Add supports_lora field to rac_model_info_t - Create LoRA registry (rac_lora_registry.h/cpp) with catalog storage - Add LoRA compatibility check to LLM component - Wire registry into rac_core global state - Add JNI bridge functions for all LoRA operations Kotlin SDK: - Add supportsLora to ModelInfo and registerModel() - Add LoRA public API: loadLoraAdapter, removeLoraAdapter, clearLoraAdapters, getLoadedLoraAdapters, checkLoraCompatibility - Add LoRA catalog API: registerLoraAdapter, loraAdaptersForModel, allRegisteredLoraAdapters - Add CppBridgeLoraRegistry bridge wrapper
1 parent 0929c86 commit 522a64a

20 files changed

Lines changed: 802 additions & 5 deletions

File tree

sdk/runanywhere-commons/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ set(RAC_INFRASTRUCTURE_SOURCES
201201
src/infrastructure/registry/service_registry.cpp
202202
src/infrastructure/download/download_manager.cpp
203203
src/infrastructure/model_management/model_registry.cpp
204+
src/infrastructure/model_management/lora_registry.cpp
204205
src/infrastructure/model_management/model_types.cpp
205206
src/infrastructure/model_management/model_paths.cpp
206207
src/infrastructure/model_management/model_strategy.cpp

sdk/runanywhere-commons/include/rac/core/rac_core.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,16 @@ RAC_API rac_result_t rac_get_model(const char* model_id, struct rac_model_info**
335335
*/
336336
RAC_API rac_result_t rac_get_model_by_path(const char* local_path, struct rac_model_info** out_model);
337337

338+
// =============================================================================
339+
// GLOBAL LORA REGISTRY API
340+
// =============================================================================
341+
342+
RAC_API struct rac_lora_registry* rac_get_lora_registry(void);
343+
RAC_API rac_result_t rac_register_lora(const struct rac_lora_entry* entry);
344+
RAC_API rac_result_t rac_get_lora_for_model(const char* model_id,
345+
struct rac_lora_entry*** out_entries,
346+
size_t* out_count);
347+
338348
#ifdef __cplusplus
339349
}
340350
#endif

sdk/runanywhere-commons/include/rac/features/llm/rac_llm_component.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,21 @@ RAC_API rac_result_t rac_llm_component_clear_lora(rac_handle_t handle);
263263
RAC_API rac_result_t rac_llm_component_get_lora_info(rac_handle_t handle,
264264
char** out_json);
265265

266+
/**
267+
* @brief Check if a LoRA adapter is compatible with the currently loaded model
268+
*
269+
* Validates that the adapter file exists and is potentially loadable.
270+
* This is a lightweight pre-check before attempting to load.
271+
*
272+
* @param handle Component handle
273+
* @param adapter_path Path to the LoRA adapter GGUF file
274+
* @param out_error Output: error message if incompatible (caller must free with rac_free), NULL if compatible
275+
* @return RAC_SUCCESS if compatible, error code otherwise
276+
*/
277+
RAC_API rac_result_t rac_llm_component_check_lora_compat(rac_handle_t handle,
278+
const char* adapter_path,
279+
char** out_error);
280+
266281
// =============================================================================
267282
// DESTRUCTION
268283
// =============================================================================
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* @file rac_lora_registry.h
3+
* @brief LoRA Adapter Registry - In-Memory LoRA Adapter Metadata Management
4+
*
5+
* Provides a centralized registry for LoRA adapter metadata across all SDKs.
6+
* Follows the same pattern as rac_model_registry.h.
7+
*
8+
* Apps register LoRA adapters at startup with explicit compatible model IDs.
9+
* SDKs can then query "which adapters work with this model" without
10+
* reinventing detection logic per platform.
11+
*
12+
* NOTE: This registry is metadata only. The runtime compat check
13+
* (rac_llm_component_check_lora_compat) remains the safety net at load time.
14+
*/
15+
16+
#ifndef RAC_LORA_REGISTRY_H
17+
#define RAC_LORA_REGISTRY_H
18+
19+
#include "rac/core/rac_error.h"
20+
#include "rac/core/rac_types.h"
21+
22+
#ifdef __cplusplus
23+
extern "C" {
24+
#endif
25+
26+
// TYPES
27+
28+
typedef struct rac_lora_entry {
29+
char* id; // Unique adapter identifier
30+
char* name; // Human-readable display name
31+
char* description; // Short description of what this adapter does
32+
char* download_url; // Direct download URL (.gguf file)
33+
char* filename; // Filename to save as on disk
34+
char** compatible_model_ids; // Explicit list of compatible base model IDs
35+
size_t compatible_model_count;
36+
int64_t file_size; // File size in bytes (0 if unknown)
37+
float default_scale; // Recommended LoRA scale (e.g. 0.3)
38+
} rac_lora_entry_t;
39+
40+
typedef struct rac_lora_registry* rac_lora_registry_handle_t;
41+
42+
// LIFECYCLE
43+
RAC_API rac_result_t rac_lora_registry_create(rac_lora_registry_handle_t* out_handle);
44+
RAC_API void rac_lora_registry_destroy(rac_lora_registry_handle_t handle);
45+
46+
// REGISTRATION
47+
RAC_API rac_result_t rac_lora_registry_register(rac_lora_registry_handle_t handle,
48+
const rac_lora_entry_t* entry);
49+
RAC_API rac_result_t rac_lora_registry_remove(rac_lora_registry_handle_t handle,
50+
const char* adapter_id);
51+
52+
// QUERIES
53+
RAC_API rac_result_t rac_lora_registry_get_all(rac_lora_registry_handle_t handle,
54+
rac_lora_entry_t*** out_entries,
55+
size_t* out_count);
56+
RAC_API rac_result_t rac_lora_registry_get_for_model(rac_lora_registry_handle_t handle,
57+
const char* model_id,
58+
rac_lora_entry_t*** out_entries,
59+
size_t* out_count);
60+
RAC_API rac_result_t rac_lora_registry_get(rac_lora_registry_handle_t handle,
61+
const char* adapter_id,
62+
rac_lora_entry_t** out_entry);
63+
64+
// MEMORY
65+
RAC_API void rac_lora_entry_free(rac_lora_entry_t* entry);
66+
RAC_API void rac_lora_entry_array_free(rac_lora_entry_t** entries, size_t count);
67+
RAC_API rac_lora_entry_t* rac_lora_entry_copy(const rac_lora_entry_t* entry);
68+
69+
#ifdef __cplusplus
70+
}
71+
#endif
72+
73+
#endif /* RAC_LORA_REGISTRY_H */

sdk/runanywhere-commons/include/rac/infrastructure/model_management/rac_model_types.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,9 @@ typedef struct rac_model_info {
247247
/** Whether model supports thinking/reasoning */
248248
rac_bool_t supports_thinking;
249249

250+
/** Whether model supports LoRA adapters */
251+
rac_bool_t supports_lora;
252+
250253
/** Tags (NULL-terminated array of strings, can be NULL) */
251254
char** tags;
252255
size_t tag_count;

sdk/runanywhere-commons/src/core/rac_core.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "rac/core/rac_structured_error.h"
1818
#include "rac/infrastructure/device/rac_device_manager.h"
1919
#include "rac/infrastructure/model_management/rac_model_registry.h"
20+
#include "rac/infrastructure/model_management/rac_lora_registry.h"
2021
#if !defined(RAC_PLATFORM_ANDROID)
2122
#include "rac/features/diffusion/rac_diffusion_model_registry.h"
2223
#endif
@@ -35,6 +36,10 @@ static std::string s_log_tag = "RAC";
3536
static rac_model_registry_handle_t s_model_registry = nullptr;
3637
static std::mutex s_model_registry_mutex;
3738

39+
// Global LoRA registry
40+
static rac_lora_registry_handle_t s_lora_registry = nullptr;
41+
static std::mutex s_lora_registry_mutex;
42+
3843
// Version info
3944
static const char* s_version_string = "1.0.0";
4045
static const rac_version_t s_version = {
@@ -288,4 +293,34 @@ rac_bool_t rac_framework_is_platform_service(rac_inference_framework_t framework
288293
}
289294
}
290295

296+
// =============================================================================
297+
// GLOBAL LORA REGISTRY
298+
// =============================================================================
299+
300+
rac_lora_registry_handle_t rac_get_lora_registry(void) {
301+
std::lock_guard<std::mutex> lock(s_lora_registry_mutex);
302+
if (s_lora_registry == nullptr) {
303+
rac_result_t result = rac_lora_registry_create(&s_lora_registry);
304+
if (result != RAC_SUCCESS) {
305+
RAC_LOG_ERROR("RAC.Core", "Failed to create global LoRA registry");
306+
return nullptr;
307+
}
308+
RAC_LOG_INFO("RAC.Core", "Global LoRA registry created");
309+
}
310+
return s_lora_registry;
311+
}
312+
313+
rac_result_t rac_register_lora(const rac_lora_entry_t* entry) {
314+
rac_lora_registry_handle_t registry = rac_get_lora_registry();
315+
if (registry == nullptr) return RAC_ERROR_NOT_INITIALIZED;
316+
return rac_lora_registry_register(registry, entry);
317+
}
318+
319+
rac_result_t rac_get_lora_for_model(const char* model_id, rac_lora_entry_t*** out_entries,
320+
size_t* out_count) {
321+
rac_lora_registry_handle_t registry = rac_get_lora_registry();
322+
if (registry == nullptr) return RAC_ERROR_NOT_INITIALIZED;
323+
return rac_lora_registry_get_for_model(registry, model_id, out_entries, out_count);
324+
}
325+
291326
} // extern "C"

sdk/runanywhere-commons/src/features/llm/llm_component.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,42 @@ extern "C" rac_result_t rac_llm_component_get_lora_info(rac_handle_t handle,
863863
return llm_service->ops->get_lora_info(llm_service->impl, out_json);
864864
}
865865

866+
extern "C" rac_result_t rac_llm_component_check_lora_compat(rac_handle_t handle,
867+
const char* adapter_path,
868+
char** out_error) {
869+
if (!handle)
870+
return RAC_ERROR_INVALID_HANDLE;
871+
if (!adapter_path || !out_error)
872+
return RAC_ERROR_INVALID_ARGUMENT;
873+
874+
*out_error = nullptr;
875+
876+
auto* component = reinterpret_cast<rac_llm_component*>(handle);
877+
std::lock_guard<std::mutex> lock(component->mtx);
878+
879+
rac_handle_t service = rac_lifecycle_get_service(component->lifecycle);
880+
if (!service) {
881+
*out_error = strdup("No model loaded");
882+
return RAC_ERROR_COMPONENT_NOT_READY;
883+
}
884+
885+
// Check if the adapter file path is non-empty
886+
if (strlen(adapter_path) == 0) {
887+
*out_error = strdup("Empty adapter path");
888+
return RAC_ERROR_INVALID_ARGUMENT;
889+
}
890+
891+
// Basic pre-check: verify the backend supports LoRA at all
892+
auto* llm_service = reinterpret_cast<rac_llm_service_t*>(service);
893+
if (!llm_service->ops || !llm_service->ops->load_lora) {
894+
*out_error = strdup("Backend does not support LoRA adapters");
895+
return RAC_ERROR_NOT_SUPPORTED;
896+
}
897+
898+
// Adapter path and backend both valid - considered compatible
899+
return RAC_SUCCESS;
900+
}
901+
866902
// =============================================================================
867903
// STATE QUERY API
868904
// =============================================================================

0 commit comments

Comments
 (0)