fix(local-asr): 切走/删模型/手动释放时真正归还 RAM
三个问题合修: 1. 用户点"释放"按钮后活动监视器看 RSS 不下降。原因:encoder f32 weights 是 malloc 出来的几百 MB,free 后 macOS libmalloc 不会立即把页归还内核。 修:drop QwenAsrEngine 后调 malloc_zone_pressure_relief(NULL, 0),主动 归还。decoder bf16 走 mmap 不依赖这个。 2. 切回云端 ASR 后本地引擎仍驻留。set_active_asr_provider 切到非 local 时未释放,1.2GB+ RAM 一直留到进程退出。 3. 删除已下载的模型时未释放内存里加载的同 id 引擎,留下 mmap 指向已 unlink 文件 + RAM 不归还的双重问题。 Windows 端本期不上线(cache.rs macOS cfg 块包住改动)。
This commit is contained in:
1 parent
c85b1bd4c6
commit
463a6d9716
2 files changed
+52
-14
No files matched your search
@@ -94,29 +94,39 @@ impl LocalAsrCache {
|
||||
pub fn release_if_idle(&self, idle_threshold: Duration) -> bool {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let mut slot = self.inner.lock();
|
||||
if let Some(cached) = slot.as_ref() {
|
||||
if cached.last_used.elapsed() >= idle_threshold {
|
||||
log::info!(
|
||||
"[local-asr cache] release engine {} after idle {:?}",
|
||||
cached.model_id,
|
||||
cached.last_used.elapsed()
|
||||
);
|
||||
slot.take();
|
||||
return true;
|
||||
let taken = {
|
||||
let mut slot = self.inner.lock();
|
||||
match slot.as_ref() {
|
||||
Some(c) if c.last_used.elapsed() >= idle_threshold => {
|
||||
log::info!(
|
||||
"[local-asr cache] release engine {} after idle {:?}",
|
||||
c.model_id,
|
||||
c.last_used.elapsed()
|
||||
);
|
||||
slot.take()
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
if let Some(cached) = taken {
|
||||
drop(cached);
|
||||
pressure_relief_macos();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
let _ = idle_threshold;
|
||||
false
|
||||
}
|
||||
|
||||
/// 立刻释放(用户点"立即释放"或删模型时调)。
|
||||
/// 立刻释放(用户点"立即释放"、切走 provider、删模型时调)。
|
||||
pub fn release_now(&self) {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if let Some(cached) = self.inner.lock().take() {
|
||||
let taken = self.inner.lock().take();
|
||||
if let Some(cached) = taken {
|
||||
log::info!("[local-asr cache] release engine {} on demand", cached.model_id);
|
||||
drop(cached);
|
||||
pressure_relief_macos();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,3 +140,18 @@ impl LocalAsrCache {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// drop QwenAsrEngine 后调一次:让 macOS libmalloc 把 freelist 上的物理页归还内核。
|
||||
/// 不调的话,encoder f32 weights 那 ~几百 MB 的 free 不会立刻反映到 RSS,活动监视器
|
||||
/// 看起来"释放按钮没生效"。decoder bf16 走 mmap,munmap 时已立即生效,不依赖这个调用。
|
||||
#[cfg(target_os = "macos")]
|
||||
fn pressure_relief_macos() {
|
||||
// SAFETY: 系统 API;NULL zone + goal=0 = 对所有 zone 尽量多地归还,无内存安全风险。
|
||||
let freed = unsafe { malloc_zone_pressure_relief(std::ptr::null_mut(), 0) };
|
||||
log::info!("[local-asr cache] malloc_zone_pressure_relief freed ~{} bytes", freed);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
extern "C" {
|
||||
fn malloc_zone_pressure_relief(zone: *mut libc::c_void, goal: libc::size_t) -> libc::size_t;
|
||||
}
|
||||
@@ -123,9 +123,14 @@ pub fn set_credential(account: String, value: String) -> Result<(), String> {
|
||||
#[tauri::command]
|
||||
pub fn set_active_asr_provider(coord: CoordinatorState<'_>, provider: String) -> Result<(), String> {
|
||||
CredentialsVault::set_active_asr_provider(&provider).map_err(|e| e.to_string())?;
|
||||
// 切到本地 ASR → 后台预加载模型,下次按 hotkey 时不必等数秒。
|
||||
if provider == crate::asr::local::PROVIDER_ID {
|
||||
// 切到本地 ASR → 后台预加载模型,下次按 hotkey 时不必等数秒。
|
||||
coord.preload_local_asr_in_background();
|
||||
} else {
|
||||
// 切回云端 → 用户已不需要本地引擎,立刻释放 1.2GB+ RAM;不释放的话只会等到
|
||||
// schedule_local_asr_release 的下一次 dictation 才触发,而切回云端后根本不会
|
||||
// 再走 local 路径,引擎会驻留到进程退出。
|
||||
coord.release_local_asr_engine();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -780,8 +785,16 @@ pub fn local_asr_cancel_download(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn local_asr_delete_model(model_id: String) -> Result<(), String> {
|
||||
pub fn local_asr_delete_model(
|
||||
coord: CoordinatorState<'_>,
|
||||
model_id: String,
|
||||
) -> Result<(), String> {
|
||||
let id = ModelId::from_str(&model_id).ok_or_else(|| format!("unknown model id: {model_id}"))?;
|
||||
// 如果内存里加载的就是要删的这个模型,先释放:否则 mmap 残留指向已 unlink 的文件,
|
||||
// 且 RAM 直到下次切模型 / 用户手动按"释放"才回收。
|
||||
if coord.local_asr_loaded_model().as_deref() == Some(id.as_str()) {
|
||||
coord.release_local_asr_engine();
|
||||
}
|
||||
crate::asr::local::models::delete_model(id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user