Machine-learning engineer comparing embedding weights before and after inference, revealing a row changed by PyTorch max_norm.

PyTorch Embedding Can Rewrite Weights During Inference

Tue, Oct 20, 2026

An embedding lookup returns a plausible vector. The module reports evaluation mode, the request executes inside torch.inference_mode(), and no optimizer exists. Yet a comparison with the table captured before the request shows that a stored row changed. Repeating the same request later appears stable, making the first difference easy to dismiss as initialization noise.

For nn.Embedding(max_norm=1.0), that sequence deserves a direct state audit. The question is whether the model owner permits this forward to renormalize stored embedding rows. Correct inference configuration and permission to change model state are separate requirements. A documented operation can function as designed while violating an immutable serving contract.

This playbook uses three independently authored rows, CPU eager execution and float64 values. One row exceeds the cap and is requested, another exceeds it but remains untouched, and a third already falls within the limit. Their different roles expose both the mutation and the weakness of a reassuring request that never reaches it.

The evidence includes exact changed-row detection, before-and-after values, numerical output checks and a sequence of different lookups. The numerical observations come from the supplied October 20, 2026 preflight; expected values come from independent arithmetic. The resulting decision is explicit: authorize declared renormalization, hold a candidate that violates immutability, rebuild from approved state, or rerun when the qualified scope changes.

Declare the embedding table’s serving-state contract

Start with a written policy for the table before its first serving forward. The default here is an immutable-table contract: requests must not mutate any stored embedding row. A row remains protected even when its norm exceeds a runtime cap. Discovering that mismatch creates a release question; it does not silently authorize a correction.

An alternative policy may explicitly permit lookup-triggered renormalization. Name the affected table, the allowed operation, the cap, the norm definition and the initialization state. The owner must approve that policy independently of whether a diagnostic script passes. Permission should describe a bounded behavior, not merely allow any difference associated with an inference request.

Keep two acceptance questions visible. State acceptance asks whether the stored values obey the declared mutation policy. Output acceptance asks whether returned vectors match independently established expectations within a stated tolerance. A numerically acceptable vector does not excuse a prohibited write, and unchanged storage does not establish acceptable outputs.

Also identify when the contract begins. If immutability applies from construction, a warm-up mutation already violates it. If an owner approves preparation before the serving boundary, that preparation needs its own defined result and review. Moving the baseline until differences disappear changes the policy under test.

Record the table identity and configuration beside the decision. “Frozen embeddings” is too ambiguous to serve as either a state specification or an approval.

Locate the write inside Embedding.forward

The PyTorch 2.10 Embedding documentation describes max_norm as a norm limit and explicitly warns that a non-None setting can modify weight in place during forward. That is the relevant mechanism: the lookup itself is allowed to change stored values. An external update operation is unnecessary.

The fixture places measurements immediately around that lookup. It does not infer the cause from a changed downstream label, a file timestamp or a later export. Fresh storage and a minimal call path make the state transition inspectable. Rows that remain untouched provide a control against interpreting the operation as normalization of the entire table.

The neighboring PyTorch serving-mode validation addresses the broader separation between module mode and gradient context. Here those controls are deliberately configured correctly before the decisive inference cases. The investigation then asks whether this particular built-in operation still writes.

Four controls that do not mean the same thing

training=False is module state. The Module evaluation contract defines eval() as train(False) and limits its effect to modules that use that distinction. It is not a declaration that every parameter is read-only.

Disabled autograd recording is a computation context. The no_grad documentation describes disabling reverse-mode gradient recording within its scope. The inference_mode documentation describes additional autograd-related restrictions and explicitly requires setting evaluation mode separately. Neither description supplies a general prohibition on ordinary state writes.

requires_grad=False concerns the parameter’s participation in autograd. The separate max_norm configuration determines whether this embedding has the runtime renormalization mechanism enabled. These settings can coexist; they are not competing names for a single freeze switch.

A useful review therefore keeps four columns rather than compressing them into “inference enabled.” Establish the module mode, the active context, the parameter flag and the cap. Then inspect the stored rows. The first three columns explain the execution conditions; they cannot substitute for the fourth or for the observed state transition.

This separation also keeps the diagnosis fair. The presence of an unauthorized write under the product policy does not, by itself, show that PyTorch violated its API contract.

At review time, name both contracts in the finding. “The capped lookup changed row 0 as expected by the diagnostic, while the release policy forbids that change” identifies the disagreement precisely. It gives the owner a concrete policy choice instead of an ambiguous report that inference was broken.

Pin the CPU runtime and author a three-row oracle

The supplied preflight used CPython 3.13.5 and PyTorch 2.10.0+cpu, with CPU eager execution, torch.float64 and one intra-op thread. The documentation reference is the 2.10 branch, matching the supplied build’s release family. That branch selection does not claim to identify the newest release, and the October 20 check date is not a publication or launch date.

The executable below records Python, the complete PyTorch version string, the PyTorch Git identifier, build configuration, device, dtype and its own source digest. It explicitly sets torch.set_num_threads(1). This is an intra-op setting; the script neither sets nor reports a separate inter-op thread count.

The source SHA-256 for the exact fixture is:

e7a10a2652dec2b317bf4a41ea7ad58af659c6900fd1869f89effc9c8a7f2395

The initial table is deliberately small enough to audit without generated data:

Row ID

Authored values

Euclidean norm

Role

0

[3, 4]

5

Requested over-limit row

1

[0, 2]

2

Initially unrequested over-limit control

2

[0.3, 0.4]

0.5

In-limit lookup control

With cap 1, independent arithmetic gives ideal capped vectors [0.6, 0.8] and [0, 1] for the first two rows. Row 2 already satisfies the limit. These are authored expectations, not values read back from the candidate and relabeled as correct.

The separation matters when interpreting numerical detail. The supplied build returned approximately [0.599999988, 0.799999984] for row 0 in E2–E6. The oracle expects the mathematical direction and scale, while its declared tolerance accommodates that observed difference. There is no need to invent an implementation constant to explain the decimal pattern.

No random initialization or downloaded model determines these inputs. The values, row identities and requested indices are all visible. Consequently, a reviewer can distinguish an unexpected row change from a different initial table, and can identify which rows a request never exercised.

Treat the runtime record as evidence attached to a particular run. Copying the supplied version string into a new report would not establish that another machine executed the same build. When rerunning, retain the values actually printed by that environment.

The selected norms also make the test sensitive without relying on a boundary case. Row 0 is clearly over the limit, row 1 supplies a different over-limit magnitude, and row 2 is clearly below it. If every row began below the cap, the experiment could complete successfully without ever demonstrating the transition under investigation.

Keep the row IDs attached to the values throughout the report. A sorted list of norms would lose the relationship between the request and the stored location. The oracle needs that relationship to distinguish correct lookup targeting from an unexpected change elsewhere.

Install the complete row-level test harness

Save the following as embedding_probe.py in an owned scratch directory. It requires a suitable installed PyTorch environment and uses only synthetic in-memory tables. It does not accept production artifacts, customer identifiers or external data. The output redirection creates or replaces the named laboratory JSON file in the current directory.

python embedding_probe.py > embedding-results.json

The file must contain the complete driver:

from contextlib import nullcontext
from pathlib import Path
import hashlib
import json
import platform
import torch


def require(ok, message):
    if not ok:
        raise AssertionError(message)


torch.set_num_threads(1)
W = torch.tensor([[3., 4.], [0., 2.], [0.3, 0.4]], dtype=torch.float64)
contexts = {"grad": nullcontext, "no_grad": torch.no_grad,
            "inference": torch.inference_mode}
rows = []


def make(cap, freeze=False, weights=W):
    return torch.nn.Embedding.from_pretrained(
        weights.clone(), freeze=freeze, max_norm=cap, norm_type=2.0,
        padding_idx=None, scale_grad_by_freq=False, sparse=False)


def run(case, cap, mode, context, ids, expected_changed, freeze=False, weights=W):
    model = make(cap, freeze, weights)
    model.train(mode == "train")
    before = model.weight.detach().clone()
    with contexts[context]():
        output = model(torch.tensor(ids, dtype=torch.long))
        flags = {"grad_enabled": torch.is_grad_enabled(),
                 "inference_enabled": torch.is_inference_mode_enabled()}
    after = model.weight.detach().clone()
    changed = torch.where((before != after).any(dim=1))[0].tolist()
    require(changed == expected_changed, f"{case}: changed rows {changed}")
    require(model.weight.grad is None, "no backward pass is permitted")
    expected_output = weights[ids].clone()
    if cap is not None:
        norm = torch.linalg.vector_norm(expected_output, dim=1, keepdim=True)
        expected_output = expected_output / torch.clamp(norm, min=1.0)
    torch.testing.assert_close(output.detach(), expected_output, rtol=1e-6, atol=1e-12)
    rows.append({"case": case, "training": model.training,
                 "requires_grad": model.weight.requires_grad, "max_norm": cap,
                 "ids": ids, **flags, "changed_rows": changed,
                 "before": before.tolist(), "after": after.tolist(),
                 "output": output.detach().tolist()})
    return model


run("E1", None, "eval", "inference", [0], [])
run("E2", 1.0, "train", "grad", [0], [0])
run("E3", 1.0, "eval", "grad", [0], [0])
run("E4", 1.0, "eval", "no_grad", [0], [0])
run("E5", 1.0, "eval", "inference", [0], [0])
run("E6", 1.0, "eval", "inference", [0], [0], freeze=True)
run("E7", 1.0, "eval", "inference", [2], [])
model = make(1.0, freeze=True).eval()
states = [model.weight.detach().clone()]
with torch.inference_mode():
    for ids in ([0], [1], [0]):
        model(torch.tensor(ids, dtype=torch.long))
        states.append(model.weight.detach().clone())
changes = [torch.where((a != b).any(dim=1))[0].tolist()
           for a, b in zip(states, states[1:])]
require(changes == [[0], [1], []], "E8: lookup coverage")
rows.append({"case": "E8", "id_sequence": [[0], [1], [0]],
             "changed_rows_per_call": changes,
             "states": [s.tolist() for s in states]})
# A separately approved NEW state, not a claim to preserve the original model.
normalized = W / torch.clamp(torch.linalg.vector_norm(W, dim=1, keepdim=True), min=1.0)
run("E9", None, "eval", "inference", [0, 1, 2], [], freeze=True, weights=normalized)
require(torch.equal(W, torch.tensor([[3., 4.], [0., 2.], [0.3, 0.4]], dtype=torch.float64)),
        "independent oracle mutated")
print(json.dumps({"status": "PASS_9_CASES", "python": platform.python_version(),
                  "torch": torch.__version__, "torch_git": torch.version.git_version,
                  "config": torch.__config__.show(), "device": "cpu", "dtype": "float64",
                  "source_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
                  "rows": rows}, indent=2))

Each independent call to run() constructs a new embedding, captures its complete table before the first forward, performs one lookup and captures the table again. E8 intentionally uses one separate instance across three lookups; that continuity is the subject of its test.

The assertions check the expected identities of changed rows and compare returned vectors with an independent numerical oracle. E1–E7 and E9 also check that the parameter’s grad field is None. The script contains no backward call or optimizer operation. Those facts describe the call path; the row comparison supplies the state evidence.

The output includes full before-and-after tables for cases using run(). It retains values instead of only a pass marker, so a reviewer can inspect what happened to both requested and unrequested rows. E8 records four states and the changes between adjacent states. It does not record or assert its returned vectors, a deliberate limit to retain when using this diagnostic.

Keep the reference table independent of the candidate

W contains the authored baseline. make() clones the chosen input table before creating an embedding, and the measurements clone the live weights at their respective observation points. These copies are measuring instruments that prevent one candidate’s forward from rewriting another case’s starting values.

Expected outputs come from the independent input table and the declared norm calculation, never from the candidate’s post-forward table. The final assertion checks that W still contains the original authored values. For E9, the reference is the separately prepared table; passing that comparison validates a lookup against that reference, not the business acceptability of the preparation.

The helper supports this experiment’s two cap choices: None and 1.0. Its numerical oracle uses clamp(norm, min=1.0) and is not a general implementation for arbitrary caps. Changing the cap requires a reviewed oracle change and a new source identity.

Finally, PASS_9_CASES means the diagnostic expectations passed, including cases expected to mutate. It is not an immutable-release verdict. A failed assertion also deserves its actual explanation: an unexpected changed row, a numerical mismatch or an execution failure must not be collapsed into the same outcome.

Because JSON is printed only after the assertions finish, an exception can leave the redirected file empty. Check the process result and diagnostic error before treating file creation as evidence of completion. The row-identity assertion also does not independently assert every changed coordinate; the retained values support that additional inspection.

Observe renormalization without any optimizer

E2 starts with training mode and ordinary gradient recording enabled. E3 changes only the module mode to evaluation. E4 uses evaluation mode inside no_grad. Each case requests row 0 from a fresh table with max_norm=1.0. The supplied preflight reports row 0 changed in all three cases.

That progression isolates two tempting explanations. Switching to evaluation does not remove the observed write, and disabling gradient recording does not remove it either. The source makes the remaining operation easy to locate: between the before and after snapshots, the driver calls the embedding with the supplied indices.

The supplied E2–E6 behavior is summarized at row level below. Decimal values marked approximate retain the precision provided in the preflight rather than pretending to be a newly captured full transcript.

Stored row

Before lookup of row 0

After lookup of row 0

State finding

0

[3, 4]

Approximately [0.599999988, 0.799999984]

Changed

1

[0, 2]

[0, 2]

Unchanged despite exceeding cap

2

[0.3, 0.4]

[0.3, 0.4]

Unchanged and already in limit

The returned row-0 vector is approximately the same normalized pair. Thus the observation contains both an output and a stored-state change. Merely checking that the output has a suitable norm would miss the distinction between producing a transformed result and changing the table used by later requests.

An unchanged grad field is useful context because the driver checks it, but it is not the state oracle. The table difference is direct evidence even while that field remains None. The absence of backward and optimizer calls is established by inspecting the complete driver, not inferred solely from that field.

This causal path is separate from optimizer-owned frozen-parameter drift. There is no retained optimizer in this experiment to investigate. The relevant release question is whether the embedding’s own lookup-time transformation belongs to the serving contract.

Under the default immutable policy, E2–E4 successfully demonstrate a prohibited transition. Their supplied results strengthen the measurement method while leaving those mutable candidates inadmissible under that policy.

Retain the full before-table for each case rather than borrowing E2’s baseline for E3 or E4. Independent construction is what makes these comparisons about configuration rather than accumulated request history. If a case accidentally reused an already-renormalized row, a later absence of change could be caused by the earlier case consuming the transition.

Likewise, E2’s training flag does not make this a training experiment. It is one forward under a comparison condition, with the same authored input and the same state observations as the other cases.

Test the correctly configured inference path

E5 applies evaluation mode and enters torch.inference_mode() around the forward. E6 adds from_pretrained(freeze=True). Both use fresh original tables, keep max_norm=1.0 and request row 0. These are the decisive cases for a team that already configured inference correctly.

The supplied preflight reports the following combination of flags and state changes:

Case

training

requires_grad

Gradient recording

Inference context

Changed rows

E5

False

True

Disabled

Enabled

[0]

E6

False

False

Disabled

Enabled

[0]

Those observations remove the need to speculate that a missing evaluation call explains the difference. The driver records the context flags inside the context that performs the lookup. It also captures the initial table before that lookup, so a prior warm-up cannot be mistaken for the starting artifact.

The result should be stated narrowly: this configured forward changes row 0 on the supplied CPU build. It does not establish that every inference request changes a row, that every embedding configuration mutates, or that all state in the process behaves similarly. The later controls distinguish those possibilities.

Freeze=True does not override runtime max_norm

The from_pretrained freeze contract equates freeze=True with setting weight.requires_grad=False. Read that definition alongside the documented forward-time mutation. The factory does not wrap the embedding in a separate no-write mechanism.

E6 supplies the concrete reconciliation. Its parameter flag is false, its inference settings are correct, and row 0 still changes. There is no contradiction once the two contracts are separated: gradient participation is restricted, while runtime renormalization remains configured.

For a release reviewer, the decisive field is therefore the owner’s allowed-mutation policy. If it prohibits every table mutation, E6 belongs on hold even though the settings are internally consistent and the returned vector meets its numerical expectation. If the owner explicitly authorizes this renormalization, the same state transition can be admissible within that approved scope.

Do not “repair” the evidence by deleting requires_grad from the report because it seems confusing. Keeping it beside the changed-row list makes the mistaken assumption visible and helps future reviewers avoid treating a false parameter flag as a complete state-protection mechanism.

For an authorized mutable policy, specify what would still fail review: an unrelated row changing, a requested vector missing its numerical expectation, or an initial table that differs from the approved artifact. Permission to renormalize cannot mean permission to accept arbitrary state drift.

The E6 result is especially useful in that review because it closes a common explanatory gap. Adding the freeze flag did not alter the observed changed-row set. The unresolved question is permission for this operation, rather than another missing inference switch.

Show why an in-limit request misses the hazard

E7 uses the same cap and the same evaluation/inference configuration as E5, but requests row 2. Its starting values are [0.3, 0.4], with norm 0.5. The supplied preflight reports no changed rows. The returned vector satisfies the independent in-limit expectation.

That is a meaningful negative control. It shows that the harness does not report a numerical difference merely because an embedding forward occurred. It also separates the configured possibility of renormalization from whether a particular request triggers a change in this table.

Inspect the complete after-table, however. Row 0 remains [3, 4] and row 1 remains [0, 2]. Both still exceed the cap because this request did not access them. A stable row-2 request therefore leaves the conditions for later mutation present in the same instance.

A request-level report should identify its coverage denominator. “One requested row remained unchanged” is supported here. “The three-row table has no future mutation exposure” is not. Even measuring all three rows after the request does not turn the two untouched rows into exercised inputs.

This distinction matters when choosing a warm-up example. A representative input for response shape or routing may happen to contain only in-limit indices. Its successful return can validate that limited path while failing to challenge the state policy that matters for release. Repeating it increases the count of successful requests without increasing the set of rows reached.

In this toy table, the two over-limit controls are known in advance. A reviewer can deliberately select their IDs and compare those results with E7. For another approved table, input coverage would need to be derived from its identity and authorized contents; the three synthetic IDs do not stand in for an unknown vocabulary.

Also preserve the first-forward boundary when collecting evidence. If a diagnostic tool quietly looks up rows before taking the baseline, it can consume the very transition the audit was intended to observe. Checking initial values before any forward makes that mistake detectable.

E7 consequently answers a useful but local question: the in-limit lookup left stored numeric values unchanged at the measurement boundaries. It cannot authorize the active cap under a blanket immutable policy for requests that may reach rows 0 or 1.

Make that limitation visible beside the result. Report requested IDs and untouched IDs separately, so a reader cannot mistake a complete table snapshot for complete lookup coverage. Observation breadth and exercised-input breadth are different quantities.

Follow the table through a sequence of different lookups

E8 changes the experimental structure deliberately. It creates one fresh frozen embedding, sets evaluation mode, captures the initial state and then requests row 0, row 1 and row 0 again inside an inference context. Four table snapshots describe three adjacent transitions.

The supplied sequence is:

Forward

Requested row

Changed stored rows

What the transition establishes

First

0

[0]

The first over-limit lookup changes row 0

Second

1

[1]

A different untouched over-limit row can still change

Third

0

[]

Repeating the earlier lookup produces no further detected value change

The third call is easy to overinterpret when viewed alone. By then, row 0 has already experienced the first transition. Its later stability says something about the instance after that history. It cannot establish that the original row was preserved or that the first request complied with an immutable policy.

The second call supplies the coverage counterexample. The first row-0 lookup leaves row 1 in its original over-limit state. Requesting row 1 exposes another transition without changing the model configuration; the later repeat of row 0 is stable.

The supplied summary does not provide row 1’s exact post-call decimal pair. The ideal arithmetic target is [0, 1], but an ideal target must not be printed as an observed exact transcript. The executable retains the states so a real rerun can supply that numerical detail.

An untouched row is not an already-qualified row

Row 1 has a known initial norm of 2 and remains [0, 2] after the first lookup. Those two facts explain why its lack of change is not positive evidence of readiness for every future request. It has not yet faced the operation whose state effect is under review.

Record order along with the set of indices. A union such as “rows 0 and 1 tested” loses the distinction between the original table and the table after an earlier lookup. State histories require adjacent comparisons and an explicit initial boundary.

E8 is specifically a state-sequence diagnostic. It records and asserts changed-row identities but does not save or validate its returned outputs. Before using such a sequence for a broader release decision, add any required output checks in a separately identified qualification harness. Do not retroactively attribute those checks to the unchanged fixture.

Keep E8’s sequential evidence separate from the fresh-case comparisons. E5 and E6 begin from independently reconstructed original tables, while each E8 call inherits the preceding call’s state. Combining those records without that distinction can create a misleading chronology.

For the immutable policy, inspect every adjacent transition and compare the final table with the original baseline. A quiet final transition cannot erase an earlier violation. For a mutable policy, the same history documents when each authorized row transformation occurred.

Compare disabling max_norm with approving a new prepared table

E1 and E9 both disable runtime renormalization, but they start from different tables. Their unchanged-state results therefore support different candidate policies. Treating them as interchangeable fixes would erase the numerical decision the model owner must make.

E1 uses the original authored table with max_norm=None, evaluation mode and inference mode. Its row-0 lookup returns [3, 4] and changes no stored row in the supplied preflight. This isolates the effect of removing the runtime cap while preserving the original input values.

That output differs substantially from E5’s approximately [0.599999988, 0.799999984]. If the model’s approved computation expects capped embeddings, simply disabling the cap changes its behavior. A passing storage-invariance check cannot authorize that output change.

E9 begins from an independently prepared table:

Row

Prepared initial values

Supplied E9 output

0

[0.6, 0.8]

[0.6, 0.8]

1

[0, 1]

[0, 1]

2

[0.3, 0.4]

[0.3, 0.4]

The case then uses freeze=True, evaluation mode, inference mode and max_norm=None, requesting all three rows. No stored row changes. The lookup is checked against the prepared reference, and the original W remains separate.

Preparation is an intentional change to the initial model state. E9’s comment describes an approval requirement; running the script does not obtain that approval. The owner must review the preparation rule, the resulting artifact and the model-quality implications before using it as a replacement.

Even in this tiny example, E9’s row-0 output is not numerically identical to the supplied capped-forward observation. Their numerical difference fits the chosen tolerance, although the harness checks each case against its independent reference rather than asserting cross-case equivalence. It does not establish equivalence of every downstream computation or every trained model.

Choose the candidate according to its required computation. If original uncapped values are approved, E1 demonstrates the relevant local storage behavior. If a prepared table is proposed, E9 demonstrates a separate lookup policy. Neither result permits editing a trained table merely to turn a failing state audit green.

Write the proposed preparation as a transformation of an identified source state. In this example it divides over-limit rows by their authored norms and leaves the in-limit row alone. Review the resulting coordinates directly instead of accepting only the description “unit-normalized.”

Then retain a separate identity for the prepared candidate. That prevents an unchanged E9 table from being reported as proof that the original E1 table survived preparation. Both tables can remain stable during their respective lookups while representing different approved or unapproved model states.

Rebuild a suspect instance from an approved artifact

Once a candidate has violated an immutable-table contract, preserve the evidence and stop treating its current table as the approved starting state. Rebuilding is a recovery action based on an independently trusted artifact. It is not an attempt to infer historical values from the table that remains.

For the synthetic fixture, the original values are available as W. A real release needs an identified approved source artifact and configuration. If those are unavailable, a normalized row and its current norm cannot establish the original magnitude or all earlier coordinates. Guessing would create a new model state without a defensible reference.

A compact recovery procedure is:

  1. Retain the source identity, request sequence, runtime record and before-and-after ledger.

  2. Remove the mutated candidate from consideration for the immutable release.

  3. Identify the approved table and resolve whether its serving configuration should permit runtime renormalization.

  4. Construct a fresh instance from that approved combination.

  5. Inspect its initial values before any forward, then repeat the scoped state and output qualification.

Changing max_norm to None after the first mutation cannot recover the earlier [3, 4] row. It only changes how subsequent lookups are configured. Likewise, taking a new baseline at that moment records the altered state faithfully without showing that it is approved.

The separate topic of selected-checkpoint state preservation concerns retaining the intended historical artifact. Here the immediate obligation is to use a known approved artifact as the reconstruction reference. There is no checkpoint-selection experiment in the causal path.

Do not declare a numerical model change to be a configuration-only fix

Two proposed repairs deserve explicit owner review. Removing the cap from an original table can change returned vectors, as E1 versus E5 demonstrates. Replacing original values with a normalized table changes the initial state, as E9 demonstrates. A small code edit can therefore represent a meaningful model change.

Require separate model-quality evaluation appropriate to the real model before approving either numerical change. This three-row laboratory establishes neither predictive usefulness nor universal equivalence. Its role is to make the competing state policies and local output consequences visible.

A rebuilt instance must solve the policy conflict, not merely restart it. Reconstructing the original over-limit table with the same active cap will reproduce the same disallowed transition when the relevant row is reached.

Preserve the original diagnostic record after reconstruction. The new instance needs a new before-table and qualification result; overwriting the old record would hide why recovery was necessary and which approved configuration changed the outcome.

Record a compact state-and-output evidence pack

An evidence pack should let another reviewer reconstruct the policy decision without guessing what “passed” meant. Keep the diagnostic result, the candidate’s admissibility and the owner’s approval as separate fields. They answer whether the experiment behaved as expected, whether the candidate met its contract and who authorized that contract.

The minimum record for this laboratory is compact:

Evidence item

Required content

Policy

Immutable table or explicitly authorized renormalization, with owner and scope

Identity

Original or prepared table, dimensions, dtype, initial values and configuration

Execution

Python and PyTorch build, source digest, CPU eager path and thread setting

Request

Case ID, requested row IDs and sequence position

State

Complete before/after values and exact changed-row IDs

Output

Independent expected values, observed values and explicit tolerances

Conditions

Module mode, parameter flag and context flags

Disposition

Decision label, reason and any required reconstruction or rerun

For this six-number table, retaining all values is simpler and more inspectable than reporting only a norm summary. A row can have an acceptable norm while differing from the approved values. The approved table identity must therefore encompass its contents, not just its shape or the statement “normalized.”

Keep the script digest distinct from the table identity. The printed SHA-256 identifies source bytes. It does not independently identify a deployed model artifact, record model-owner approval or prove that a reported run actually used a particular external table. The fixture avoids that last ambiguity by authoring its values directly in source.

Preserve the full JSON for an executed run. A screenshot of PASS_9_CASES loses the changed-row evidence needed to reject intentionally mutable candidates under the negative contract. Similarly, a shortened output that rounds every near-unit value to one decimal place conceals the numerical detail behind the tolerance decision.

Record evidence gaps accurately. E1–E7 and E9 carry output and flag fields from run(). E8 carries the sequence and table states; its configuration is visible in source. If the release record requires per-call sequential outputs, collect them with an explicitly revised harness instead of filling missing fields from expectations.

This pack belongs within notebook-to-production model delivery. Attach the narrow finding to the wider release identity and handoff. The embedding audit contributes a specific state-policy decision; the rest of model qualification still needs its own evidence and responsible owner.

A reviewer should be able to reconcile three items without running the candidate: the authored starting values, the expected changed-row set and the reported observations. For E6, that means recognizing row 0’s transformation while verifying that rows 1 and 2 retain their initial values. For E7, it means seeing both the unchanged requested row and the remaining over-limit controls.

Label a supplied preflight, a newly executed run and an unexecuted expectation explicitly. Their tables may have the same layout, but they carry different evidence provenance. Retain that distinction when moving the result into another review document.

Bound the claim to the execution path actually tested

The supplied observations concern a built-in embedding on CPU, using eager execution, float64 values, integer row indices, norm type 2 and the exact authored three-row table. The driver uses padding_idx=None, scale_grad_by_freq=False and sparse=False. It sets one intra-op thread and issues its lookups sequentially.

Those boundaries matter because a release claim must identify the path that produced its evidence. This laboratory does not establish CUDA, sparse-gradient, compiled, exported, distributed, padding, mixed-precision or concurrent-serving behavior. It also does not inspect every other parameter, buffer or application object in a larger service.

Rerun when the installed build, interpreter, dtype, device, table contents, cap, norm type, execution path or relevant request coverage changes. A source edit changes the experiment’s identity even if its filename remains embedding_probe.py. New settings also require checking that the independent oracle still describes the intended computation.

An unexpected result is a reason to compare the recorded configuration, the primary API contract and a fresh scoped experiment. Do not silently adjust expected_changed to match whatever happened. That would remove the independent expectation and make a diagnostic failure look like successful qualification.

Separate exact state equality from tolerant output comparison

Changed-row detection uses elementwise numeric inequality without a tolerance. For these finite fixture values, any stored coordinate difference at the measurement boundaries marks its row as changed. A tiny difference would still violate the default value-invariance requirement.

That is different from the output comparison, which uses rtol=1e-6 and atol=1e-12. The supplied row-0 output differs from the ideal [0.6, 0.8] by approximately 1.2e-8 and 1.6e-8. Those output discrepancies fit the declared tolerance. They do not cancel the much larger stored-state transition from [3, 4].

The comparison is exact numerical equality, not a bytewise identity test or a trace of every memory write. No changed rows means no stored-value differences were detected between the snapshots. A same-value store or a change restored before measurement would require other instrumentation to observe.

Accordingly, pair measurements with review of the configured operation and the declared scope. Removing this known runtime renormalization mechanism and observing stable values supports the tested candidate’s qualification; it does not prove universal immutability throughout an arbitrary serving process. Keep stronger claims outside the evidence boundary.

The limited claim is still actionable. One prohibited observed change is sufficient to reject that candidate under the negative contract. Establishing acceptance requires the additional identity, output and coverage checks appropriate to the declared request scope.

Choose accept, hold, rebuild or rerun with the model owner

Apply the decision to the declared policy, not to the presence of a green diagnostic marker. E5 and E6 demonstrate behavior that can be valid under one policy and prohibited under another. The owner must settle that distinction before the candidate is admitted.

Decision

Evidence condition

Required action

ACCEPT_DECLARED_MUTATION

Owner explicitly permits the observed renormalization; identity, outputs, affected rows and scope match that policy

Retain the authorization and evidence for that bounded mutable behavior

HOLD_IMMUTABLE_CONTRACT

A protected table changes, or evidence cannot establish the required immutable boundary

Keep the candidate out of the immutable release until the conflict is resolved

REBUILD_APPROVED_INSTANCE

The candidate has already mutated and an approved source state and configuration are available

Preserve diagnostics, reconstruct independently and qualify the new instance

RERUN_SCOPE_CHANGE

Runtime, table, configuration, inputs, oracle or execution assumptions differ from those qualified

Establish the new scope and rerun with an appropriate independent oracle

An unchanged candidate with correct outputs and a defined identity may satisfy the scoped immutable gate. That result does not require labeling it ACCEPT_DECLARED_MUTATION: that label is reserved for an explicit permission to mutate.

The dispositions can also form a sequence. A prohibited difference produces a hold; approved reconstruction provides the recovery action; a changed execution scope requires new evidence before reconsideration. Rebuilding alone does not erase the original finding or authorize the replacement.

When the cap is an intentional part of the approved computation, the owner must resolve the incompatibility with a blanket no-mutation requirement. That may mean approving a bounded mutable policy or reviewing a different candidate. The operator should not make that numerical choice simply because one configuration produces fewer failed checks.

If permission is unclear, retain the immutable default. A successful demonstration of documented behavior is useful evidence for the discussion, but it cannot substitute for an authorization that has not been given.

Scope also prevents unnecessary rejection: an explicitly restricted request domain is different from an unrestricted vocabulary. State that restriction in the contract and qualify it accordingly; do not infer it merely because the laboratory happened to request only one row.

E7 cannot clear a hold for requests that reach untested over-limit rows. E8’s last stable lookup cannot retroactively approve its first two transitions. E9 cannot supply its own model-owner or model-quality approval.

The connection to broader machine-learning project pitfalls is ownership of an explicit requirement. Resolve which state behavior is acceptable and who can authorize a change. The fixture then informs that decision with controlled evidence, without claiming to establish the quality or success of the wider model project.

Develop wider AI-system qualification skills

This laboratory develops a specific engineering habit: identify the operation that can change state, place observations around it and test the model owner’s requirement directly. A familiar inference label is useful context, but the row ledger supplies the evidence about stored values.

The three rows make that habit concrete. An accessed over-limit row exposes the transition. An untouched over-limit row preserves a future challenge. An in-limit lookup demonstrates why a successful request can leave that challenge unresolved. The sequential case then shows why the history of the instance belongs in the qualification record.

Carry those distinctions into release reviews. Explain the initial artifact, the operation permitted to transform it, the requests actually exercised and the evidence required to accept the result. When preparation changes numerical values, keep its approval separate from whether later requests leave those values stable.

The practical outcome is a reproducible decision with a visible boundary. Authorized renormalization can be accepted as declared behavior. A prohibited change requires a hold and an approved recovery path. A different execution scope requires new evidence. None of those judgments needs an invented failure story or a claim that the API is defective.

For structured work on AI systems, model optimization and practical projects, review the AI Engineering Program; confirm the tools and prerequisites for your intended cohort before enrolling.