Tighten the comments added for PR #8757

This commit is contained in:
danielhanchen 2026-08-16 12:28:38 +00:00
parent afe7c9cec3
commit f04ad2fdb9
4 changed files with 17 additions and 34 deletions

View file

@ -127,14 +127,9 @@ export function getReplayedParams(
if (!remembered) {
return current;
}
// Key by key over the remembered set, not a spread of the entry. An entry is
// whatever is in the settings row, and this app is not its only possible
// writer: the backend's ChatInferenceSettings accepts every persisted key
// inside a per-model dict, maxSeqLength included, and the client sanitiser
// keeps them. Spreading such an entry would replay a context the backend
// never loaded -- the exact thing REMEMBERED_INFERENCE_PARAM_KEYS exists to
// prevent on the way in -- and would carry any stale key straight into the
// live params.
// Key by key, not a spread: the row accepts every persisted key (maxSeqLength
// included) from any writer, and spreading one would replay a context the
// backend never loaded, plus any stale key, into the live params.
const replayed = { ...current };
for (const key of REMEMBERED_INFERENCE_PARAM_KEYS) {
const value = remembered[key];

View file

@ -2215,10 +2215,8 @@ function getHydratedSettingsState(
) {
// Same fence as the global set above: a key the user moved while this
// request was in flight is their edit, and the memory does not outrank it.
// REMEMBERED, not PERSISTED: the same distinction getReplayedParams draws.
// maxSeqLength is persisted but never remembered, and an entry that carries
// one anyway -- the row accepts it -- must not replace the context the model
// actually loaded with.
// REMEMBERED, not PERSISTED, as in getReplayedParams: a maxSeqLength the row
// happens to carry must not replace the context the model loaded with.
const replayed = { ...params };
for (const key of REMEMBERED_INFERENCE_PARAM_KEYS) {
const value = remembered[key];

View file

@ -1,8 +1,9 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
// The hydration replay is a second, independent copy of the replay rules, so it
// needs its own guard for the one key the memory deliberately does not keep.
// The hydration replay is a second copy of the replay rules, so it needs its own
// guard. Its own file: per-model-params-hydration.test.ts shares store state
// across its tests, and an appended case picks up an earlier one's temperature.
import assert from "node:assert/strict";
import { register } from "node:module";
@ -22,12 +23,8 @@ const { useChatRuntimeStore } = await import(
const QWEN = "unsloth/Qwen3.5-9B-GGUF";
test("hydration does not replay a maxSeqLength an entry happens to carry", async () => {
// maxSeqLength is persisted but never remembered: the context a model loads
// with is owned by its load config, and replaying a second copy would leave
// the runtime advertising a context the backend never loaded. This app never
// writes one into an entry -- but the settings row accepts one, from an older
// client, a hand-written payload or a future key set, so the read side has to
// hold the rule too, not just the write side.
// maxSeqLength is persisted but never remembered: the context belongs to the
// load config. This app writes none into an entry, but the row accepts one.
settingsHttp.settings = {
inferenceParamsByModel: {
[QWEN]: { temperature: 0.2, maxSeqLength: 131072 },

View file

@ -362,10 +362,8 @@ test("the snapshot drops params the current model never set", () => {
assert.equal("topK" in picked, false);
});
// An entry is whatever the settings row holds, and this app is not its only
// possible writer: the backend's ChatInferenceSettings accepts every persisted
// key inside a per-model dict, so the read side has to hold the same rules the
// write side does rather than trusting the shape.
// The row accepts every persisted key from any writer, so the read side has to
// hold the write side's rules rather than trust the entry's shape.
test("a maxSeqLength in a stored entry is not replayed over the loaded context", () => {
const replayed = getReplayedParams(
true,
@ -375,8 +373,7 @@ test("a maxSeqLength in a stored entry is not replayed over the loaded context",
true,
);
assert.equal(replayed.temperature, 0.2, "the remembered value still replays");
// The same rule pickRememberedParams follows on the way in: a second copy of
// the context would leave the runtime advertising one the backend never loaded.
// A second copy of the context would advertise one the backend never loaded.
assert.equal(replayed.maxSeqLength, 4096);
});
@ -389,17 +386,13 @@ test("a key that is not an inference param cannot reach the live params", () =>
true,
);
assert.equal(replayed.temperature, 0.2);
// params flows on into request bodies and into the settings write, where the
// payload is extra="forbid".
// params flows on into request bodies and into an extra="forbid" settings write.
assert.equal("notAParam" in replayed, false);
});
// Model ids are not sanitised identifiers. A Hub repo is slash-separated, a
// custom folder is an absolute path -- a drive path on Windows, a UNC path from
// a share or WSL, a spaced path under ~/Library on macOS -- and an external
// model is provider-qualified. All of them are opaque keys here, and an id that
// did not survive the round trip would mean that platform silently cannot
// remember settings at all.
// Model ids are opaque keys: a Hub repo, an absolute path on any OS, or a
// provider-qualified external id. One that did not round trip would mean that
// platform silently cannot remember settings.
for (const [label, id] of [
["a Windows drive path", "C:\\Users\\Daniel\\models\\Qwen3-8B"],
["a UNC share path", "\\\\fileserver\\models\\gemma-3-270m-it"],