From dc7440d0d4e4c8a3bb9c5fb336575a9ff19433bc Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 22 Oct 2026 16:30:16 -0700 Subject: [PATCH] Drop the install hint from the selection failure for a provider the host cannot run On a host with no ROCm and no MIGraphX plugin vendored, run --provider migraphx --require-provider printed "migraphx is not applicable on this host" and, from ProviderListing.NotListedReason, "run 'linml catalog install migraphx'". The release workflow (v0.6.0) failed on ExitCodeTests.Run_MissingRequiredPlugin_Returns3_WithInstallHint because of it; PR CI passed only because test order left a registration failure recorded for the plugin, which takes a branch without the hint. NotListedReason takes installable, and InferencePipeline passes false when ProviderArgs.NotApplicableNote (architecture, or MIGraphX without ROCm) says the host cannot run it. Co-Authored-By: Claude Opus 5.5 --- src/LinML.Cli/InferencePipeline.cs | 9 ++++++++- src/LinML.Cli/ProviderArgs.cs | 20 +++++++++++++++---- src/LinML.Runtime.Onnx/ProviderListing.cs | 10 +++++++--- tests/LinML.Cli.Tests/ProviderArgsTests.cs | 17 ++++++++++++++++ .../ProviderListingTests.cs | 16 +++++++++++++++ 5 files changed, 64 insertions(+), 8 deletions(-) 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() {