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
13 changes: 13 additions & 0 deletions engines/llamacpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,25 @@ include(LoadVersions)
# nativeGetVersion() doesn't drift from the actual linked llama.cpp build.
add_compile_definitions(RAC_LLAMACPP_VERSION="${RAC_LLAMACPP_VERSION}")

# Issue #480: Apply Metal residency set guard patch for A12/A13 devices (iOS only).
# A12 Bionic (iPhone XS Max) and A13 do not support MTLResidencySet, but the
# upstream @available(iOS 18.0) guard passes on those devices, causing a SIGABRT
# in newResidencySetWithDescriptor:. The patch adds a
# [device supportsFamily:MTLGPUFamilyApple7] check before enabling residency sets
# (there is no `-[MTLDevice supportsResidencySets]` API).
if(RAC_PLATFORM_IOS)
set(LLAMACPP_PATCH_COMMAND git apply --ignore-whitespace "${CMAKE_CURRENT_SOURCE_DIR}/patches/001-metal-residency-set-guard.patch")
else()
set(LLAMACPP_PATCH_COMMAND "")
endif()

FetchContent_Declare(
llamacpp
GIT_REPOSITORY https://github.com/ggerganov/llama.cpp.git
GIT_TAG ${RAC_LLAMACPP_VERSION}
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
PATCH_COMMAND ${LLAMACPP_PATCH_COMMAND}
)

# Configure llama.cpp build options
Expand Down
45 changes: 45 additions & 0 deletions engines/llamacpp/patches/001-metal-residency-set-guard.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
From: RunAnywhere SDK <dev@runanywhere.ai>
Date: Tue, 8 Jul 2026 10:30:00 +0530
Subject: [PATCH] ggml-metal: guard residency set with supportsFamily check

The @available(iOS 18.0, ...) check passes on A12/A13 devices (iPhone XS
Max, iPhone 11) because these devices run iOS 18, but their GPU does not
support MTLResidencySet. Calling newResidencySetWithDescriptor: on these
devices triggers a Metal debug assertion failure:

-[MTLDebugDevice newResidencySetWithDescriptor:error:]:2785:
failed assertion 'device does not support residency sets.'

Fix: also check [device supportsFamily:MTLGPUFamilyApple6] when
initializing the use_residency_sets flag in the device properties, so
the entire residency set code path is skipped on unsupported GPUs.
(There is no `-[MTLDevice supportsResidencySets]` API; supportsFamily:
is the real, documented way to query GPU-family-gated capabilities and
is the real runtime capability check for this GPU-family-gated feature.)

Residency sets require Apple GPU family 6+ (A13+).

Comment on lines +5 to +21

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Patch comment contradicts itself on A13 residency-set support.

Lines 5-8 state A12/A13 GPUs "do not support MTLResidencySet," but line 20 states "Residency sets require Apple GPU family 6+ (A13+)," implying A13 does support them. If A13 truly lacks support, the guard at line 39 (supportsFamily:MTLGPUFamilyApple6) would still enable residency sets on A13 devices, leaving the crash unfixed on iPhone 11. Clarify whether the iPhone 11 crash is real-hardware or simulator-only, and update the comment to resolve the contradiction.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@engines/llamacpp/patches/001-metal-residency-set-guard.patch` around lines 5
- 21, Resolve the contradictory residency-support documentation in the patch
comment: verify whether iPhone 11/A13 hardware supports the capability or
whether the reported crash is simulator-only, then update the explanatory text
to state the correct supported GPU families and affected devices. Keep the
`supportsFamily:MTLGPUFamilyApple6` guard aligned with that verified runtime
behavior.

Fixes: https://github.com/RunanywhereAI/runanywhere-sdks/issues/480
---
ggml/src/ggml-metal/ggml-metal-device.m | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/ggml/src/ggml-metal/ggml-metal-device.m b/ggml/src/ggml-metal/ggml-metal-device.m
index 0000000..0000001 100644
--- a/ggml/src/ggml-metal/ggml-metal-device.m
+++ b/ggml/src/ggml-metal/ggml-metal-device.m
@@ -824,9 +824,12 @@ static void ggml_metal_device_init(ggml_metal_device_t dev) {
}
}

- dev->props.use_residency_sets = true;
+ dev->props.use_residency_sets = false;
#if defined(GGML_METAL_HAS_RESIDENCY_SETS)
- dev->props.use_residency_sets = getenv("GGML_METAL_NO_RESIDENCY") == nil;
+ if ([dev->mtl_device supportsFamily:MTLGPUFamilyApple6]) {
+ dev->props.use_residency_sets = getenv("GGML_METAL_NO_RESIDENCY") == nil;
+ }
+ // else: device GPU does not support residency sets (A12 and older), leave as false
#endif

dev->props.use_shared_buffers = dev->props.has_unified_memory;
8 changes: 8 additions & 0 deletions sdk/runanywhere-commons/exports/RACommons.exports
Original file line number Diff line number Diff line change
Expand Up @@ -534,16 +534,22 @@ _rac_platform_tts_set_callbacks
# Kotlin bridges during the proto-byte migration (CPP_PROTO_OWNERSHIP.md §2.1).
_rac_llm_cancel
_rac_llm_component_cancel
_rac_llm_component_append_context
_rac_llm_component_cleanup
_rac_llm_component_clear_context
_rac_llm_component_create
_rac_llm_component_destroy
_rac_llm_component_generate
_rac_llm_component_generate_from_context
_rac_llm_component_generate_stream
_rac_llm_component_get_model_id
_rac_llm_component_inject_system_prompt
_rac_llm_component_is_loaded
_rac_llm_component_load_model
_rac_llm_component_supports_streaming
_rac_llm_component_unload
_rac_llm_append_context_lifecycle
_rac_llm_clear_context_lifecycle
_rac_llm_create
_rac_llm_generate
_rac_llm_generate_stream
Expand Down Expand Up @@ -949,7 +955,9 @@ _rac_llm_component_load_lora
_rac_llm_component_remove_lora
_rac_llm_destroy
_rac_llm_generate_from_context
_rac_llm_generate_from_context_proto
_rac_llm_get_info
_rac_llm_inject_system_prompt_lifecycle
_rac_llm_initialize
_rac_llm_inject_system_prompt
_rac_llm_platform_create
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,64 @@ RAC_API rac_lifecycle_state_t rac_llm_component_get_state(rac_handle_t handle);
RAC_API rac_result_t rac_llm_component_get_metrics(rac_handle_t handle,
rac_lifecycle_metrics_t* out_metrics);

// =============================================================================
// ADAPTIVE CONTEXT API
// =============================================================================

/**
* @brief Inject a system prompt into the loaded model's adaptive context
*
* Clears existing KV cache, then seeds it with the given prompt.
* Optional - returns RAC_ERROR_NOT_SUPPORTED if backend doesn't support it.
*
* @param handle Component handle
* @param prompt System prompt text
* @return RAC_SUCCESS or error code
*/
RAC_API rac_result_t rac_llm_component_inject_system_prompt(rac_handle_t handle,
const char* prompt);

/**
* @brief Append text to the loaded model's adaptive context
*
* Does not clear existing KV state - accumulates context incrementally.
* Optional - returns RAC_ERROR_NOT_SUPPORTED if backend doesn't support it.
*
* @param handle Component handle
* @param text Text to append
* @return RAC_SUCCESS or error code
*/
RAC_API rac_result_t rac_llm_component_append_context(rac_handle_t handle, const char* text);

/**
* @brief Generate a response from accumulated adaptive context
*
* Unlike rac_llm_component_generate(), this does not clear the KV cache first.
* Use after inject_system_prompt + append_context to generate from accumulated state.
* Optional - returns RAC_ERROR_NOT_SUPPORTED if backend doesn't support it.
*
* @param handle Component handle
* @param query Query/suffix text to append before generation
* @param options Generation options (can be NULL for defaults)
* @param out_result Output: Generation result (caller must free with rac_llm_result_free)
* @return RAC_SUCCESS or error code
*/
RAC_API rac_result_t rac_llm_component_generate_from_context(rac_handle_t handle,
const char* query,
const rac_llm_options_t* options,
rac_llm_result_t* out_result);

/**
* @brief Clear all adaptive context state
*
* Resets the LLM's context for a fresh adaptive query cycle.
* Optional - returns RAC_ERROR_NOT_SUPPORTED if backend doesn't support it.
*
* @param handle Component handle
* @return RAC_SUCCESS or error code
*/
RAC_API rac_result_t rac_llm_component_clear_context(rac_handle_t handle);

// =============================================================================
// LORA ADAPTER API
// =============================================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
* - rac_llm_service_ops_t and rac_llm_service_t: `internal`. Engine
* dispatch contract.
* - Proto-byte APIs (rac_llm_generate_proto,
* rac_llm_generate_stream_proto, rac_llm_cancel_proto):
* rac_llm_generate_stream_proto, rac_llm_cancel_proto, plus
* lifecycle adaptive-context helpers):
* `SDK-facing default` over runanywhere.v1.LLMGenerateRequest /
* LLMGenerationResult / LLMStreamEvent / SDKEvent bytes.
* - Struct APIs (rac_llm_create, initialize, generate,
Expand Down Expand Up @@ -277,6 +278,37 @@ RAC_API rac_result_t rac_llm_generate_stream_proto(const uint8_t* request_proto_
*/
RAC_API rac_result_t rac_llm_cancel_proto(rac_proto_buffer_t* out_event);

/**
* @brief Inject a system prompt into the lifecycle-owned LLM's adaptive context
*
* Uses the LLM model loaded through rac_model_lifecycle_load_proto().
*/
RAC_API rac_result_t rac_llm_inject_system_prompt_lifecycle(const char* prompt);

/**
* @brief Append text to the lifecycle-owned LLM's adaptive context
*
* Uses the LLM model loaded through rac_model_lifecycle_load_proto().
*/
RAC_API rac_result_t rac_llm_append_context_lifecycle(const char* text);

/**
* @brief Generate from the lifecycle-owned LLM's accumulated adaptive context
*
* Accepts serialized runanywhere.v1.LLMGenerateRequest bytes and returns a
* serialized runanywhere.v1.LLMGenerationResult in out_result.
*/
RAC_API rac_result_t rac_llm_generate_from_context_proto(const uint8_t* request_proto_bytes,
size_t request_proto_size,
rac_proto_buffer_t* out_result);

/**
* @brief Clear the lifecycle-owned LLM's adaptive context
*
* Uses the LLM model loaded through rac_model_lifecycle_load_proto().
*/
RAC_API rac_result_t rac_llm_clear_context_lifecycle(void);

// =============================================================================
// ADAPTIVE CONTEXT API - For RAG and similar pipelines
// =============================================================================
Expand Down
Loading
Loading