fix(ml): 优化PyTorch模型推理流程与资源管理

- 修改API接口路径从serve切换到common,统一资源调用
- 增加独立推理线程,隔离ArcSoft线程污染,保障FPU状态稳定
- 推理模块预热,降低PyTorch线程初启功率峰值,避免电流保护触发
- 推理前后记录系统内存信息,辅助OOM重启排查
- 主屏相机与副屏人脸识别推理前暂停,规避硬件并发带宽冲突重启
- 控制相机HAL清理时长,确保DMC带宽资源释放后再启动推理
- 异步推理调用增加超时机制,避免长时间卡死
- 推理结束后恢复相机,确保正常工作流程
This commit is contained in:
mazengfei
2026-07-08 16:23:37 +08:00
parent b6ed47ff87
commit 5a7b090c02
3 changed files with 63 additions and 5 deletions
@@ -1297,7 +1297,24 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
startTime = System.currentTimeMillis()
log("registerDataChange photoUri bitmap保存文件路径:${file?.absolutePath}")
//val nameList = FoodModule.queryFood(bitmap)
val scoreList = FoodModule.getFoodScoreList(bitmap)
// 推理前同时暂停主屏相机和副屏人脸识别,让 ArcSoft 线程进入空闲后再启动 PyTorch
withContext(Dispatchers.Main) {
shutdownCamera()
presentation?.pauseCamera()
}
// 等待相机 HAL 在内核层(uvcvideo URB / DMC system_status)完成异步清理
// 实测:unbindAll() 之后 HAL 仍会在 ~200ms 内写 sysfs_dmc/system_status
// 若此时 PyTorch forward() 同时占用大量 DRAM 带宽,RK3588 DMC 频率切换
// 与高带宽访问并发会触发硬件级故障导致设备重启。
// 1500ms 给 HAL 足够时间完成 DMC 带宽 hold 释放后再启动推理。
Timber.tag(TAG).d("queryFoodData: 等待相机HAL完成DMC清理 (1500ms)...")
kotlinx.coroutines.delay(1500)
Timber.tag(TAG).d("queryFoodData: 延迟结束,开始 forward()")
val scoreList = try {
FoodModule.getFoodScoreList(bitmap)
} finally {
withContext(Dispatchers.Main) { setupCamera() }
}
val foodName = if (scoreList.isNotEmpty()) {
val recDataJson = GsonUtils.toJson(scoreList)
log("registerDataChange main,getFoodScoreList耗时:${System.currentTimeMillis() - startTime}")
@@ -36,13 +36,13 @@ interface ApiServiceV2 {
@POST
suspend fun getFacePage(
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/serve/face/page",
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/common/face/page",
@Body request: Map<String, Long>
): ApiResponse<List<FaceVO>?>
@POST
suspend fun getFaceIncrement(
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/serve/face/increment",
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/common/face/increment",
@Body request: Map<String, Long>
): ApiResponse<List<FaceVO>?>
@@ -15,6 +15,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.pytorch.IValue
import org.pytorch.Module
import org.pytorch.Tensor
import org.pytorch.torchvision.TensorImageUtils
import timber.log.Timber
import java.io.File
@@ -29,6 +30,9 @@ object FoodModule {
// private const val THRESHOLD = 0.0
private var module: Module? = null
// 专用推理线程:与 Kotlin IO 调度器完全隔离,避免 ArcSoft 污染该线程的 FPU 状态(FPSCR)
private val inferenceExecutor = java.util.concurrent.Executors.newSingleThreadExecutor()
// private lateinit var embeddingsList: List<List<Float>>
// private lateinit var labelsList: IntArray
// private lateinit var classInfo: FoodClassInfo
@@ -46,6 +50,23 @@ object FoodModule {
withContext(Dispatchers.IO) {
val modelPath = copyAssetToCache(context, "best_embedding_model_mobile.pt")
module = Module.load(modelPath)
// 预热:在相机启动前先跑一次 forward,触发 PyTorch 线程池创建
// 在预热前将当前线程优先级调低,使 PyTorch 内部新建的工作线程也继承低优先级
// 从而降低后续推理时的瞬间功率,避免触发 PMIC 过流保护
Timber.tag(TAG).d("预热 forward() 开始")
val warmupTensor = Tensor.fromBlob(
FloatArray(1 * 3 * 224 * 224),
longArrayOf(1, 3, 224, 224)
)
inferenceExecutor.submit { module?.forward(IValue.from(warmupTensor)) }.get()
context.let { ctx ->
val am = ctx.getSystemService(android.content.Context.ACTIVITY_SERVICE)
as android.app.ActivityManager
val mi = android.app.ActivityManager.MemoryInfo()
am.getMemoryInfo(mi)
Timber.tag(TAG).d("预热完成后系统可用内存: ${mi.availMem / 1024 / 1024} MB / 总: ${mi.totalMem / 1024 / 1024} MB")
}
Timber.tag(TAG).d("预热 forward() 完成")
//初始化默认重新拉取数据,先清空本地数据
val list = ObjectBox.getAll()
if (list.isNotEmpty()) {
@@ -86,8 +107,28 @@ object FoodModule {
if (module == null) {
return null
}
val outputTensor = module?.forward(IValue.from(inputTensor))?.toTensor()
return outputTensor?.dataAsFloatArray
Timber.tag(TAG).d("module:${module}")
val iValue = IValue.from(inputTensor)
Timber.tag(TAG).d("inputTensor:${inputTensor}")
// 推理前记录系统可用内存,排查是否因内存不足触发 OOM 重启
MyApp.instance?.let { ctx ->
val am = ctx.getSystemService(android.content.Context.ACTIVITY_SERVICE)
as android.app.ActivityManager
val mi = android.app.ActivityManager.MemoryInfo()
am.getMemoryInfo(mi)
Timber.tag(TAG).d("forward() 前系统可用内存: ${mi.availMem / 1024 / 1024} MB / 总: ${mi.totalMem / 1024 / 1024} MB, lowMemory=${mi.lowMemory}")
}
Timber.tag(TAG).d("forward() 开始: ${System.currentTimeMillis()}")
val outputIValue = inferenceExecutor.submit<IValue?> { module?.forward(iValue) }
.get(10, java.util.concurrent.TimeUnit.SECONDS) ?: return null
Timber.tag(TAG).d("forward() 结束: ${System.currentTimeMillis()}")
Timber.tag(TAG).d("outputIValue:${outputIValue}")
val outputTensor = outputIValue.toTensor()
Timber.tag(TAG).d("outputTensor:${outputTensor}")
return outputTensor.dataAsFloatArray
} catch (e: OutOfMemoryError) {
e.printStackTrace()
} finally {