Phase 3: device profiler and tier matrix
Browse files
app/src/main/java/io/github/abrarul/modularai/model/DeviceProfiler.kt
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package io.github.abrarul.modularai.model
|
| 2 |
+
|
| 3 |
+
import android.app.ActivityManager
|
| 4 |
+
import android.content.Context
|
| 5 |
+
import android.content.pm.PackageManager
|
| 6 |
+
import android.os.Build
|
| 7 |
+
import android.os.Environment
|
| 8 |
+
import android.os.StatFs
|
| 9 |
+
import java.io.File
|
| 10 |
+
import kotlin.system.measureTimeMillis
|
| 11 |
+
|
| 12 |
+
enum class Tier(val label: String, val sizes: String) {
|
| 13 |
+
ULTRA_LIGHT("Ultra-Light", "1B-3B"),
|
| 14 |
+
BALANCED("Balanced", "7B-8B"),
|
| 15 |
+
POWER("Power", "14B+")
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
data class Profile(
|
| 19 |
+
val soc: String,
|
| 20 |
+
val abi: String,
|
| 21 |
+
val cores: Int,
|
| 22 |
+
val totalRamMb: Long,
|
| 23 |
+
val availRamMb: Long,
|
| 24 |
+
val freeStorageMb: Long,
|
| 25 |
+
val vulkan: Boolean,
|
| 26 |
+
val storageMBps: Double,
|
| 27 |
+
val tier: Tier
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
data class Recommendation(
|
| 31 |
+
val tier: Tier,
|
| 32 |
+
val maxParamsB: Double,
|
| 33 |
+
val quant: String,
|
| 34 |
+
val contextTokens: Int,
|
| 35 |
+
val estTokensPerSec: String
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
object DeviceProfiler {
|
| 39 |
+
|
| 40 |
+
fun scan(ctx: Context): Profile {
|
| 41 |
+
val am = ctx.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
| 42 |
+
val mi = ActivityManager.MemoryInfo().also { am.getMemoryInfo(it) }
|
| 43 |
+
val total = mi.totalMem / 1_048_576
|
| 44 |
+
val stat = StatFs(Environment.getDataDirectory().path)
|
| 45 |
+
val soc = if (Build.VERSION.SDK_INT >= 31) "${Build.SOC_MANUFACTURER} ${Build.SOC_MODEL}" else Build.HARDWARE
|
| 46 |
+
return Profile(
|
| 47 |
+
soc = soc,
|
| 48 |
+
abi = Build.SUPPORTED_ABIS.firstOrNull() ?: "unknown",
|
| 49 |
+
cores = Runtime.getRuntime().availableProcessors(),
|
| 50 |
+
totalRamMb = total,
|
| 51 |
+
availRamMb = mi.availMem / 1_048_576,
|
| 52 |
+
freeStorageMb = stat.availableBytes / 1_048_576,
|
| 53 |
+
vulkan = ctx.packageManager.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL),
|
| 54 |
+
storageMBps = benchStorage(ctx.cacheDir),
|
| 55 |
+
tier = when {
|
| 56 |
+
total < 5_000 -> Tier.ULTRA_LIGHT
|
| 57 |
+
total < 10_000 -> Tier.BALANCED
|
| 58 |
+
else -> Tier.POWER
|
| 59 |
+
}
|
| 60 |
+
)
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
/** 32 MB sequential write benchmark. */
|
| 64 |
+
private fun benchStorage(dir: File): Double {
|
| 65 |
+
val f = File(dir, "bench.tmp")
|
| 66 |
+
val buf = ByteArray(1 shl 20)
|
| 67 |
+
val ms = measureTimeMillis {
|
| 68 |
+
f.outputStream().use { o -> repeat(32) { o.write(buf) }; o.fd.sync() }
|
| 69 |
+
}
|
| 70 |
+
f.delete()
|
| 71 |
+
return if (ms == 0L) 0.0 else 32_000.0 / ms
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
/**
|
| 75 |
+
* Heuristic: a Q4_K_M GGUF needs roughly 0.6 GB per billion params plus KV cache.
|
| 76 |
+
* Uses ~60% of currently available RAM as the budget. Tokens/s is a rough estimate only;
|
| 77 |
+
* real numbers come from the in-app benchmark.
|
| 78 |
+
*/
|
| 79 |
+
fun recommend(p: Profile): Recommendation {
|
| 80 |
+
val budgetGb = p.availRamMb * 0.6 / 1024.0
|
| 81 |
+
val maxB = ((budgetGb - 0.5) / 0.6).coerceAtLeast(0.5)
|
| 82 |
+
val quant = when {
|
| 83 |
+
maxB >= 8 -> "Q5_K_M"
|
| 84 |
+
maxB >= 3 -> "Q4_K_M"
|
| 85 |
+
maxB >= 1.5 -> "Q4_K_S"
|
| 86 |
+
else -> "IQ2_XS"
|
| 87 |
+
}
|
| 88 |
+
val ctxTokens = when {
|
| 89 |
+
budgetGb >= 8 -> 8192
|
| 90 |
+
budgetGb >= 4 -> 4096
|
| 91 |
+
else -> 2048
|
| 92 |
+
}
|
| 93 |
+
val tps = when (p.tier) {
|
| 94 |
+
Tier.ULTRA_LIGHT -> "3-10"
|
| 95 |
+
Tier.BALANCED -> "2-8"
|
| 96 |
+
Tier.POWER -> "3-12"
|
| 97 |
+
}
|
| 98 |
+
return Recommendation(p.tier, maxB, quant, ctxTokens, tps)
|
| 99 |
+
}
|
| 100 |
+
}
|