Skip to content

Commit 657fa34

Browse files
Fix lora [ios changes + fixign some bugs identified in the other PR #414] (#420)
* refactor * fixing lora
1 parent 12a6613 commit 657fa34

27 files changed

Lines changed: 495 additions & 265 deletions

File tree

.idea/vcs.xml

Lines changed: 1 addition & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -222,11 +222,10 @@ func ragProducts() -> [Product] {
222222
}
223223

224224
/// RAG dependency for the RunAnywhere core target
225+
/// NOTE: Core already accesses RAG C headers via CRACommons umbrella (rac_rag.h, rac_rag_pipeline.h).
226+
/// No additional dependency needed — RAGBackend is only used by RAGRuntime.
225227
func ragCoreDependencies() -> [Target.Dependency] {
226-
guard useLocalBinaries || ragRemoteBinaryAvailable else { return [] }
227-
return [
228-
"RAGBackend",
229-
]
228+
return []
230229
}
231230

232231
/// RAG-related targets (C bridge + Swift runtime)
@@ -246,6 +245,8 @@ func ragTargets() -> [Target] {
246245
dependencies: [
247246
"RunAnywhere",
248247
"RAGBackend",
248+
"ONNXRuntime",
249+
"LlamaCPPRuntime",
249250
],
250251
path: "sdk/runanywhere-swift/Sources/RAGRuntime",
251252
exclude: ["include"],
@@ -291,12 +292,12 @@ func binaryTargets() -> [Target] {
291292

292293
// Local combined ONNX Runtime xcframework (iOS + macOS)
293294
// Created by: cd sdk/runanywhere-swift && ./scripts/create-onnxruntime-xcframework.sh
294-
// targets.append(
295-
// .binaryTarget(
296-
// name: "ONNXRuntimeBinary",
297-
// path: "sdk/runanywhere-swift/Binaries/onnxruntime.xcframework"
298-
// )
299-
// )
295+
targets.append(
296+
.binaryTarget(
297+
name: "ONNXRuntimeBinary",
298+
path: "sdk/runanywhere-swift/Binaries/onnxruntime.xcframework"
299+
)
300+
)
300301

301302
return targets
302303
} else {
@@ -341,4 +342,4 @@ func binaryTargets() -> [Target] {
341342

342343
return targets
343344
}
344-
}
345+
}

examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/lora/LoraAdapterPickerSheet.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ fun LoraAdapterPickerSheet(
132132
isDownloading = state.downloadingAdapterId == entry.id,
133133
downloadProgress = if (state.downloadingAdapterId == entry.id) state.downloadProgress else 0f,
134134
onDownload = { loraViewModel.downloadAdapter(entry) },
135+
onCancelDownload = { loraViewModel.cancelDownload() },
135136
onApply = { scale ->
136137
val path = loraViewModel.localPath(entry) ?: return@CatalogAdapterRow
137138
loraViewModel.loadAdapter(path, scale)
@@ -218,6 +219,7 @@ private fun CatalogAdapterRow(
218219
isDownloading: Boolean,
219220
downloadProgress: Float,
220221
onDownload: () -> Unit,
222+
onCancelDownload: () -> Unit,
221223
onApply: (Float) -> Unit,
222224
onRemove: () -> Unit,
223225
) {
@@ -314,6 +316,10 @@ private fun CatalogAdapterRow(
314316
"${(downloadProgress * 100).toInt()}%",
315317
style = MaterialTheme.typography.bodySmall,
316318
)
319+
Spacer(modifier = Modifier.width(Dimensions.smallMedium))
320+
IconButton(onClick = onCancelDownload, modifier = Modifier.size(24.dp)) {
321+
Icon(Icons.Default.Close, contentDescription = "Cancel download", modifier = Modifier.size(16.dp))
322+
}
317323
}
318324
} else {
319325
// Not downloaded — show download button

examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/lora/LoraManagerScreen.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import androidx.compose.foundation.lazy.LazyColumn
1515
import androidx.compose.foundation.lazy.items
1616
import androidx.compose.foundation.shape.RoundedCornerShape
1717
import androidx.compose.material.icons.Icons
18+
import androidx.compose.material.icons.filled.Close
1819
import androidx.compose.material.icons.filled.CloudDownload
1920
import androidx.compose.material.icons.filled.Delete
2021
import androidx.compose.material.icons.filled.DeleteForever
@@ -153,6 +154,7 @@ fun LoraManagerScreen(
153154
isDownloading = state.downloadingAdapterId == entry.id,
154155
downloadProgress = if (state.downloadingAdapterId == entry.id) state.downloadProgress else 0f,
155156
onDownload = { loraViewModel.downloadAdapter(entry) },
157+
onCancelDownload = { loraViewModel.cancelDownload() },
156158
onDelete = { loraViewModel.deleteAdapter(entry) },
157159
)
158160
}
@@ -181,6 +183,7 @@ private fun RegisteredAdapterCard(
181183
isDownloading: Boolean,
182184
downloadProgress: Float,
183185
onDownload: () -> Unit,
186+
onCancelDownload: () -> Unit,
184187
onDelete: () -> Unit,
185188
) {
186189
Card(
@@ -272,6 +275,10 @@ private fun RegisteredAdapterCard(
272275
"Downloading ${(downloadProgress * 100).toInt()}%",
273276
style = MaterialTheme.typography.bodySmall,
274277
)
278+
Spacer(modifier = Modifier.width(Dimensions.smallMedium))
279+
IconButton(onClick = onCancelDownload, modifier = Modifier.size(24.dp)) {
280+
Icon(Icons.Default.Close, contentDescription = "Cancel download", modifier = Modifier.size(16.dp))
281+
}
275282
}
276283
}
277284
isDownloaded -> {

examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/lora/LoraViewModel.kt

Lines changed: 54 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import kotlinx.coroutines.Job
2121
import kotlinx.coroutines.flow.MutableStateFlow
2222
import kotlinx.coroutines.flow.StateFlow
2323
import kotlinx.coroutines.flow.asStateFlow
24+
import kotlinx.coroutines.flow.update
2425
import kotlinx.coroutines.launch
2526
import kotlinx.coroutines.withContext
2627
import java.io.File
@@ -60,14 +61,16 @@ class LoraViewModel(application: Application) : AndroidViewModel(application) {
6061
val (registered, loaded) = withContext(Dispatchers.IO) {
6162
RunAnywhere.allRegisteredLoraAdapters() to RunAnywhere.getLoadedLoraAdapters()
6263
}
63-
_uiState.value = _uiState.value.copy(
64-
registeredAdapters = registered,
65-
loadedAdapters = loaded,
66-
error = null,
67-
)
64+
_uiState.update {
65+
it.copy(
66+
registeredAdapters = registered,
67+
loadedAdapters = loaded,
68+
error = null,
69+
)
70+
}
6871
} catch (e: Exception) {
6972
Timber.e(e, "Failed to refresh LoRA state")
70-
_uiState.value = _uiState.value.copy(error = e.message)
73+
_uiState.update { it.copy(error = e.message) }
7174
}
7275
}
7376
}
@@ -79,14 +82,16 @@ class LoraViewModel(application: Application) : AndroidViewModel(application) {
7982
val (compatible, loaded) = withContext(Dispatchers.IO) {
8083
RunAnywhere.loraAdaptersForModel(modelId) to RunAnywhere.getLoadedLoraAdapters()
8184
}
82-
_uiState.value = _uiState.value.copy(
83-
compatibleAdapters = compatible,
84-
loadedAdapters = loaded,
85-
error = null,
86-
)
85+
_uiState.update {
86+
it.copy(
87+
compatibleAdapters = compatible,
88+
loadedAdapters = loaded,
89+
error = null,
90+
)
91+
}
8792
} catch (e: Exception) {
8893
Timber.e(e, "Failed to refresh for model $modelId")
89-
_uiState.value = _uiState.value.copy(error = e.message)
94+
_uiState.update { it.copy(error = e.message) }
9095
}
9196
}
9297
}
@@ -98,11 +103,11 @@ class LoraViewModel(application: Application) : AndroidViewModel(application) {
98103
val config = LoRAAdapterConfig(path = path, scale = scale)
99104
withContext(Dispatchers.IO) { RunAnywhere.loadLoraAdapter(config) }
100105
val loaded = withContext(Dispatchers.IO) { RunAnywhere.getLoadedLoraAdapters() }
101-
_uiState.value = _uiState.value.copy(loadedAdapters = loaded, error = null)
106+
_uiState.update { it.copy(loadedAdapters = loaded, error = null) }
102107
Timber.i("Loaded LoRA adapter: $path (scale=$scale)")
103108
} catch (e: Exception) {
104109
Timber.e(e, "Failed to load LoRA adapter")
105-
_uiState.value = _uiState.value.copy(error = "Failed to load adapter: ${e.message}")
110+
_uiState.update { it.copy(error = "Failed to load adapter: ${e.message}") }
106111
}
107112
}
108113
}
@@ -113,11 +118,11 @@ class LoraViewModel(application: Application) : AndroidViewModel(application) {
113118
try {
114119
withContext(Dispatchers.IO) { RunAnywhere.removeLoraAdapter(path) }
115120
val loaded = withContext(Dispatchers.IO) { RunAnywhere.getLoadedLoraAdapters() }
116-
_uiState.value = _uiState.value.copy(loadedAdapters = loaded, error = null)
121+
_uiState.update { it.copy(loadedAdapters = loaded, error = null) }
117122
Timber.i("Unloaded LoRA adapter: $path")
118123
} catch (e: Exception) {
119124
Timber.e(e, "Failed to unload LoRA adapter")
120-
_uiState.value = _uiState.value.copy(error = "Failed to unload adapter: ${e.message}")
125+
_uiState.update { it.copy(error = "Failed to unload adapter: ${e.message}") }
121126
}
122127
}
123128
}
@@ -127,11 +132,11 @@ class LoraViewModel(application: Application) : AndroidViewModel(application) {
127132
viewModelScope.launch {
128133
try {
129134
withContext(Dispatchers.IO) { RunAnywhere.clearLoraAdapters() }
130-
_uiState.value = _uiState.value.copy(loadedAdapters = emptyList(), error = null)
135+
_uiState.update { it.copy(loadedAdapters = emptyList(), error = null) }
131136
Timber.i("Cleared all LoRA adapters")
132137
} catch (e: Exception) {
133138
Timber.e(e, "Failed to clear LoRA adapters")
134-
_uiState.value = _uiState.value.copy(error = e.message)
139+
_uiState.update { it.copy(error = e.message) }
135140
}
136141
}
137142
}
@@ -167,11 +172,13 @@ class LoraViewModel(application: Application) : AndroidViewModel(application) {
167172
fun downloadAdapter(entry: LoraAdapterCatalogEntry) {
168173
if (_uiState.value.downloadingAdapterId != null) return
169174

170-
_uiState.value = _uiState.value.copy(
171-
downloadingAdapterId = entry.id,
172-
downloadProgress = 0f,
173-
error = null,
174-
)
175+
_uiState.update {
176+
it.copy(
177+
downloadingAdapterId = entry.id,
178+
downloadProgress = 0f,
179+
error = null,
180+
)
181+
}
175182

176183
downloadJob = viewModelScope.launch {
177184
val destFile = File(loraDir, entry.filename)
@@ -196,11 +203,12 @@ class LoraViewModel(application: Application) : AndroidViewModel(application) {
196203
downloaded += bytesRead
197204
if (totalSize > 0) {
198205
val progress = (downloaded.toFloat() / totalSize).coerceIn(0f, 1f)
199-
_uiState.value = _uiState.value.copy(downloadProgress = progress)
206+
_uiState.update { it.copy(downloadProgress = progress) }
200207
}
201208
}
202209
}
203210
}
211+
destFile.delete()
204212
if (!tmpFile.renameTo(destFile)) {
205213
tmpFile.delete()
206214
throw Exception("Failed to move downloaded file to final location")
@@ -209,17 +217,21 @@ class LoraViewModel(application: Application) : AndroidViewModel(application) {
209217
}
210218

211219
Timber.i("Downloaded LoRA adapter: ${entry.name} -> ${destFile.absolutePath}")
212-
_uiState.value = _uiState.value.copy(
213-
downloadingAdapterId = null,
214-
downloadProgress = 0f,
215-
)
220+
_uiState.update {
221+
it.copy(
222+
downloadingAdapterId = null,
223+
downloadProgress = 0f,
224+
)
225+
}
216226
} catch (e: Exception) {
217227
Timber.e(e, "Failed to download LoRA adapter: ${entry.name}")
218-
_uiState.value = _uiState.value.copy(
219-
downloadingAdapterId = null,
220-
downloadProgress = 0f,
221-
error = "Download failed: ${e.message}",
222-
)
228+
_uiState.update {
229+
it.copy(
230+
downloadingAdapterId = null,
231+
downloadProgress = 0f,
232+
error = "Download failed: ${e.message}",
233+
)
234+
}
223235
} finally {
224236
if (!downloadComplete && tmpFile.exists()) {
225237
tmpFile.delete()
@@ -232,10 +244,12 @@ class LoraViewModel(application: Application) : AndroidViewModel(application) {
232244
fun cancelDownload() {
233245
downloadJob?.cancel()
234246
downloadJob = null
235-
_uiState.value = _uiState.value.copy(
236-
downloadingAdapterId = null,
237-
downloadProgress = 0f,
238-
)
247+
_uiState.update {
248+
it.copy(
249+
downloadingAdapterId = null,
250+
downloadProgress = 0f,
251+
)
252+
}
239253
}
240254

241255
/** Delete a downloaded adapter file. Always attempts unload first (ignores if not loaded). */
@@ -255,15 +269,15 @@ class LoraViewModel(application: Application) : AndroidViewModel(application) {
255269
}
256270
}
257271
val loaded = withContext(Dispatchers.IO) { RunAnywhere.getLoadedLoraAdapters() }
258-
_uiState.value = _uiState.value.copy(loadedAdapters = loaded)
272+
_uiState.update { it.copy(loadedAdapters = loaded) }
259273
} catch (e: Exception) {
260274
Timber.e(e, "Failed to delete adapter: ${entry.filename}")
261-
_uiState.value = _uiState.value.copy(error = "Delete failed: ${e.message}")
275+
_uiState.update { it.copy(error = "Delete failed: ${e.message}") }
262276
}
263277
}
264278
}
265279

266280
fun clearError() {
267-
_uiState.value = _uiState.value.copy(error = null)
281+
_uiState.update { it.copy(error = null) }
268282
}
269283
}

examples/ios/RunAnywhereAI/Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/ios/RunAnywhereAI/RunAnywhereAI.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
541C59DA2E63772A00DD7839 /* RunAnywhere in Frameworks */ = {isa = PBXBuildFile; productRef = 541C59D92E63772A00DD7839 /* RunAnywhere */; };
1111
58ABEDD22ED16DA40058D033 /* RunAnywhereONNX in Frameworks */ = {isa = PBXBuildFile; productRef = 58ABEDD12ED16DA40058D033 /* RunAnywhereONNX */; };
1212
58LLAMACPP12ED16DA40058D0 /* RunAnywhereLlamaCPP in Frameworks */ = {isa = PBXBuildFile; productRef = 58LLAMACPP02ED16DA40058D0 /* RunAnywhereLlamaCPP */; };
13+
58RAGBKND12ED16DA40058D03 /* RunAnywhereRAG in Frameworks */ = {isa = PBXBuildFile; productRef = 58RAGBKND02ED16DA40058D03 /* RunAnywhereRAG */; };
1314
/* End PBXBuildFile section */
1415

1516
/* Begin PBXContainerItemProxy section */
@@ -65,6 +66,7 @@
6566
58ABEDD22ED16DA40058D033 /* RunAnywhereONNX in Frameworks */,
6667
541C59DA2E63772A00DD7839 /* RunAnywhere in Frameworks */,
6768
58LLAMACPP12ED16DA40058D0 /* RunAnywhereLlamaCPP in Frameworks */,
69+
58RAGBKND12ED16DA40058D03 /* RunAnywhereRAG in Frameworks */,
6870
);
6971
runOnlyForDeploymentPostprocessing = 0;
7072
};
@@ -140,6 +142,7 @@
140142
541C59D92E63772A00DD7839 /* RunAnywhere */,
141143
58ABEDD12ED16DA40058D033 /* RunAnywhereONNX */,
142144
58LLAMACPP02ED16DA40058D0 /* RunAnywhereLlamaCPP */,
145+
58RAGBKND02ED16DA40058D03 /* RunAnywhereRAG */,
143146
);
144147
productName = RunAnywhereAI;
145148
productReference = 5480A1F02E2F250200337F2F /* RunAnywhereAI.app */;
@@ -706,6 +709,11 @@
706709
package = 58E021172E52A86000B722EF /* XCLocalSwiftPackageReference "../../.." */;
707710
productName = RunAnywhereLlamaCPP;
708711
};
712+
58RAGBKND02ED16DA40058D03 /* RunAnywhereRAG */ = {
713+
isa = XCSwiftPackageProductDependency;
714+
package = 58E021172E52A86000B722EF /* XCLocalSwiftPackageReference "../../.." */;
715+
productName = RunAnywhereRAG;
716+
};
709717
/* End XCSwiftPackageProductDependency section */
710718
};
711719
rootObject = 5480A1E82E2F250200337F2F /* Project object */;

examples/ios/RunAnywhereAI/RunAnywhereAI/App/RunAnywhereAIApp.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,10 @@ struct RunAnywhereAIApp: App {
370370

371371
logger.info("✅ Diffusion models registered (Apple Stable Diffusion / CoreML only)")
372372

373+
// Register LoRA adapters into SDK registry (same catalog as Android)
374+
await LoRAAdapterCatalog.registerAll()
375+
logger.info("✅ LoRA adapters registered (\(LoRAAdapterCatalog.adapters.count))")
376+
373377
logger.info("🎉 All modules and models registered")
374378
}
375379
}

0 commit comments

Comments
 (0)