Revert MemoryOptimizer — reclaim wasn't worth the footprint

The kill-background approach worked at startup (−1.6 GB) but respawns
pulled most of that back within 1–3 min, and the periodic sweep only
kept the first sweep's reclaim stable rather than going lower. Net
practical benefit over "just let Android manage it" is small, not
worth a custom optimizer + normal-perm + file maintenance.

Removes:
- MemoryOptimizer.kt
- KazeiaService.onCreate calls to freeRamForModels / startPeriodicOptimizer
- KILL_BACKGROUND_PROCESSES permission from the manifest

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kazeia Team 2026-04-14 22:46:58 +02:00
parent 751e3e0868
commit 6a958c1a10
3 changed files with 0 additions and 182 deletions

View File

@ -9,13 +9,6 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Freeing RAM for the LLM/TTS/STT models at startup. Kazeia uses
~5 GB across Qwen3-4B PTE + Qwen3-TTS + Whisper, and the OnePlus
Pad's ColorOS base load alone keeps ~7 GB resident, pushing most
of Kazeia's weights into ZRAM swap. KILL_BACKGROUND_PROCESSES is
a normal permission (no user prompt) that lets us reclaim that
headroom by nudging non-essential apps out of RAM on launch. -->
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
<application
android:name=".KazeiaApplication"

View File

@ -442,15 +442,6 @@ class KazeiaService : Service() {
} else {
startForeground(NOTIFICATION_ID, createNotification())
}
// Free RAM for the ML models BEFORE they start loading. The tablet
// is 16 GB but ColorOS base + Google services routinely hog ~7 GB
// on their own, pushing Qwen3-4B's 3.2 GB of weights into ZRAM
// swap and causing page faults on every inference. Nudging the
// background apps out (see MemoryOptimizer.KILL_TARGETS) typically
// reclaims 1.5 GB on first run, then re-killed processes respawn
// over 13 minutes so a periodic sweep keeps them contained.
MemoryOptimizer.freeRamForModels(this) { msg -> log(msg) }
MemoryOptimizer.startPeriodicOptimizer(this, serviceScope) { msg -> log(msg) }
initializeComponents()
}

View File

@ -1,166 +0,0 @@
package com.kazeia.service
import android.app.ActivityManager
import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
/**
* Frees RAM for Kazeia's ML models by nudging non-essential background
* processes out of memory. Uses `ActivityManager.killBackgroundProcesses`
* which:
* - only kills processes that are NOT in foreground or bound to something
* foreground (so no user-visible disruption),
* - leaves the package installed/launchable the OS will respawn it on
* demand if the user opens the app or a push arrives,
* - requires only `KILL_BACKGROUND_PROCESSES`, a normal permission.
*
* Call [freeRamForModels] once, early in KazeiaService.onCreate, BEFORE
* loading Qwen3-4B (which needs ~3.2 GB resident to avoid swap thrashing).
*
* The list below was picked by reading `dumpsys meminfo` on a fresh boot
* of a OnePlus Pad 3 (ColorOS 16): every package here is either a Google
* background feature Kazeia doesn't use (Wallet, Chromecast, YouTube
* prefetch, ) or an OPLUS feature that re-spawns harmlessly when needed.
* We deliberately avoid Play Services core, input method, launcher,
* system_server, systemui, surfaceflinger, audioserver, HAL services
* killing any of those would break the UI or audio routing.
*/
object MemoryOptimizer {
private const val TAG = "MemoryOptimizer"
/** Packages safe to evict. Order matches approximate RAM savings. */
private val KILL_TARGETS = listOf(
// Google optional features
"com.google.android.googlequicksearchbox",
"com.google.android.youtube",
"com.google.android.apps.walletnfcrel",
"com.google.android.apps.chromecast.app",
"com.google.android.aicore",
"com.google.android.apps.messaging",
"com.google.android.apps.tachyon", // Google Meet/Duo (resp. 96 MB)
"com.google.android.gm",
"com.google.android.as",
"com.google.android.as.oss",
// Additional targets discovered as respawning heavy after the
// first kill sweep on OnePlus Pad 3 — all user-facing apps that
// we don't need while Kazeia is the active task.
"com.google.android.apps.photos",
"com.google.android.calendar",
"com.android.providers.calendar",
"com.google.android.contacts",
"com.android.vending", // Play Store (resp. 150 MB)
"com.google.android.rkpdapp",
"android.process.acore",
// OPLUS / ColorOS optional features
"com.oneplus.deskclock",
"com.coloros.smartsidebar",
"com.coloros.assistantscreen",
"com.coloros.weather.service",
"com.heytap.mcs",
"com.heytap.accessory",
"com.oplus.cosa",
"com.oplus.pantanal.ums",
"com.oplus.nhs",
"com.oplus.midas",
"com.oplus.olc",
"com.oplus.deepthinker",
"com.oplus.blur",
"com.oplus.statistics.rom",
"com.oplus.powermonitor",
"com.oplus.romupdate",
"com.oplus.location",
"com.oplus.gesture",
"com.oplus.appplatform",
"com.oplus.persist.multimedia",
"com.oplus.nas",
"com.oplus.notificationmanager",
"com.oplus.safecenter",
"com.oplus.securitypermission",
"com.oplus.sau",
// Qualcomm workload profiler — perf hints only, respawns
"com.qualcomm.qti.workloadclassifier"
)
/**
* Ask ActivityManager to drop background processes for every package
* in [KILL_TARGETS] that is installed. Returns a (attempted, skipped)
* pair for logging. Safe to call from any thread.
*/
fun freeRamForModels(context: Context, log: (String) -> Unit = {}): Pair<Int, Int> {
val am = context.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
?: run { log("$TAG: ActivityManager unavailable"); return 0 to 0 }
val pm = context.packageManager
val memBefore = readAvailMb(am)
var killed = 0
var skipped = 0
for (pkg in KILL_TARGETS) {
try {
// Only attempt for installed packages — unknown packages
// would silently no-op but still spam the audit log.
pm.getPackageInfo(pkg, 0)
am.killBackgroundProcesses(pkg)
killed++
} catch (_: android.content.pm.PackageManager.NameNotFoundException) {
skipped++
} catch (e: SecurityException) {
log("$TAG: killBackgroundProcesses($pkg) denied: ${e.message}")
skipped++
} catch (e: Exception) {
log("$TAG: killBackgroundProcesses($pkg) failed: ${e.message}")
skipped++
}
}
// Give the kernel a moment to reclaim pages before we report.
Thread.sleep(250)
val memAfter = readAvailMb(am)
log("$TAG: killed=$killed skipped=$skipped; avail RAM ${memBefore} MB → ${memAfter} MB (+${memAfter - memBefore} MB)")
return killed to skipped
}
private fun readAvailMb(am: ActivityManager): Long {
val info = ActivityManager.MemoryInfo()
am.getMemoryInfo(info)
return info.availMem / (1024 * 1024)
}
/**
* Kick off a background coroutine that re-runs [freeRamForModels]
* every [periodMs]. ColorOS's ActivityManager auto-respawns killed
* apps within 13 minutes (observed: quicksearchbox coming back at
* 210 MB, photos/calendar/vending spawning fresh), so a single
* startup sweep isn't enough periodic pruning is needed while
* Kazeia holds its models in memory.
*
* Cheap (~100 ms total per sweep, mostly the intentional 250 ms
* settle sleep in freeRamForModels), so 60 s cadence is safe. The
* returned [kotlinx.coroutines.Job] should be cancelled by the
* caller when the service stops, to avoid waking the CPU after
* shutdown.
*/
fun startPeriodicOptimizer(
context: Context,
scope: CoroutineScope,
periodMs: Long = 60_000L,
log: (String) -> Unit = {}
): Job = scope.launch(Dispatchers.IO) {
// First run already happened at service onCreate — skip the
// initial interval so we don't double-sweep on startup.
delay(periodMs)
while (currentCoroutineContext()[Job]?.isActive != false) {
try {
freeRamForModels(context, log)
} catch (e: Exception) {
log("$TAG: periodic sweep failed: ${e.message}")
}
delay(periodMs)
}
}
}