mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 02:29:33 +01:00
The previous commit counted 4 rollback copies of the recurrent state for every GGUF with a prediction layer, the runtime's default `draft_num_predict`. A tag can set its own in its params layer, and the runtime then passes that as `--spec-draft-n-max`, which is what `need_n_rs_seq()` turns into rollback copies. Read on 2026-11-08: - qwen3.6:35b-a3b-mtp-q4_k_m: draft_num_predict 2, so 2 copies (was counted as 4). - qwen3.8:27b-mtp-q4_k_m: draft_num_predict 4. - qwen3.8:27b-q8_0: none in its params, so the runtime's 4. The 35B-A3B GGUF row's overhead drops by 132 MB: 0.971 instead of 0.976 on a 36 GB M3 Pro or M3 Max, the same pick. test/local-model-catalog.test.ts now carries the drafted tokens per row instead of a yes/no prediction layer. Also states what was verified about llama.cpp's checkpoint default: 8 from b6800 to b8200, 32 from b8250 together with a checkpoint every 8,192 prompt tokens (common/common.h at those tags), in place of an inferred reason. Checked in the b12111 source as well: the draft context of a Qwen-family MTP model caches the prediction block alone (`mtp_on_hybrid_qwen`, layers at or above n_layer()), and the main context leaves it out, so the row counts that layer once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
139 lines
6.5 KiB
TypeScript
139 lines
6.5 KiB
TypeScript
/**
|
|
* @fileoverview Tests for `modelFitsBudget()` of src/local-model/recommend.ts: the judgement
|
|
* the install job makes before it downloads a model.
|
|
*
|
|
* Pinned: it agrees with what `recommend()` offers (a model that is the pick or listed fits,
|
|
* a model that is in neither and is not left out for its rank does not), it does not depend
|
|
* on the ranking, a model the catalogue does not have never fits, and it judges against the
|
|
* budget the runtime measured when the caller passes one (the whole point of asking again
|
|
* after the runtime started).
|
|
*
|
|
* Port: N/A (pure functions).
|
|
*/
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
import { getLocalModelCatalog } from '../src/config/local-model-catalog.js';
|
|
import { formatGb, memClassGb } from '../src/local-model/format.js';
|
|
import { modelFitsBudget, recommend } from '../src/local-model/recommend.js';
|
|
import type { HardwareFacts, HarnessFact } from '../src/types.js';
|
|
|
|
/** A Mac with Apple silicon and `gb` GB of memory as sold. */
|
|
function mac(gb: number, chip = 'Apple M5 Pro'): HardwareFacts {
|
|
const memBytes = gb * 1024 ** 3;
|
|
return {
|
|
platform: 'darwin',
|
|
arch: 'arm64',
|
|
arm64: true,
|
|
translated: false,
|
|
memBytes,
|
|
memGb: formatGb(memBytes),
|
|
memClassGb: memClassGb(memBytes),
|
|
chip,
|
|
macosMajor: 27,
|
|
macosVersion: '27.2',
|
|
wiredLimitMb: 0,
|
|
source: 'sysctl',
|
|
};
|
|
}
|
|
|
|
/** Every harness of the catalogue, installed and accepting every model id. */
|
|
function harnessFacts(): HarnessFact[] {
|
|
return getLocalModelCatalog().harnesses.map((row) => ({
|
|
cli: row.cli,
|
|
label: row.cli,
|
|
installed: true,
|
|
installable: false,
|
|
acceptsModelId: () => true,
|
|
}));
|
|
}
|
|
|
|
describe('modelFitsBudget', () => {
|
|
const catalog = getLocalModelCatalog();
|
|
|
|
it('agrees with the recommendation on every model and context of the catalogue', () => {
|
|
for (const gb of [8, 16, 18, 24, 32, 36, 48, 64, 96, 128, 192, 256, 512]) {
|
|
const hw = mac(gb);
|
|
const offered = recommend(hw, catalog, harnessFacts());
|
|
const rows = [offered.pick, ...offered.alternatives].filter((row) => row !== undefined);
|
|
// Whatever the recommendation offers fits at the context it offers it at.
|
|
for (const row of rows) {
|
|
expect(modelFitsBudget(hw, catalog, row.modelId, row.context), `${gb} GB ${row.modelId}`).toBe(true);
|
|
}
|
|
// A Mac that gets no recommendation for lack of memory has no model that fits at a
|
|
// context a harness works at.
|
|
if (offered.verdict === 'none') {
|
|
const smallestHarnessContext = Math.min(...catalog.harnesses.filter((h) => !h.gated).map((h) => h.minContext));
|
|
for (const model of catalog.models) {
|
|
for (const context of model.contexts.filter((ctx) => ctx >= smallestHarnessContext)) {
|
|
expect(modelFitsBudget(hw, catalog, model.id, context), `${gb} GB ${model.id}`).toBe(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
it('does not depend on the ranking: a model that fits and is listed nowhere still fits', () => {
|
|
// On a 128 GB Mac more models fit than the recommendation lists.
|
|
const hw = mac(128, 'Apple M5 Max');
|
|
const offered = recommend(hw, catalog, harnessFacts());
|
|
const listed = new Set([offered.pick, ...offered.alternatives].map((row) => row?.modelId));
|
|
const unlisted = catalog.models.filter((model) => !listed.has(model.id));
|
|
expect(unlisted.length).toBeGreaterThan(0);
|
|
for (const model of unlisted) {
|
|
expect(modelFitsBudget(hw, catalog, model.id, model.contexts.at(-1) as number), model.id).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('rules out a model that is too large, and one the catalogue does not have', () => {
|
|
expect(modelFitsBudget(mac(48), catalog, 'ornith-1.5:122b', 131072)).toBe(false);
|
|
expect(modelFitsBudget(mac(48), catalog, 'no-such:1b', 65536)).toBe(false);
|
|
expect(modelFitsBudget(mac(48), catalog, 'qwen3.6:35b-a3b', 131072)).toBe(true);
|
|
});
|
|
|
|
it('judges against the budget the runtime measured, when it is given one', () => {
|
|
const id = 'qwen3.6:35b-a3b';
|
|
// The MacBook: the runtime reported 37.4 GiB, and the model needs 36.0 of those 40.2 GB.
|
|
expect(modelFitsBudget(mac(48), catalog, id, 131072, { measuredTotalBytes: 40_157_944_217 })).toBe(true);
|
|
// A runtime that reports less than the estimate rules out what the estimate allowed.
|
|
expect(modelFitsBudget(mac(48), catalog, id, 131072, { measuredTotalBytes: 33 * 1024 ** 3 })).toBe(false);
|
|
// And one that reports more admits what the estimate ruled out: on a 36 GB Mac the GGUF
|
|
// 35B-A3B at its full window needs 28.8 GB, 1.032 of the 27.9 GB estimate and 0.958 of 28 GiB.
|
|
const moeGguf = 'qwen3.6:35b-a3b-mtp-q4_k_m';
|
|
expect(modelFitsBudget(mac(36), catalog, moeGguf, 262144)).toBe(false);
|
|
expect(modelFitsBudget(mac(36), catalog, moeGguf, 262144, { measuredTotalBytes: 28 * 1024 ** 3 })).toBe(true);
|
|
// The reserve still bounds it: 100 GiB "measured" on a 48 GB Mac is 40 GiB (42.9 GB) of
|
|
// budget, so the 122B with its 97.4 GB stays out although 100 GiB would hold it.
|
|
const huge = { measuredTotalBytes: 100 * 1024 ** 3 };
|
|
expect(modelFitsBudget(mac(48), catalog, 'ornith-1.5:122b', 131072, huge)).toBe(false);
|
|
expect(modelFitsBudget(mac(128), catalog, 'ornith-1.5:122b', 131072, huge)).toBe(true);
|
|
});
|
|
|
|
it('agrees with the recommendation under a measured budget too', () => {
|
|
for (const [gb, measuredGib] of [
|
|
[16, 11.8],
|
|
[32, 20],
|
|
[48, 37.4],
|
|
[48, 30],
|
|
[64, 50],
|
|
]) {
|
|
const hw = mac(gb);
|
|
const options = { measuredTotalBytes: Math.floor(measuredGib * 1024 ** 3) };
|
|
const offered = recommend(hw, catalog, harnessFacts(), options);
|
|
expect(offered.verdict === 'none' || offered.budgetSource === 'measured').toBe(true);
|
|
for (const row of [offered.pick, ...offered.alternatives].filter((entry) => entry !== undefined)) {
|
|
expect(modelFitsBudget(hw, catalog, row.modelId, row.context, options), `${gb} GB ${row.modelId}`).toBe(true);
|
|
// The next larger context of the same model does not fit, or the row would be at it.
|
|
const model = catalog.models.find((entry) => entry.id === row.modelId);
|
|
const larger = model?.contexts.filter((ctx) => ctx > row.context) ?? [];
|
|
for (const ctx of larger) expect(modelFitsBudget(hw, catalog, row.modelId, ctx, options)).toBe(false);
|
|
}
|
|
}
|
|
});
|
|
|
|
it('is false for a context that is no token count', () => {
|
|
for (const context of [0, -1, Number.NaN, Number.POSITIVE_INFINITY]) {
|
|
expect(modelFitsBudget(mac(128), catalog, 'qwen3.5:9b-q4_k_m', context), String(context)).toBe(false);
|
|
}
|
|
});
|
|
});
|