diff --git a/src/LinML.Cli/InferencePipeline.cs b/src/LinML.Cli/InferencePipeline.cs
index 09907bd..a653244 100644
--- a/src/LinML.Cli/InferencePipeline.cs
+++ b/src/LinML.Cli/InferencePipeline.cs
@@ -73,7 +73,7 @@ public static SelectionOutcome Select(
if (!report.AvailableProviders.Contains(candidate)
&& KnownPlugins.All.Any(p => p.Id == candidate))
{
- unavailableReasons[candidate] = ProviderListing.NotListedReason(candidate);
+ unavailableReasons[candidate] = UnavailableReason(candidate);
}
}
@@ -260,6 +260,13 @@ public static OpenSessionOutcome OpenSession(
null, decision.SelectedProvider!, fallbackChain);
}
+ ///
+ /// Why catalog plugin is not available. One this host cannot run
+ /// gets no install hint: says why instead.
+ ///
+ internal static string UnavailableReason(ProviderId candidate) =>
+ ProviderListing.NotListedReason(candidate, installable: ProviderArgs.NotApplicableNote(candidate) is null);
+
internal static string FormatSelectionFailure(string? failureReason, ProviderId? preferredProvider)
{
var errorMsg = $"Provider selection failed: {failureReason ?? "Unknown reason"}";
diff --git a/src/LinML.Cli/ProviderArgs.cs b/src/LinML.Cli/ProviderArgs.cs
index b6c5afd..fe93bb4 100644
--- a/src/LinML.Cli/ProviderArgs.cs
+++ b/src/LinML.Cli/ProviderArgs.cs
@@ -368,10 +368,7 @@ public static IReadOnlyDictionary RegisterEpLibraries(
return null;
}
- string? gap = Commands.SetupCommand.ArchitectureNote(
- spec, System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant())
- ?? Commands.SetupCommand.MigraphXHardwareNote(spec.Id, Commands.SetupCommand.ProbeAmdRocm());
- if (gap is not null)
+ if (NotApplicableNote(spec.Id) is { } gap)
{
return $"{GetShortName(spec.Id)} is not applicable on this host: {gap}.";
}
@@ -381,6 +378,21 @@ public static IReadOnlyDictionary RegisterEpLibraries(
: null;
}
+ ///
+ /// Why this host lacks the hardware or architecture to run catalog plugin
+ /// (so installing it would not help), or null when it could run here
+ /// or is not a catalog plugin.
+ ///
+ public static string? NotApplicableNote(ProviderId? provider)
+ {
+ var spec = LinML.Catalog.KnownPlugins.All.FirstOrDefault(p => p.Id == provider);
+ return spec is null
+ ? null
+ : Commands.SetupCommand.ArchitectureNote(
+ spec, System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant())
+ ?? Commands.SetupCommand.MigraphXHardwareNote(spec.Id, Commands.SetupCommand.ProbeAmdRocm());
+ }
+
///
/// Registers, in this process, every catalog plugin that resolves on this host, so that
/// discovery, readiness and catalog status all see the same EP devices. A plugin that
diff --git a/src/LinML.Runtime.Onnx/ProviderListing.cs b/src/LinML.Runtime.Onnx/ProviderListing.cs
index 3a8f54a..619c47e 100644
--- a/src/LinML.Runtime.Onnx/ProviderListing.cs
+++ b/src/LinML.Runtime.Onnx/ProviderListing.cs
@@ -22,9 +22,11 @@ public static bool HasEpDevices(ProviderId provider) =>
///
/// The reason a provider is not listed, worded for the state it is in: built-in, loaded but
- /// without devices, failed to load (with ORT's message), or not loaded at all.
+ /// without devices, failed to load (with ORT's message), or not loaded at all. Pass
+ /// false when the caller knows this host cannot run the
+ /// provider (no ROCm for MIGraphX, say): a plugin that is not loaded then gets no install hint.
///
- public static string NotListedReason(ProviderId provider)
+ public static string NotListedReason(ProviderId provider, bool installable = true)
{
var plugin = KnownPlugins.All.FirstOrDefault(p => p.Id == provider);
if (plugin is null)
@@ -51,7 +53,9 @@ public static string NotListedReason(ProviderId provider)
return $"Provider '{provider}' is installed but failed to register: {failure}";
}
- return $"Provider '{provider}' is not loaded into this process. If the plugin is not installed, run 'linml catalog install {ShortName(provider)}' to vendor it.";
+ return installable
+ ? $"Provider '{provider}' is not loaded into this process. If the plugin is not installed, run 'linml catalog install {ShortName(provider)}' to vendor it."
+ : $"Provider '{provider}' is not loaded into this process.";
}
// Matches the CLI's short names for these providers (ProviderArgs.GetShortName).
diff --git a/tests/LinML.Cli.Tests/ProviderArgsTests.cs b/tests/LinML.Cli.Tests/ProviderArgsTests.cs
index f4e34f8..5c3b7a3 100644
--- a/tests/LinML.Cli.Tests/ProviderArgsTests.cs
+++ b/tests/LinML.Cli.Tests/ProviderArgsTests.cs
@@ -519,4 +519,21 @@ public void ResolveEpLibraries_Wsl_ExplicitAndNonStockMappings_StayAuthoritative
[$"NvTensorRTRTXExecutionProvider={layout.Stock}"], ProviderId.TensorrtRtx, layout.Root);
Assert.Equal(layout.Stock, explicitArg["NvTensorRTRTXExecutionProvider"]);
}
+
+ // The selection failure for a provider this host cannot run must not tell the user to install
+ // it, whatever this process has registered: `run --require-provider migraphx` on a host without
+ // ROCm printed the not-applicable hint and an install hint from the listing reason.
+ [Fact]
+ public void UnavailableReason_ProviderTheHostCannotRun_HasNoInstallHint()
+ {
+ // MIGraphX without ROCm reaches the generic not-loaded reason; a plugin for another
+ // architecture (QNN on x64) is already worded as such and never had the hint.
+ if (Commands.SetupCommand.ProbeAmdRocm())
+ {
+ return;
+ }
+
+ Assert.NotNull(ProviderArgs.NotApplicableNote(ProviderId.MigraphX));
+ Assert.DoesNotContain("catalog install", InferencePipeline.UnavailableReason(ProviderId.MigraphX), StringComparison.Ordinal);
+ }
}
diff --git a/tests/LinML.Runtime.Onnx.Tests/ProviderListingTests.cs b/tests/LinML.Runtime.Onnx.Tests/ProviderListingTests.cs
index be48f83..91086b2 100644
--- a/tests/LinML.Runtime.Onnx.Tests/ProviderListingTests.cs
+++ b/tests/LinML.Runtime.Onnx.Tests/ProviderListingTests.cs
@@ -38,6 +38,22 @@ public void NotListedReason_UnloadedPlugin_SuggestsInstallWithShortId()
Assert.Contains("linml catalog install openvino", reason, StringComparison.Ordinal);
}
+ [Fact]
+ public void NotListedReason_UnloadedPluginTheHostCannotRun_HasNoInstallHint()
+ {
+ const string openVinoLibrary = "libonnxruntime_providers_openvino_plugin.so";
+ if (!OpenVinoRunsOnThisHost || PluginLibraryRegistry.RegisteredLibraryFileNames.Contains(openVinoLibrary)
+ || PluginLibraryRegistry.RegistrationFailure(openVinoLibrary) is not null)
+ {
+ return;
+ }
+
+ string reason = ProviderListing.NotListedReason(ProviderId.OpenVino, installable: false);
+
+ Assert.Contains("is not loaded into this process", reason, StringComparison.Ordinal);
+ Assert.DoesNotContain("catalog install", reason, StringComparison.Ordinal);
+ }
+
[Fact]
public void NotListedReason_PluginForAnotherArchitecture_SaysItWillNotRunHere()
{