CONTEMPORARY LABORATORY · ORIGINAL EVIDENCE
One AI Task Contract: Local and Hosted Boundary in a Reference Lab
This notebook preserves the later investigation, its original measured results, exact versions and execution dates. It is separate from the historical subject scope of the reconstructed article.
Return to the historical explanation → · Current reconstruction sources
Prepared on 12 November 2026. This is a current reference implementation, not a retrospective claim that the demonstrated application existed earlier.
Moving inference from a browser to a server looks like a deployment decision until the two implementations disagree. A different tokenizer, a forgotten normalization step or a broader candidate set can change the task while leaving the interface almost identical. The page still accepts a question and returns three documents. The architecture diagram still contains an arrow labelled inference. Yet the comparison no longer tells us what we think it tells us.
My position is that an execution boundary should be movable only after the task boundary becomes explicit. The contract includes input preparation, model identity, output interpretation, candidate policy and failure semantics. A matching method signature is insufficient. If a local implementation and an HTTP implementation rank different authorized material because one silently truncated the question, we have changed behavior, not simply placement.
The accompanying application executes both paths. Blazor WebAssembly hosts the interface. JavaScript runs ONNX Runtime Web in the browser. An ASP.NET endpoint runs the same ONNX model through the .NET runtime. The endpoint is genuinely called over HTTP, but it listens on loopback on the same machine. Nothing in this article establishes Azure latency, internet reliability, cloud economics or production multitenancy. The value of the lab is narrower and more useful: we can inspect the precise work that crosses a process boundary and test whether the result contract survives.
First define what is allowed to move
01 / See the idea
Same task, two execution boundaries
The browser and .NET endpoint execute the same query contract with identical token input and frozen document vectors. Only the HTTP path crosses the loopback process boundary.
Interpretation and evidence
- Freeze the task before placement.
- Separate preprocessing from inference.
- Loopback is not cloud deployment.
The task is retrieval over a small synthetic corpus inherited unchanged from the M08 laboratory. Given a question, produce a normalized query vector, compare it with frozen document vectors and return the three highest ranked allowed documents. There is no generated answer, chat history, tool execution or model judgement of truth. A retrieved document is a candidate, not a certified answer.
The browser and the endpoint share a model artifact, a document-vector artifact, a scope rule and a version identifier. The version identifier is m08-frozen-v1. In this fixture, blue and public documents are candidates. Red documents are excluded from ranking. That rule is applied independently in both paths. Ties are resolved by identifier; the fixture identifiers use a restricted alphabet. The current client uses JavaScript locale comparison while the server uses ordinal comparison, so unrestricted identifiers would require a deliberately identical comparator before claiming universal tie parity.
The movable part is query-model execution plus ranking. The browser performs both locally. The HTTP endpoint receives prepared token IDs, performs its own ONNX execution and ranking, then returns its vector and selected identifiers. Returning the vector is a diagnostic choice that makes comparison inspectable. A production response could omit that array, but then another verification mechanism would have to establish parity.
Notice the deliberate preprocessing boundary. This is not a comparison of two independent raw-text tokenizers. The browser prepares tokens for both paths. That reduces one source of ambiguity and makes a precise question possible: given identical IDs and identical artifacts, do the two execution paths agree closely enough for this retrieval task? Independent server tokenization would be an additional contract, not a free consequence of this result.
There is also a security boundary that the demonstration does not satisfy. All corpus fixtures are synthetic and downloaded to the browser. Client filtering therefore does not provide confidentiality. The endpoint accepts only the fixed fixture tenant, but it has no identity provider or authorization middleware. A caller-supplied tenant string is never a substitute for authenticated scope. These limitations are architectural facts, not small print to remove when the screenshot looks convincing.
Freeze the artifacts before comparing runtimes
02 / See the idea
The contract has more than a model name
Model identity, preprocessing, pooling and candidate policy must remain visible together. A matching API shape alone does not establish equivalent behavior.
Interpretation and evidence
- Record artifact hashes.
- Declare the pooling rule.
- Test candidate scope separately.
The model is sentence-transformers/all-MiniLM-L6-v2, with repository revision 1110a243fdf4706b3f48f1d95db1a4f5529b4d41. The model manifest carries the downloaded artifact hash, license and tokenizer files. The ONNX file is 90,405,214 bytes and has SHA256 6fd5d72fe4589f189f8ebc006442dbb529bb7ce38f8082112682524616046452. The source package excludes the weights and supplies a pinned download procedure. The model repository documents the embedding model and its Apache 2.0 license.
The .NET endpoint checks the model bytes against the saved hash before opening an inference session. This catches an accidental replacement in the reproduction directory. The browser-side check is different: it verifies tokenizer outputs against forty frozen encodings and checks an actual browser-produced vector against a reference vector. The browser does not independently hash the downloaded model in this implementation. Do not collapse these two checks into a claim that every layer performs identical artifact attestation.
The application is pinned to SDK 10.0.400 and .NET runtime 10.0.15. The server package is Microsoft.ML.OnnxRuntime 1.22.1; the browser package is onnxruntime-web 1.22.0. Those versions are recorded because execution-provider differences can matter even when the ONNX graph is unchanged. We are testing compatibility between these specific implementations, not asserting binary equivalence between all future releases.
Both paths use one inference thread. The server sets intra-operation and inter-operation thread counts to one and selects sequential execution. The browser sets ort.env.wasm.numThreads = 1 before creating its session. These are explicit runtime controls described in the C# session options and Web environment documentation. One thread makes the resource boundary easier to inspect; it does not make the timings representative of a tuned deployment.
The frozen document vectors matter just as much as the graph. Recomputing documents with another tokenizer or pooling rule while preserving the same filename would invalidate the comparison. The lab therefore reads the accepted M08 reference artifact unchanged. This article adds a second execution boundary for queries. It does not quietly replace the corpus or manufacture a new retrieval benchmark.
Make tokenization and pooling visible
03 / Watch the mechanism
The query changes representation
Static view. Play to see how the mechanism unfolds.
An actual query becomes token IDs, token representations, a normalized vector and ranked candidates. The highlighted stage moves through the implemented pipeline, with all information visible throughout.
Interpretation and evidence
- Tokens are the shared input.
- Mean pooling precedes normalization.
- Ranking uses frozen document vectors.
The client contains a bounded English WordPiece adapter. It normalizes text, splits punctuation, looks up vocabulary pieces, inserts the classification and separator IDs, and refuses input beyond the declared 256-token limit. Forty fixture encodings match the saved reference exactly. This is evidence for those inputs and the documented adapter boundary, not proof of complete compatibility for every Unicode string or every language.
For the displayed question, “What is the maximum size of one uploaded file?”, the request contains the following token IDs:
{
"ids": [101,2054,2003,1996,4555,2946,1997,2028,21345,5371,1029,102],
"version": "m08-frozen-v1",
"tenant": "blue"
}
The test captures the real HTTP request body and compares its IDs with the browser tokenizer output. It does not merely compare two hard-coded arrays in a unit test. An incorrect serialization path would therefore be visible at the transport boundary. The endpoint separately refuses unknown versions, a nonfixture tenant and token sequences that violate its limited shape and range checks.
Each path creates three tensors: input IDs, an all-one attention mask for the unpadded sequence and all-zero token-type IDs. The graph produces token representations with 384 components per token. The implementation averages those representations across the actual sequence and normalizes the resulting vector to unit length. Because this request is unpadded, averaging all positions matches the chosen mask convention. Introducing batching and padding would require mask-aware pooling rather than retaining the current loop unchanged.
The relevant server calculation is short enough to inspect:
int n = request.Ids.Length;
var v = new double[384];
for (int i = 0; i < n; i++)
for (int d = 0; d < 384; d++)
v[d] += tokenOutput[0, i, d] / (double)n;
double norm = Math.Sqrt(v.Sum(x => x * x));
for (int d = 0; d < 384; d++)
v[d] /= norm;
The complete source uses InferenceSession.Run, checks output dimensions and disposes the returned output collection. The browser similarly disposes temporary input and output tensors after extracting its vector. These ownership choices prevent the demonstration from leaving every temporary tensor alive deliberately. They do not establish zero allocation, a stable long-running memory plateau or complete browser-session disposal under repeated page navigation. Those are separate experiments.
A real endpoint needs a narrower promise than a cloud label
The ASP.NET process binds an ephemeral loopback port. It serves local static assets and exposes /api/search. The browser sends JSON to that route and parses the JSON response. The server independently executes the ONNX graph. This is a genuine process and HTTP boundary, even though both sides share a computer.
Calling that endpoint hosted is accurate only with its local qualifier. It is hosted by the reference server process. It has not been deployed to Azure, configured behind a gateway, subjected to WAN round trips, exercised through TLS termination or evaluated under concurrent remote clients. The benchmark cannot answer how a phone on a congested network compares with a cloud CPU. It can reveal serialization work, endpoint validation and cross-runtime result agreement in a controlled environment.
A local-only query path avoids sending that query to the inference endpoint once the assets are loaded. However, the page still obtains the model and document fixtures from a server. Local inference does not mean that no information ever crossed the network. It also does not make downloaded documents private from the person operating the browser. The architectural question is which data crosses which boundary and when, not whether the word local appears in the product description.
The HTTP path has a different disclosure profile. It sends token IDs derived from the query. Those IDs are not an anonymization guarantee. A vocabulary mapping can expose much of their content. A production policy should treat the request as query-bearing data unless a specific, reviewed transformation establishes something stronger. Logging raw token arrays merely because they look numeric would be an avoidable mistake.
For a hypothetical enterprise search product, these distinctions could determine placement before any latency measurement. If documents may not be distributed to the browser, this particular local-ranking design is unsuitable regardless of its speed. If disconnected operation is required, the HTTP-only path is unsuitable without a deliberate offline strategy. Neither requirement can be solved by a faster matrix multiplication.
Test agreement at several levels
The browser acceptance check starts from the visible application, loads the model, submits the query and reads the rendered comparison. The observed top three identifiers are b-storage, b-upload and b-backup for both paths. The maximum component difference between the two normalized query vectors is approximately 5.593 × 10^-8. That is below the lab's declared absolute tolerance of 10^-4.
| Check | Observed result | What it establishes |
|---|---|---|
| Frozen tokenizer encodings | 40 matched | Fixture preprocessing parity |
| Browser reference vector | Maximum error about 6.934 × 10^-8 | One browser inference agrees with the saved reference |
| Browser versus HTTP query vector | Maximum error about 5.593 × 10^-8 | Cross-runtime numerical agreement for the submitted question |
| Top three identifiers | Identical in both paths | This ranking survives the execution boundary |
| Wrong version, scope or token contract | HTTP 400 | The tested invalid requests are refused |
| Simulated endpoint failure | Explicit comparison failure | No silent fallback or stale result presentation |
These checks occupy different layers. Vector proximity does not guarantee ranking stability for every possible corpus, especially where two candidates have almost identical scores. Ranking equality on one query does not prove identical vectors. A correct vector does not prove that the allowed candidate set is correct. The layered test exists because any single success can conceal failure in another dimension.
The threshold is a test policy, not a universal scientific constant. A sensible next step for a new deployment would examine score margins and ranking agreement across representative queries, including deliberate near ties. It might also use both absolute and relative tolerances where appropriate. The current result supports the bounded comparison shown here. It should not be promoted into a claim of universal runtime interchangeability.

The screenshot is evidence of the rendered state, while the JSON record supplies request bodies, assertions and cleanup status. Neither replaces the other. A screenshot alone would not establish that the endpoint ran a model; a backend console alone would not establish that the user-facing page displayed the current result.
Failure policy belongs in the task contract
05 / See the idea
Refuse rather than silently substitute
The endpoint refuses invalid version, scope or token contracts. A simulated503 causes an explicit UI failure, without presenting an old comparison as current.
Interpretation and evidence
- Version mismatch is refused.
- Fixture scope is not authentication.
- Fallback must be a named policy.
The test deliberately returns HTTP 503 for one comparison. The page clears the previous comparison before starting, then displays an explicit failure. It does not automatically replace the failed remote result with the local result. That choice may appear less convenient, but it keeps the experiment honest: a comparison that silently substitutes one side cannot prove agreement between two sides.
In a product, fallback can be valuable. The important requirement is that fallback be a named policy with observable consequences. The fallback may use another model, another corpus snapshot, fewer documents or a narrower authorization capability. If those differences matter to the user, the UI should say what changed. A generic success banner can conceal a different task being performed under the original task's name.
Version refusal is similarly useful friction. The endpoint accepts only its known frozen contract. It does not attempt to infer what an unknown caller version meant. For a production migration, accepting several versions might be reasonable, but each accepted version would need its own model, preprocessing and response expectations. A version string that is accepted regardless of behavior contributes little to reproducibility.
The scope refusal in this fixture is intentionally simple. It proves that the endpoint does not blindly accept the tested red-tenant request. It does not prove authentication, resistance to every malformed payload or a complete authorization policy. The browser already holds synthetic data from multiple fixture scopes, which is acceptable for teaching but incompatible with a confidentiality claim.
There are other missing hardening steps worth naming precisely: request-body limits beyond normal host defaults, complete null and malformed-payload tests, rate control, authenticated identity, inference admission control, graceful browser-session release, and operational logging that avoids query disclosure. These are not necessary to demonstrate the selected parity contract. They would be necessary topics before turning this reference server into an externally reachable service.
Compare intervals without pretending they are equivalent
The measurement mode is separate from the correctness mode. It records model initialization after the page has bootstrapped, then one successful local query interval and one successful HTTP query interval. The local interval includes query embedding and ranking in the browser. The HTTP interval includes request serialization, loopback transport, server validation, server inference, server ranking and response parsing. Both are real observations, but they are not measurements of the same inner operation.
This difference is not a flaw to hide. It is the point of a boundary experiment. An end-user action pays for more than model execution. At the same time, a loopback transport cost is not an internet transport cost, and one first query is not a percentile. The lab's measurement record should be read with its included and excluded stages visible. Any timing comparison without those labels invites a false conclusion.
The model file is also a placement cost. The local path needs its browser runtime assets and model bytes delivered before it can answer. A warm local query can therefore look attractive while first use remains substantial. Conversely, the server can keep a model session alive across requests, but that introduces a service lifecycle, shared capacity and failure domain that this single-client experiment does not size.
The correct question is not which side wins a tiny timing table. It is whether a candidate architecture meets the workload's privacy, availability, device, freshness and operational constraints, and then what its measured costs are under those constraints. Performance is meaningful after the contract is fixed. Otherwise a supposedly faster implementation may simply have stopped doing part of the work.
Read the recorded pair as a boundary observation
04 / See the idea
One measured pair, different intervals
The accepted run observed17.8 ms locally and53.3 ms through HTTP. The HTTP interval includes serialization and transport; one pair does not establish cloud speed or a latency distribution.
Interpretation and evidence
- One machine and one pair.
- HTTP includes additional work.
- Initialization801.7 ms is separate.
The exclusive measurement run recorded initialization at 801.7 milliseconds, the local query interval at 17.8 milliseconds and the HTTP query interval at 53.3 milliseconds. There was one successful measured comparison, on one developer machine, in one browser session. The JSON record preserves the underlying values rather than presenting rounded figures as repeatability estimates.
| Recorded interval | Observed milliseconds | Included boundary |
|---|---|---|
| Browser initialization | 801.7 | Local asset loading, session creation and the reference inference after page bootstrap |
| Local query | 17.8 | Browser query inference and ranking |
| HTTP query | 53.3 | JSON request, loopback transport, server inference and ranking, response parsing |
These intervals should not be turned into a claim that browser inference is three times faster than server inference. The server interval contains work absent from the local interval. The local model has also already executed a reference inference during initialization, while the endpoint's first query can include first-use effects specific to its runtime. The order is fixed, local first and HTTP second. There is no randomized order, warm-up study or distribution of repetitions.
The result nevertheless answers a concrete question: the actual application performed the same tested retrieval task through both paths, and the measured end-to-end intervals for this single comparison differed. That observation can motivate a more careful deployment experiment. It cannot replace one. In particular, adding remote network latency arithmetically to 53.3 milliseconds would not establish a realistic cloud result, because hardware, concurrency, connection reuse and service initialization could also change.
Initialization belongs in the same decision even though it is not included in either query interval. A person using the feature once pays a different cost profile from someone who submits many queries in one session. The current record does not establish model caching across visits or browser eviction behavior. A product decision based only on the warm-looking local number would therefore omit a large part of the first interaction.
Design the next parity failure before it surprises you
A useful follow-up matrix would vary one contract dimension at a time. Keep the graph fixed and change the tokenizer adapter to see whether the input checksum catches the difference. Keep tokens fixed and intentionally omit normalization to verify that the vector comparison fails. Keep vectors fixed and alter the candidate policy to ensure that ranking parity alone does not conceal a scope violation. These are proposed mutation checks, not additional executed results in this record.
The distinction between numerical and semantic failure is important. A small floating-point difference may be acceptable while a one-document authorization difference is not. A model upgrade may legitimately change rankings while still satisfying a new task contract, but then the version identifier must change and evaluation should be rerun against that new behavior. Compatibility should mean preservation of declared invariants, not preservation of every incidental byte.
Consider a hypothetical upgrade that improves average retrieval on a broader evaluation set but fails a contractual query about deletion authority. The aggregate score cannot decide whether that regression is acceptable. The task owner must specify critical cases and refusal rules. The local-versus-HTTP harness provides a place to run the same contract on both sides, but it does not supply the business importance of each query.
The endpoint's returned vector is especially useful during this stage because it localizes disagreement. If vectors agree but IDs differ, inspect candidate filtering, document artifacts and ordering. If token IDs agree but vectors differ substantially, inspect graph identity, pooling, runtime options and output selection. If IDs differ before inference, inspect preprocessing. This diagnostic sequence prevents a team from changing model parameters to compensate for an ordinary protocol defect.
Admission control would become a separate concern as soon as the endpoint served concurrent callers. Restricting ONNX to one inference thread does not restrict the number of HTTP requests allowed to enter inference. A production service would need an explicit concurrency and queue policy, cancellation semantics and overload response. The current single-browser run cannot establish those properties. It demonstrates that one request crosses the selected boundary correctly, not that the host has a safe capacity envelope.
The request also carries prepared IDs rather than a proof that a trusted tokenizer produced them. Range and special-token checks reject some malformed inputs, but a caller can still construct an arbitrary sequence that passes those checks. That is acceptable for this reference comparison. If an application requires a guarantee about text normalization or content policy, it must enforce that requirement at a trusted boundary instead of assuming the browser followed the advertised preprocessing path.
Reproduce the boundary, then decide what to extend
The source bundle includes the client, endpoint, lock files, artifact manifests and test harness. Build the client and server from their respective directories using the pinned SDK. Retrieve the pinned model separately, preserving the supplied licenses. The harness starts the backend with the client, model and ONNX Runtime Web asset directories, discovers its ephemeral loopback URL and opens one headless browser. Its finally path closes the browser and sends the server its shutdown signal.
The accepted check records both resources closed and server exit code zero. No externally hosted model is contacted. The browser harness blocks requests outside the selected loopback origin. This establishes the execution boundary for the run; it is not a claim that a copied application will remain safe after arbitrary changes to its scripts or static assets.
Three extensions would materially strengthen the experiment. First, add an independently implemented server tokenizer and compare raw-text inputs across a deliberately broader Unicode corpus. Second, introduce representative near-tie rankings and verify stability rather than only component tolerance. Third, replace the fixed fixture scope with authenticated authorization before using any confidential documents. Each extension has a different acceptance criterion and should remain separately visible.
A fourth extension would study real deployment placement. That would require an actual remote environment, identified hardware, network conditions, concurrency, cold and warm behavior, and failure tests. Nothing in the present record substitutes for those observations. The current endpoint is intentionally local because the immediate unresolved question was whether the task survived movement across a concrete execution boundary.
The broader lesson is practical. Before asking where an AI feature should run, write down what must remain true when it moves. A model name is not the whole contract. A response-shaped object is not the whole contract. Input preparation, candidate authority, artifact identity, output interpretation and visible failure are all part of the work. Once those are explicit, placement becomes an engineering decision that can be tested rather than a slogan that can only be defended.
Source code and execution evidence
Download the complete lab, source record and raw execution results. This package supports the specific execution claims in this article. It is not a production certification.