Skip to content

Commit 7509e61

Browse files
Siddhesh2377claude
andcommitted
Add getChip() NPU chip detection across Kotlin, Flutter, and React Native SDKs
Adds public getChip() API that detects Qualcomm NPU chipsets (Snapdragon 8 Elite / 8 Elite Gen 5) and returns an identifier used to construct dynamic HuggingFace download URLs for Genie NPU models. Android app ModelList now uses getChip() for dynamic model registration instead of hardcoded URLs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1f69cc4 commit 7509e61

15 files changed

Lines changed: 404 additions & 14 deletions

File tree

examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/data/ModelList.kt

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.runanywhere.sdk.public.extensions.LoraAdapterCatalogEntry
1111
import com.runanywhere.sdk.public.extensions.ModelCompanionFile
1212
import com.runanywhere.sdk.public.extensions.Models.ModelCategory
1313
import com.runanywhere.sdk.public.extensions.Models.ModelFileDescriptor
14+
import com.runanywhere.sdk.public.extensions.getChip
1415
import com.runanywhere.sdk.public.extensions.registerLoraAdapter
1516
import com.runanywhere.sdk.public.extensions.registerModel
1617
import com.runanywhere.sdk.public.extensions.registerMultiFileModel
@@ -126,19 +127,21 @@ object ModelList {
126127
),
127128
)
128129

129-
// Genie NPU Models (Qualcomm Snapdragon 8 Gen 2+)
130-
// Pre-compiled QNN context binaries for Qualcomm Genie SDK.
131-
// Compiled via: python -m qai_hub_models.models.<model>.export --chipset qualcomm-snapdragon-8-elite
132-
private val genieModels = listOf(
133-
AppModel(id = "qwen2_5-7b-instruct-genie", name = "Qwen 2.5 7B (NPU)",
134-
url = "https://huggingface.co/runanywhere/genie-npu-models/resolve/main/qwen2.5-7b-instruct-genie-w8a16.tar.gz",
135-
framework = InferenceFramework.GENIE, category = ModelCategory.LANGUAGE,
136-
memoryRequirement = 5_000_000_000),
137-
AppModel(id = "llama-3.2-1b-instruct-genie", name = "Llama 3.2 1B (NPU)",
138-
url = "https://huggingface.co/runanywhere/genie-npu-models/resolve/main/llama-3.2-1b-instruct-genie-w4.tar.gz",
139-
framework = InferenceFramework.GENIE, category = ModelCategory.LANGUAGE,
140-
memoryRequirement = 1_500_000_000),
141-
)
130+
// Genie NPU Models — URLs are built dynamically based on detected chipset.
131+
// getChip() returns the NPUChip for this device, or null if unsupported.
132+
private fun genieModels(): List<AppModel> {
133+
val chip = RunAnywhere.getChip() ?: return emptyList()
134+
return listOf(
135+
AppModel(
136+
id = "qwen-npu-${chip.identifier}",
137+
name = "Qwen3 4B (NPU - ${chip.displayName})",
138+
url = chip.downloadUrl("qwen"),
139+
framework = InferenceFramework.GENIE,
140+
category = ModelCategory.LANGUAGE,
141+
memoryRequirement = 2_800_000_000,
142+
),
143+
)
144+
}
142145

143146
// VLM
144147
private val vlmModels = listOf(
@@ -176,7 +179,7 @@ object ModelList {
176179

177180
val allModels = listOf(
178181
"LLM/STT/TTS" to (llmModels + sttModels + ttsModels),
179-
"Genie NPU" to genieModels,
182+
"Genie NPU" to genieModels(),
180183
"Embedding" to embeddingModels,
181184
"VLM" to vlmModels,
182185
)

sdk/runanywhere-flutter/packages/runanywhere/android/src/main/kotlin/ai/runanywhere/sdk/RunAnywherePlugin.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ class RunAnywherePlugin : FlutterPlugin, MethodCallHandler {
4848
"getCommonsVersion" -> {
4949
result.success(COMMONS_VERSION)
5050
}
51+
"getSocModel" -> {
52+
result.success(getSocModel())
53+
}
5154
else -> {
5255
result.notImplemented()
5356
}
@@ -57,4 +60,18 @@ class RunAnywherePlugin : FlutterPlugin, MethodCallHandler {
5760
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
5861
channel.setMethodCallHandler(null)
5962
}
63+
64+
/**
65+
* Get the SoC model string for NPU chip detection.
66+
* Uses Build.SOC_MODEL (API 31+) with Build.HARDWARE fallback.
67+
*/
68+
private fun getSocModel(): String {
69+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
70+
val socModel = Build.SOC_MODEL
71+
if (!socModel.isNullOrEmpty() && socModel != "unknown") {
72+
return socModel
73+
}
74+
}
75+
return Build.HARDWARE ?: ""
76+
}
6077
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/// Supported NPU chipsets for on-device Genie model inference.
2+
///
3+
/// Each chip has an [identifier] used to construct dynamic download URLs
4+
/// for chipset-specific NPU model binaries.
5+
///
6+
/// Example:
7+
/// ```dart
8+
/// final chip = RunAnywhere.getChip();
9+
/// if (chip != null) {
10+
/// final url = chip.downloadUrl('qwen');
11+
/// // → https://huggingface.co/Void2377/npu-models/resolve/main/qwen-gen1.zip?download=true
12+
/// }
13+
/// ```
14+
enum NPUChip {
15+
snapdragon8Elite('gen1', 'Snapdragon 8 Elite', 'SM8750'),
16+
snapdragon8EliteGen5('gen2', 'Snapdragon 8 Elite Gen 5', 'SM8850');
17+
18+
final String identifier;
19+
final String displayName;
20+
final String socModel;
21+
22+
const NPUChip(this.identifier, this.displayName, this.socModel);
23+
24+
/// Base URL for NPU model downloads on HuggingFace.
25+
static const baseUrl =
26+
'https://huggingface.co/Void2377/npu-models/resolve/main/';
27+
28+
/// Build a HuggingFace download URL for this chip.
29+
/// [modelName] is the model prefix (e.g. "qwen") → produces "qwen-gen1.zip"
30+
String downloadUrl(String modelName) =>
31+
'$baseUrl$modelName-$identifier.zip?download=true';
32+
33+
/// Match an NPU chip from a SoC model string (e.g. "SM8750").
34+
/// Returns null if the SoC is not a supported NPU chipset.
35+
static NPUChip? fromSocModel(String socModel) {
36+
final upper = socModel.toUpperCase();
37+
for (final chip in NPUChip.values) {
38+
if (upper.contains(chip.socModel)) return chip;
39+
}
40+
return null;
41+
}
42+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/// RunAnywhere + Device
2+
///
3+
/// Public API for NPU chip detection.
4+
/// Android only — returns null on iOS and other platforms.
5+
library runanywhere_device;
6+
7+
import 'dart:io' show Platform;
8+
9+
import 'package:flutter/services.dart';
10+
import 'package:runanywhere/core/types/npu_chip.dart';
11+
import 'package:runanywhere/public/runanywhere.dart';
12+
13+
// =============================================================================
14+
// NPU Chip Detection
15+
// =============================================================================
16+
17+
/// Extension methods for NPU chip detection
18+
extension RunAnywhereDevice on RunAnywhere {
19+
static final _channel = MethodChannel('runanywhere');
20+
21+
/// Detect the device's NPU chipset for Genie model compatibility.
22+
///
23+
/// Returns the [NPUChip] if the device has a supported Qualcomm SoC,
24+
/// or null if the device is not Android or does not support NPU inference.
25+
///
26+
/// Example:
27+
/// ```dart
28+
/// final chip = RunAnywhereDevice.getChip();
29+
/// if (chip != null) {
30+
/// final url = chip.downloadUrl('qwen');
31+
/// RunAnywhere.registerModel(id: 'qwen-npu', name: 'Qwen NPU', url: url, ...);
32+
/// }
33+
/// ```
34+
static Future<NPUChip?> getChip() async {
35+
if (!Platform.isAndroid) return null;
36+
37+
try {
38+
final socModel = await _channel.invokeMethod<String>('getSocModel');
39+
if (socModel == null || socModel.isEmpty) return null;
40+
return NPUChip.fromSocModel(socModel);
41+
} on PlatformException {
42+
return null;
43+
}
44+
}
45+
}

sdk/runanywhere-flutter/packages/runanywhere/lib/runanywhere.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export 'public/configuration/sdk_environment.dart';
2424
export 'public/errors/errors.dart';
2525
export 'public/events/event_bus.dart';
2626
export 'public/events/sdk_event.dart';
27+
export 'core/types/npu_chip.dart';
28+
export 'public/extensions/runanywhere_device.dart';
2729
export 'public/extensions/runanywhere_frameworks.dart';
2830
export 'public/extensions/runanywhere_logging.dart';
2931
export 'public/extensions/runanywhere_storage.dart';
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.runanywhere.sdk.public.extensions
2+
3+
import android.os.Build
4+
import com.runanywhere.sdk.core.types.NPUChip
5+
import com.runanywhere.sdk.foundation.SDKLogger
6+
import com.runanywhere.sdk.public.RunAnywhere
7+
8+
/**
9+
* Android implementation of NPU chip detection.
10+
*
11+
* Detection strategy (ordered):
12+
* 1. [Build.SOC_MODEL] (API 31+) — e.g. "SM8750"
13+
* 2. [Build.HARDWARE] — fallback codename
14+
* 3. /proc/cpuinfo Hardware line — last resort
15+
*/
16+
actual fun RunAnywhere.getChip(): NPUChip? {
17+
val logger = SDKLogger("NPUChip")
18+
19+
// 1. Try Build.SOC_MODEL (API 31+)
20+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
21+
val socModel = Build.SOC_MODEL
22+
if (!socModel.isNullOrEmpty() && socModel != "unknown") {
23+
val chip = NPUChip.fromSocModel(socModel)
24+
if (chip != null) {
25+
logger.info("Detected NPU chip: ${chip.displayName} (SOC_MODEL=$socModel)")
26+
return chip
27+
}
28+
}
29+
}
30+
31+
// 2. Try Build.HARDWARE
32+
val hardware = Build.HARDWARE
33+
if (!hardware.isNullOrEmpty() && hardware != "unknown") {
34+
val chip = NPUChip.fromSocModel(hardware)
35+
if (chip != null) {
36+
logger.info("Detected NPU chip: ${chip.displayName} (HARDWARE=$hardware)")
37+
return chip
38+
}
39+
}
40+
41+
// 3. Try /proc/cpuinfo
42+
try {
43+
val cpuInfo = java.io.File("/proc/cpuinfo").readText()
44+
val hardwareLine = cpuInfo.lines().find { it.startsWith("Hardware", ignoreCase = true) }
45+
if (hardwareLine != null) {
46+
val cpuHardware = hardwareLine.substringAfter(":").trim()
47+
val chip = NPUChip.fromSocModel(cpuHardware)
48+
if (chip != null) {
49+
logger.info("Detected NPU chip: ${chip.displayName} (cpuinfo=$cpuHardware)")
50+
return chip
51+
}
52+
}
53+
} catch (_: Exception) {
54+
// Fall through
55+
}
56+
57+
logger.debug("No supported NPU chip detected")
58+
return null
59+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.runanywhere.sdk.core.types
2+
3+
/**
4+
* Supported NPU chipsets for on-device Genie model inference.
5+
*
6+
* Each chip has an [identifier] used to construct dynamic download URLs
7+
* for chipset-specific NPU model binaries.
8+
*
9+
* Example URL construction:
10+
* ```
11+
* val chip = RunAnywhere.getChip()
12+
* val url = "${NPUChip.BASE_URL}qwen-${chip.identifier}.zip?download=true"
13+
* ```
14+
*/
15+
enum class NPUChip(
16+
val identifier: String,
17+
val displayName: String,
18+
val socModel: String,
19+
) {
20+
SNAPDRAGON_8_ELITE("gen1", "Snapdragon 8 Elite", "SM8750"),
21+
SNAPDRAGON_8_ELITE_GEN5("gen2", "Snapdragon 8 Elite Gen 5", "SM8850"),
22+
;
23+
24+
/**
25+
* Build a HuggingFace download URL for this chip.
26+
* @param modelName Model prefix (e.g. "qwen") → produces "qwen-gen1.zip"
27+
*/
28+
fun downloadUrl(modelName: String): String =
29+
"${BASE_URL}${modelName}-${identifier}.zip?download=true"
30+
31+
companion object {
32+
/** Base URL for NPU model downloads on HuggingFace. */
33+
const val BASE_URL = "https://huggingface.co/Void2377/npu-models/resolve/main/"
34+
35+
/**
36+
* Match an NPU chip from a SoC model string (e.g. "SM8750").
37+
* Returns null if the SoC is not a supported NPU chipset.
38+
*/
39+
fun fromSocModel(socModel: String): NPUChip? {
40+
val upper = socModel.uppercase()
41+
return entries.firstOrNull { upper.contains(it.socModel) }
42+
}
43+
}
44+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.runanywhere.sdk.public.extensions
2+
3+
import com.runanywhere.sdk.core.types.NPUChip
4+
import com.runanywhere.sdk.public.RunAnywhere
5+
6+
/**
7+
* Detect the device's NPU chipset for Genie model compatibility.
8+
*
9+
* Returns the [NPUChip] if the device has a supported Qualcomm SoC,
10+
* or null if the device does not support NPU inference.
11+
*
12+
* Use [NPUChip.identifier] to construct chipset-specific download URLs:
13+
* ```kotlin
14+
* val chip = RunAnywhere.getChip()
15+
* if (chip != null) {
16+
* val url = "https://example.com/models/qwen-${chip.identifier}.zip"
17+
* RunAnywhere.registerModel(id = "qwen-npu", name = "Qwen NPU", url = url, ...)
18+
* }
19+
* ```
20+
*/
21+
expect fun RunAnywhere.getChip(): NPUChip?
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.runanywhere.sdk.public.extensions
2+
3+
import com.runanywhere.sdk.core.types.NPUChip
4+
import com.runanywhere.sdk.public.RunAnywhere
5+
6+
/**
7+
* JVM stub — NPU chip detection is not applicable on desktop.
8+
*/
9+
actual fun RunAnywhere.getChip(): NPUChip? = null
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* RunAnywhere+Device.ts
3+
*
4+
* NPU chip detection extension. Android only.
5+
* Returns null on iOS and other platforms.
6+
*/
7+
8+
import { Platform } from 'react-native';
9+
import { requireDeviceInfoModule } from '../../native/NativeRunAnywhereCore';
10+
import { npuChipFromSocModel } from '../../types/NPUChip';
11+
import { SDKLogger } from '../../Foundation/Logging/Logger/SDKLogger';
12+
import type { NPUChip } from '../../types/NPUChip';
13+
14+
const logger = new SDKLogger('RunAnywhere.Device');
15+
16+
/**
17+
* Detect the device's NPU chipset for Genie model compatibility.
18+
*
19+
* Returns the NPUChip if the device has a supported Qualcomm SoC,
20+
* or null if the device is not Android or does not support NPU inference.
21+
*
22+
* @example
23+
* ```typescript
24+
* const chip = await getChip();
25+
* if (chip) {
26+
* const url = getNPUDownloadUrl(chip, 'qwen');
27+
* await RunAnywhere.registerModel({ id: 'qwen-npu', name: 'Qwen NPU', url, ... });
28+
* }
29+
* ```
30+
*/
31+
export async function getChip(): Promise<NPUChip | null> {
32+
if (Platform.OS !== 'android') {
33+
return null;
34+
}
35+
36+
try {
37+
const deviceInfo = requireDeviceInfoModule();
38+
const chipName = await deviceInfo.getChipName();
39+
40+
if (!chipName || chipName === 'Unknown') {
41+
logger.debug('No chip name available from device info');
42+
return null;
43+
}
44+
45+
const chip = npuChipFromSocModel(chipName);
46+
if (chip) {
47+
logger.info(
48+
`Detected NPU chip: ${chip.displayName} (chipName=${chipName})`
49+
);
50+
} else {
51+
logger.debug(`No supported NPU chip for: ${chipName}`);
52+
}
53+
54+
return chip ?? null;
55+
} catch (error) {
56+
logger.debug('Failed to detect NPU chip');
57+
return null;
58+
}
59+
}

0 commit comments

Comments
 (0)