fix(ml): 优化PyTorch模型推理流程与资源管理
- 修改API接口路径从serve切换到common,统一资源调用 - 增加独立推理线程,隔离ArcSoft线程污染,保障FPU状态稳定 - 推理模块预热,降低PyTorch线程初启功率峰值,避免电流保护触发 - 推理前后记录系统内存信息,辅助OOM重启排查 - 主屏相机与副屏人脸识别推理前暂停,规避硬件并发带宽冲突重启 - 控制相机HAL清理时长,确保DMC带宽资源释放后再启动推理 - 异步推理调用增加超时机制,避免长时间卡死 - 推理结束后恢复相机,确保正常工作流程
This commit is contained in:
@@ -1297,7 +1297,24 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
startTime = System.currentTimeMillis()
|
startTime = System.currentTimeMillis()
|
||||||
log("registerDataChange photoUri bitmap保存文件路径:${file?.absolutePath}")
|
log("registerDataChange photoUri bitmap保存文件路径:${file?.absolutePath}")
|
||||||
//val nameList = FoodModule.queryFood(bitmap)
|
//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 foodName = if (scoreList.isNotEmpty()) {
|
||||||
val recDataJson = GsonUtils.toJson(scoreList)
|
val recDataJson = GsonUtils.toJson(scoreList)
|
||||||
log("registerDataChange main,getFoodScoreList耗时:${System.currentTimeMillis() - startTime}")
|
log("registerDataChange main,getFoodScoreList耗时:${System.currentTimeMillis() - startTime}")
|
||||||
|
|||||||
@@ -36,13 +36,13 @@ interface ApiServiceV2 {
|
|||||||
|
|
||||||
@POST
|
@POST
|
||||||
suspend fun getFacePage(
|
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>
|
@Body request: Map<String, Long>
|
||||||
): ApiResponse<List<FaceVO>?>
|
): ApiResponse<List<FaceVO>?>
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
suspend fun getFaceIncrement(
|
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>
|
@Body request: Map<String, Long>
|
||||||
): ApiResponse<List<FaceVO>?>
|
): ApiResponse<List<FaceVO>?>
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.pytorch.IValue
|
import org.pytorch.IValue
|
||||||
import org.pytorch.Module
|
import org.pytorch.Module
|
||||||
|
import org.pytorch.Tensor
|
||||||
import org.pytorch.torchvision.TensorImageUtils
|
import org.pytorch.torchvision.TensorImageUtils
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -29,6 +30,9 @@ object FoodModule {
|
|||||||
// private const val THRESHOLD = 0.0
|
// private const val THRESHOLD = 0.0
|
||||||
private var module: Module? = null
|
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 embeddingsList: List<List<Float>>
|
||||||
// private lateinit var labelsList: IntArray
|
// private lateinit var labelsList: IntArray
|
||||||
// private lateinit var classInfo: FoodClassInfo
|
// private lateinit var classInfo: FoodClassInfo
|
||||||
@@ -46,6 +50,23 @@ object FoodModule {
|
|||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val modelPath = copyAssetToCache(context, "best_embedding_model_mobile.pt")
|
val modelPath = copyAssetToCache(context, "best_embedding_model_mobile.pt")
|
||||||
module = Module.load(modelPath)
|
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()
|
val list = ObjectBox.getAll()
|
||||||
if (list.isNotEmpty()) {
|
if (list.isNotEmpty()) {
|
||||||
@@ -86,8 +107,28 @@ object FoodModule {
|
|||||||
if (module == null) {
|
if (module == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
val outputTensor = module?.forward(IValue.from(inputTensor))?.toTensor()
|
Timber.tag(TAG).d("module:${module}")
|
||||||
return outputTensor?.dataAsFloatArray
|
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) {
|
} catch (e: OutOfMemoryError) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user