feat(commands): route AMD/Intel to DirectML ONNX engine + download_directml_package (P2)
load_transcriber dispatches OnnxOpenVino/OnnxDirectML through one OnnxTranscriber block. download_and_extract_runtime is parameterized (sha + readiness) so the new stage_directml_runtime reuses it; hardware_status reports a directml package field.
This commit is contained in:
+86
-12
@@ -126,18 +126,22 @@ fn load_transcriber(
|
||||
// backend has a working accel path just because the hardware exists.
|
||||
let path = resolve_accel(backend);
|
||||
|
||||
// ONNX engine — NPU (OpenVINO EP) or AMD/Intel GPU (DirectML EP). Same model
|
||||
// artifacts either way; the EP is chosen inside OnnxTranscriber::load from
|
||||
// `backend`, and the resolved `backend` is reported so the UI shows the real
|
||||
// engine.
|
||||
#[cfg(feature = "npu")]
|
||||
if path == AccelPath::OnnxOpenVino {
|
||||
use crate::transcription::{onnx_models, OnnxNpuTranscriber};
|
||||
if matches!(path, AccelPath::OnnxOpenVino | AccelPath::OnnxDirectML) {
|
||||
use crate::transcription::{onnx_models, OnnxTranscriber};
|
||||
if onnx_models::is_installed(onnx_models::DEFAULT_ONNX_MODEL) {
|
||||
let dir = onnx_models::model_dir(onnx_models::DEFAULT_ONNX_MODEL);
|
||||
match OnnxNpuTranscriber::load(&dir, BackendId::Npu) {
|
||||
Ok(t) => return Ok((Box::new(t), BackendId::Npu)),
|
||||
Err(e) => tracing::warn!("NPU engine load failed ({e}); falling back to CPU"),
|
||||
match OnnxTranscriber::load(&dir, backend) {
|
||||
Ok(t) => return Ok((Box::new(t), backend)),
|
||||
Err(e) => tracing::warn!("ONNX engine load failed ({e}); falling back to CPU"),
|
||||
}
|
||||
} else {
|
||||
tracing::warn!(
|
||||
"NPU selected but ONNX model not installed; falling back to whisper.cpp"
|
||||
"ONNX backend selected but model not installed; falling back to whisper.cpp"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -852,6 +856,12 @@ pub async fn hardware_status() -> WaResult<serde_json::Value> {
|
||||
"runtimeReady": crate::paths::npu_runtime_ready(),
|
||||
"modelInstalled": npu_model_installed(),
|
||||
},
|
||||
// DirectML GPU package (P2): shares the ONNX model with the NPU path;
|
||||
// only the runtime differs. Drives a future Settings ▸ Hardware indicator.
|
||||
"directml": {
|
||||
"runtimeReady": crate::paths::directml_runtime_ready(),
|
||||
"modelInstalled": npu_model_installed(),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -928,7 +938,35 @@ async fn stage_npu_runtime(app: &AppHandle) -> WaResult<()> {
|
||||
}
|
||||
|
||||
let url = std::env::var("WA_NPU_RUNTIME_URL").unwrap_or_else(|_| NPU_RUNTIME_URL.to_string());
|
||||
download_and_extract_runtime(app, &dir, &url).await
|
||||
download_and_extract_runtime(
|
||||
app,
|
||||
&dir,
|
||||
&url,
|
||||
NPU_RUNTIME_SHA256,
|
||||
crate::paths::npu_runtime_ready,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Stages the DirectML ONNX Runtime into the app's DirectML runtime dir (P2).
|
||||
/// Unlike the NPU bundle there's no hosted default yet, so the URL must come
|
||||
/// from `WA_DIRECTML_RUNTIME_URL` — or drop `onnxruntime.dll` into
|
||||
/// `runtime\directml\` manually (dev testing can also just set `ORT_DYLIB_PATH`).
|
||||
#[cfg(feature = "npu")]
|
||||
async fn stage_directml_runtime(app: &AppHandle) -> WaResult<()> {
|
||||
if crate::paths::directml_runtime_ready() {
|
||||
return Ok(());
|
||||
}
|
||||
let dir = crate::paths::directml_runtime_dir();
|
||||
std::fs::create_dir_all(&dir).map_err(|e| WaError::new("directml", e.to_string()))?;
|
||||
let url = std::env::var("WA_DIRECTML_RUNTIME_URL").map_err(|_| {
|
||||
WaError::new(
|
||||
"directml",
|
||||
"no DirectML runtime: set WA_DIRECTML_RUNTIME_URL or place onnxruntime.dll in runtime\\directml\\",
|
||||
)
|
||||
})?;
|
||||
// Empty SHA: no published bundle to pin yet (ponytail — fill in once hosted).
|
||||
download_and_extract_runtime(app, &dir, &url, "", crate::paths::directml_runtime_ready).await
|
||||
}
|
||||
|
||||
#[cfg(feature = "npu")]
|
||||
@@ -966,10 +1004,21 @@ fn stage_npu_runtime_from_local(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Downloads the runtime bundle (streaming progress + SHA-256 check) and unzips
|
||||
/// its DLLs flat into `dir`.
|
||||
/// Downloads a runtime bundle (streaming progress + SHA-256 check) and unzips
|
||||
/// its DLLs flat into `dir`. `sha256` empty disables the integrity check;
|
||||
/// `ready` is the on-disk readiness predicate for the target runtime (NPU or
|
||||
/// DirectML), so this one function serves both.
|
||||
// ponytail: progress event + error domain stay "npu"/"npu://download" even on
|
||||
// the DirectML path — cosmetic only, no frontend consumer for a directml
|
||||
// channel yet. Split them out when the Settings UI grows a DirectML indicator.
|
||||
#[cfg(feature = "npu")]
|
||||
async fn download_and_extract_runtime(app: &AppHandle, dir: &Path, url: &str) -> WaResult<()> {
|
||||
async fn download_and_extract_runtime(
|
||||
app: &AppHandle,
|
||||
dir: &Path,
|
||||
url: &str,
|
||||
sha256: &str,
|
||||
ready: fn() -> bool,
|
||||
) -> WaResult<()> {
|
||||
use futures_util::StreamExt;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
@@ -1002,7 +1051,7 @@ async fn download_and_extract_runtime(app: &AppHandle, dir: &Path, url: &str) ->
|
||||
drop(file);
|
||||
|
||||
let digest = format!("{:x}", hasher.finalize());
|
||||
if !NPU_RUNTIME_SHA256.is_empty() && digest != NPU_RUNTIME_SHA256 {
|
||||
if !sha256.is_empty() && digest != sha256 {
|
||||
let _ = std::fs::remove_file(&tmp);
|
||||
return Err(WaError::new("npu", "runtime bundle checksum mismatch"));
|
||||
}
|
||||
@@ -1016,7 +1065,7 @@ async fn download_and_extract_runtime(app: &AppHandle, dir: &Path, url: &str) ->
|
||||
.map_err(|e| WaError::new("npu", e))?;
|
||||
let _ = std::fs::remove_file(&tmp);
|
||||
|
||||
if !crate::paths::npu_runtime_ready() {
|
||||
if !ready() {
|
||||
return Err(WaError::new(
|
||||
"npu",
|
||||
"runtime bundle extracted but onnxruntime.dll is missing",
|
||||
@@ -1070,6 +1119,31 @@ pub async fn download_npu_package(app: AppHandle) -> WaResult<()> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Downloads everything the DirectML GPU engine needs — the same Whisper ONNX
|
||||
/// model as the NPU path, plus the DirectML runtime (P2). The non-Vulkan GPU
|
||||
/// path for AMD/Intel.
|
||||
#[tauri::command]
|
||||
pub async fn download_directml_package(app: AppHandle) -> WaResult<()> {
|
||||
#[cfg(feature = "npu")]
|
||||
{
|
||||
download_npu_model(app.clone()).await?; // identical ONNX artifacts
|
||||
stage_directml_runtime(&app).await?;
|
||||
let _ = app.emit(
|
||||
"directml://download",
|
||||
serde_json::json!({ "stage": "done", "ready": crate::paths::directml_runtime_ready() }),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(not(feature = "npu"))]
|
||||
{
|
||||
let _ = app;
|
||||
Err(WaError::new(
|
||||
"directml",
|
||||
"this build has no ONNX/DirectML support",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_models() -> WaResult<Vec<ModelInfo>> {
|
||||
let settings = load_settings();
|
||||
|
||||
Reference in New Issue
Block a user