Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static LocalInferencePlan ResolveInstalledPlan(LocalAiResolvedInstall ins
LocalAiInstallManifest manifest = install.Manifest;
LlamaRuntimeVariant runtime = LlamaRuntimeCatalog.FindInstalled(manifest.RuntimeId)
?? throw new InvalidDataException("The managed llama-server runtime is no longer qualified.");
LocalModelInfo model = LocalModelCatalog.FindInstalled(manifest.ModelCatalogId)
LocalModelInfo model = LocalModelCatalog.FindInstalled(manifest.ModelCatalogId, manifest.ModelAsset.SourceUrl)
?? throw new InvalidDataException("The managed local AI model is no longer qualified.");
return new LocalInferencePlan(runtime, model, ResolveQualifiedReceipt(manifest, runtime, model),
LocalInferenceModelSelectionOrigin.Explicit, manifest.SelectedGpuId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ public static string BuildPrimaryModel(LocalAiResolvedInstall install)

private static LocalModelInfo GetQualifiedModel(LocalAiResolvedInstall install)
{
LocalModelInfo model = LocalModelCatalog.FindInstalled(install.Manifest.ModelCatalogId)
LocalModelInfo model = LocalModelCatalog.FindInstalled(
install.Manifest.ModelCatalogId, install.Manifest.ModelAsset.SourceUrl)
?? throw new InvalidDataException("The managed Local AI model is no longer qualified.");
if (!string.Equals(model.Id, install.Manifest.ModelAlias, StringComparison.Ordinal))
throw new InvalidDataException("The managed Local AI model alias does not match the qualified catalog.");
Expand Down
3 changes: 2 additions & 1 deletion src/OpenClaw.SetupEngine/LocalAiInstallReconciler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,8 @@ public async Task<LocalAiReconcileResult> ReconcileAsync(
/// <summary>Read-only inspection for onboarding; unlike reconciliation, never migrates or saves receipts.</summary>
public async Task<bool> InspectAsync(LocalAiResolvedInstall install, CancellationToken ct)
{
var model = LocalModelCatalog.FindInstalled(install.Manifest.ModelCatalogId);
var model = LocalModelCatalog.FindInstalled(
install.Manifest.ModelCatalogId, install.Manifest.ModelAsset.SourceUrl);
var installedRuntime = LlamaRuntimeCatalog.FindInstalled(install.Manifest.RuntimeId);
if (model is null || installedRuntime is null)
return false;
Expand Down
5 changes: 3 additions & 2 deletions src/OpenClaw.SetupEngine/LocalAiSetupSteps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ public override async Task<StepResult> ExecuteAsync(SetupContext ctx, Cancellati
StringComparison.OrdinalIgnoreCase)
? LocalInferenceEligibility.EvaluateInstalled(hardware, selectedModelId)
: LocalInferenceEligibility.Evaluate(hardware, selectedModelId);
if (string.Equals(selectedModelId, LocalModelCatalog.NemotronBoltModelId, StringComparison.OrdinalIgnoreCase) &&
string.Equals(selectedModelId, ctx.Config.LocalAi.InstalledReceiptModelId, StringComparison.OrdinalIgnoreCase))
// Headless --config setup cannot carry the UI-only receipt hint. The
// stored receipt owns an installed Bolt recipe and its exact context.
if (string.Equals(selectedModelId, LocalModelCatalog.NemotronBoltModelId, StringComparison.OrdinalIgnoreCase))
{
try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ namespace OpenClaw.Shared.Inference.Catalog;

public static partial class LlamaRuntimeCatalog
{
// Bolt needs LiLiCorr decoding from support-lilicorr plus per-layer expert loading.
// Pin the tested source and payload here while other models keep the stable runtime.
// Base: https://github.com/frozenblade1224/llama.cpp/tree/a972392d7db3678954c66f06744e535519314009
// Bolt uses upstream LiLiCorr support for the public drafter tensor names.
// Keep its runtime pin independent from the stable runtime used by other models.
public const string NemotronBoltRuntimeFamily = "nemotron-bolt-lilicorr";
public const string NemotronBoltSourceCommitSha = "b3e4f6ce7f22adb7f1b1eaa012db8367d45a574a";
public const string NemotronBoltReleaseTag = "bolt-lilicorr-b3e4f6c";
public const string NemotronBoltArm64RuntimeId = "bolt-lilicorr-b3e4f6c-cuda13-arm64";
public const string NemotronBoltSourceCommitSha = "23f7aabc16c72f6f707dba227251c35d7bd1ed96";
public const string NemotronBoltReleaseTag = "b11700";
public const string NemotronBoltArm64RuntimeId = "bolt-lilicorr-b11700-cuda13-arm64";

private static readonly LlamaRuntimeVariant s_nemotronBoltArm64 = new(
NemotronBoltArm64RuntimeId,
Expand All @@ -19,12 +18,12 @@ public static partial class LlamaRuntimeCatalog
Array.AsReadOnly(new[]
{
new PinnedArtifact(
"llama-bolt-lilicorr-b3e4f6c-cuda13-arm64",
"llama-bolt-lilicorr-b11700-cuda13-arm64",
ArtifactRole.RuntimeBinary,
new GitHubReleaseSource("joelagnel/llama.cpp", NemotronBoltReleaseTag, NemotronBoltSourceCommitSha),
"llama-bolt-lilicorr-b3e4f6c-bin-win-cuda-13.4-arm64.zip",
46_836_189,
new Sha256Digest("f2adf45fda36a336d2a539e418c6e26f51d50d6f7bb3c086c79771ea348a9ee5")),
new GitHubReleaseSource("ggml-org/llama.cpp", NemotronBoltReleaseTag, NemotronBoltSourceCommitSha),
"llama-b11700-bin-win-cuda-13.4-arm64.zip",
146_943_791,
new Sha256Digest("8474f9edb24e917e5bf0c3824dd79bda5659483ee6adfcbf27197d05defb2783")),
new PinnedArtifact(
"cudart-b11320-cuda13-arm64",
ArtifactRole.RuntimeDependency,
Expand All @@ -41,15 +40,16 @@ public static partial class LlamaRuntimeCatalog
RuntimeFile("cublas64_13.dll", 24_207_984, "49e8fa23d88ac0cbae27e200fc092b894dd55b4de5cb4293993986e29fb9b65a"),
RuntimeFile("cublasLt64_13.dll", 193_128_560, "daf579ae36bb3c85e2340c57a1994f634e07c6f55ef70dd4cdc26695de21c752"),
RuntimeFile("cudart64_13.dll", 606_832, "bd927ddf03823eeead7c8b261a8669d96764b89109311f4ab6accde8b7d97ec2"),
RuntimeFile("ggml-base.dll", 606_720, "a8e7cfb6f5ad7b866d08b5c012cf9469a9b61456f62d76137db00f25031ce4f4"),
RuntimeFile("ggml-cpu.dll", 667_136, "5e1fa7cd5d38f218714dd243e3f7079a01e11e7dd02c62490ab33eb5396b9b7a"),
RuntimeFile("ggml-cuda.dll", 54_052_864, "edf5fe4a86c17b0b64e16f2d634686f929133cfce8802dc7ab726d710afae0a4"),
RuntimeFile("ggml.dll", 49_664, "b2b8d88e1c1b9a95581a5690ddd5b1691099c2e6d5b7e1794185bac615a12c6d"),
RuntimeFile("llama-common.dll", 8_754_688, "c3f9a2d9756a222f359b14b21c0464d1fb2ba19ae5d77ee3333515086e454a83"),
RuntimeFile("llama-server-impl.dll", 3_414_016, "a2e42c8e93194fe67b463b708d7ec0e9f4c10fa2b4707b60126f50a07c463558"),
RuntimeFile("llama-server.exe", 10_240, "33eea374c24f120b9eaf2303d646f67af41d2db4b37e589276f86340e6efb4ca"),
RuntimeFile("llama.dll", 2_708_992, "a85d7984020e23437a0ea91adce0142b6b774925f3301c80dedd8b8961188bcd"),
RuntimeFile("mtmd.dll", 2_408_448, "1d12a069d7cb94bcca9e1d4647108a9bee8d3cd45bdc87623884c23888094f5b"),
RuntimeFile("vcomp140.dll", 367_160, "1a455c526e42d80d3aa3189e082500b6eb7837fcf8be0e9fbd1f26c92acdab71"),
RuntimeFile("ggml-base.dll", 667_136, "ad891be29b12d53c5e7f5b681b7ba9ebedb5101f04314d0cbd83795bcb460896"),
RuntimeFile("ggml-cpu.dll", 835_072, "e49583ceb5ad0c076f2b9905bac66eb1dd49d34fda14f0a4f4fd368fa2b9fef8"),
RuntimeFile("ggml-cuda.dll", 146_929_152, "e3c7a7b5a79cc71cb667957ad2e5b28644ea843344c217a31fde13ed22e1e8ae"),
RuntimeFile("ggml-rpc.dll", 215_040, "e7617fb63ae34218fbc15d4644057c4f44a3590afc21f8bfb4cabe6030996fc5"),
RuntimeFile("ggml.dll", 69_120, "cca7613b2124c1b2cd9e53663269cd64fbcb1b480a465872915130b967c50fec"),
RuntimeFile("llama-common.dll", 7_157_760, "b88408489b7445b4bba94cccf18d54b6f07618fae7a8a643c5db3d768f7fb5d6"),
RuntimeFile("llama-server-impl.dll", 8_471_040, "43ff8798b8bf397c6fd39cb47a0366a9a5fa0a72c17185e51261747451542349"),
RuntimeFile("llama-server.exe", 9_728, "acaea7451d287ee8812bc2f8175594cbb43008313450ecc22e6e2f4b69b6220d"),
RuntimeFile("llama.dll", 3_121_152, "bf1083da93ce656214a74cae8fdad3580ee96e0bd4e8b27e65c8b6b65c5d3005"),
RuntimeFile("mtmd.dll", 1_576_960, "4b34d7a35913a85a013b1050c143480c87c18aeb7f1421c0574447c2203270e4"),
RuntimeFile("libomp.dll", 764_928, "26caae17f29aaf2238f664375b663cd306d596bc9e36e780fa88356c51fe876a"),
}));
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ namespace OpenClaw.Shared.Inference.Catalog;

public static partial class LocalModelCatalog
{
private static LocalModelInfo CreateNemotronBolt() => new(
// Existing receipts keep their exact prerelease pins until explicitly reinstalled.
// This recipe is never offered for fresh acquisition.
private static readonly LocalModelInfo s_prereleaseNemotronBolt = CreateNemotronBolt(prerelease: true);

private static LocalModelInfo CreateNemotronBolt(bool prerelease = false) => new(
NemotronBoltModelId,
"Nemotron 3.5 Bolt 74B-A9B (IQ2, LiLiCorr)",
"Nemotron 3.5",
Expand All @@ -11,9 +15,10 @@ public static partial class LocalModelCatalog
NemotronBoltModelId,
ArtifactRole.ModelWeights,
new HuggingFaceRevisionSource(
"NVIDIA-Nemotron/Nemotron-3.5-Bolt-74B-A9B-IQ2-GGUF",
"06c7fae4665f61a83319ad98e2b00a2824a5f390"),
"super3.5-vl.gguf",
prerelease ? "NVIDIA-Nemotron/Nemotron-3.5-Bolt-74B-A9B-IQ2-GGUF"
: "nvidia/NVIDIA-Nemotron-3.5-Bolt-74B-A9B-24GB-GGUF",
prerelease ? "06c7fae4665f61a83319ad98e2b00a2824a5f390" : "e0fecb69cb3e5e4c5e8a056fa2aec98c78e91246"),
prerelease ? "super3.5-vl.gguf" : "NVIDIA-Nemotron-3.5-Bolt-74B-A9B-24GB.gguf",
22_018_326_528,
new Sha256Digest("91d63363e782c8a3d5f4be42816eba8e0d42dd2530bd07a7d0d2dc440599af75")),
new LocalModelRunRecipe(
Expand All @@ -32,11 +37,13 @@ public static partial class LocalModelCatalog
"nemotron-3.5-bolt-lilicorr-nvfp4",
ArtifactRole.ModelWeights,
new HuggingFaceRevisionSource(
"NVIDIA-Nemotron/Nemotron-3.5-Bolt-74B-A9B-IQ2-LiLiCorr-GGUF",
"77844a4db96a62c0a6e14247af855b8ae8e2f89b"),
"originalLi5_final_step353859_NVFP4.gguf",
998_242_176,
new Sha256Digest("9c4033517096cfca23ad9450044186204ad1ab35efa77e058870f6f61fc245e0")),
prerelease ? "NVIDIA-Nemotron/Nemotron-3.5-Bolt-74B-A9B-IQ2-LiLiCorr-GGUF"
: "nvidia/NVIDIA-Nemotron-3.5-Bolt-74B-A9B-24GB-LiLiCorr-GGUF",
prerelease ? "77844a4db96a62c0a6e14247af855b8ae8e2f89b" : "182d2987939e12f458298b5d5f6ef98912f4ad6c"),
prerelease ? "originalLi5_final_step353859_NVFP4.gguf" : "NVIDIA-Nemotron-3.5-Bolt-74B-A9B-24GB-LiLiCorr.gguf",
prerelease ? 998_242_176 : 998_242_560,
new Sha256Digest(prerelease ? "9c4033517096cfca23ad9450044186204ad1ab35efa77e058870f6f61fc245e0"
: "080e1ea41c9562a7804dbf30c6fbd4d17e216c6b8ff666f88e6edb1816010194")),
draftAttentionLayerCount: 5,
// Saved prompt snapshots and serialized recurrent checkpoints can
// exceed the KV cache itself when agent conversations switch.
Expand Down
10 changes: 10 additions & 0 deletions src/OpenClaw.Shared/Inference/Catalog/LocalModelCatalog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,16 @@ public static IReadOnlyList<LocalInferenceRunProfile> GetProfiles(LocalModelInfo
: s_legacyModels.SingleOrDefault(model =>
string.Equals(model.Id, id, StringComparison.OrdinalIgnoreCase)));

/// <summary>
/// Retains known earlier artifact pins under the same catalog id when the
/// receipt names their source URL. Full receipt validation is still required.
/// </summary>
public static LocalModelInfo? FindInstalled(string? id, string? modelSourceUrl) =>
string.Equals(id, NemotronBoltModelId, StringComparison.OrdinalIgnoreCase) &&
string.Equals(modelSourceUrl, s_prereleaseNemotronBolt.Weights.DownloadUri.AbsoluteUri, StringComparison.Ordinal)
? s_prereleaseNemotronBolt
: FindInstalled(id);

/// <summary>True when the id resolves only to a retired catalog entry.</summary>
public static bool IsLegacy(string? id) => Find(id) is null && FindInstalled(id) is not null;

Expand Down
51 changes: 41 additions & 10 deletions tests/OpenClaw.Connection.Tests/LocalAiBoltRuntimeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,14 @@ public async Task BoltStartup_DoesNotDowngrade256KWhenOnly128KFits()
Assert.Equal(262144, (await new LocalAiManifestStore(paths).LoadAsync())!.Manifest.ContextLength);
}

[Fact]
public async Task BoltStartupAndRestart_Retain128KWhenMoreMemoryIsAvailable()
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task BoltStartupAndRestart_Retain128KWhenMoreMemoryIsAvailable(bool prerelease)
{
using var temp = new TempDirectory("bolt-runtime-retention-");
var paths = await PrepareBoltInstallAsync(temp, 131072);
var paths = await PrepareBoltInstallAsync(temp, 131072, prerelease);
var original = (await new LocalAiManifestStore(paths).LoadAsync())!.Manifest;
var events = new SynchronizedEventLog();
var platform = new FakePlatform();
var hardware = new BoltHardwareProbe(48L << 30);
Expand All @@ -68,13 +71,41 @@ public async Task BoltStartupAndRestart_Retain128KWhenMoreMemoryIsAvailable()
Assert.Contains("ctx-size = 131072", (await File.ReadAllTextAsync(paths.RouterPresetPath)).Split(Environment.NewLine));
Assert.Equal(LocalAiRuntimeState.Healthy, (await runtime.RestartAsync()).State);
Assert.Equal(2, hardware.Calls);
Assert.Equal(131072, (await new LocalAiManifestStore(paths).LoadAsync())!.Manifest.ContextLength);
var current = (await new LocalAiManifestStore(paths).LoadAsync())!.Manifest;
Assert.Equal(131072, current.ContextLength);
Assert.Equal(original.ModelId, current.ModelId);
Assert.Equal(original.ModelAsset, current.ModelAsset);
Assert.Equal(Assert.Single(original.AdditionalModelAssetsOrEmpty), Assert.Single(current.AdditionalModelAssetsOrEmpty));
}

[Fact]
public async Task PrereleaseBolt_RejectsPublicDraftReceipt()
{
using var temp = new TempDirectory("bolt-mixed-receipt-");
var paths = await PrepareBoltInstallAsync(temp, 131072, prerelease: true);
var install = (await new LocalAiManifestStore(paths).LoadAsync())!;
var draft = LocalModelCatalog.Find(LocalModelCatalog.NemotronBoltModelId)!.Recipe.DraftWeights!;
var mixed = install with
{
Manifest = install.Manifest with
{
AdditionalModelAssets = [new LocalAiAssetReceipt
{
FileName = Path.GetFileName(draft.RelativePath), SourceUrl = draft.DownloadUri.AbsoluteUri,
SizeBytes = draft.SizeBytes, Sha256 = draft.Sha256.Value,
}],
},
};

Assert.Throws<InvalidDataException>(() => LlamaServerRouterConfiguration.ResolveInstalledPlan(mixed));
}

private static async Task<LocalAiPaths> PrepareBoltInstallAsync(TempDirectory temp, int context)
private static async Task<LocalAiPaths> PrepareBoltInstallAsync(TempDirectory temp, int context, bool prerelease = false)
{
var paths = new LocalAiPaths(temp.Path);
var model = LocalModelCatalog.Find(LocalModelCatalog.NemotronBoltModelId)!;
var weights = prerelease ? NemotronBoltPrereleaseFixture.Weights : model.Weights;
var draft = prerelease ? NemotronBoltPrereleaseFixture.DraftWeights : model.Recipe.DraftWeights!;
var runtime = LlamaRuntimeCatalog.Find(RuntimeArchitecture.Arm64, model.RuntimeFamily)!;
var profile = LocalModelCatalog.GetProfiles(model).Single(p => p.ContextTokens == context);
string cache = temp.Combine("hf");
Expand All @@ -92,17 +123,17 @@ string Snapshot(PinnedArtifact artifact)
FileName = Path.GetFileName(artifact.RelativePath), SourceUrl = artifact.DownloadUri.AbsoluteUri,
SizeBytes = artifact.SizeBytes, Sha256 = artifact.Sha256.Value,
};
var source = (HuggingFaceRevisionSource)model.Weights.Source;
var source = (HuggingFaceRevisionSource)weights.Source;
var manifest = ValidManifest() with
{
SchemaVersion = LocalAiInstallManifest.AdditionalAssetsSchemaVersion,
EngineVersion = runtime.ReleaseTag, RuntimeId = runtime.Id, ModelCatalogId = model.Id,
ModelAlias = model.Id, ModelId = source.RepositoryId + "@" + source.RevisionSha,
RuntimeAssets = runtime.Artifacts.Select(Receipt).ToImmutableArray(),
ModelAsset = Receipt(model.Weights), ModelPath = Path.Combine("models", model.Weights.RelativePath),
ModelCacheRoot = cache, CachedModelPath = Snapshot(model.Weights),
AdditionalModelAssets = [Receipt(model.Recipe.DraftWeights!)],
AdditionalModelPaths = [Snapshot(model.Recipe.DraftWeights!)],
ModelAsset = Receipt(weights), ModelPath = Path.Combine("models", weights.RelativePath),
ModelCacheRoot = cache, CachedModelPath = Snapshot(weights),
AdditionalModelAssets = [Receipt(draft)],
AdditionalModelPaths = [Snapshot(draft)],
ContextLength = context, KeyCachePrecision = profile.KeyCachePrecision,
ValueCachePrecision = profile.ValueCachePrecision, DraftKeyCachePrecision = profile.DraftKeyCachePrecision,
DraftValueCachePrecision = profile.DraftValueCachePrecision,
Expand Down
Loading
Loading