Skip to main content
Nori predicts in context, so your table is input: every call reads all of X_train and keeps a per-layer key/value cache over those rows. That cache — not the model itself — is what fills a GPU on a big table. Nori handles it by walking a ladder of fallbacks, keeping the cache at full precision while it fits and stepping down only when it must. memory_policy lets you decide which steps it may take, and tells you afterwards which one it used. memory_policy governs how the cache holds your context. Above a row-count threshold, large_context_policy governs which context rows a call actually uses — routing across one or more Nori calls instead of trimming randomly or paying the VRAM cost of the whole table. See context routing below. The same fields work locally and over the hosted API.
Locally this needs synthefy-nori 0.13.0 or newer; see Installation.

Quickstart

Omit memory_policy entirely and nothing changes — the defaults are what almost every call should use. They are: cache at full precision (bf16), let it drop to int8 only if that is what keeps it on the GPU, spend at most 40% of VRAM on it, offload to host RAM rather than give up, and shrink the context only as a last resort. In our tests, changing chunking or batching introduced only small rounding differences, though predictions were not always bit-for-bit identical. Reach for a policy when you want a different trade. A preset covers the common three:

Which lever saves the most memory

Ranked by how much GPU memory each one frees, most first. Figures are from a 400-row × 8-feature context, whose full-precision cache is 0.0122 GiB — yours scales with rows and features, but the ratios hold. Note that int8 is ~1.9× and not 4×: the saving is against bf16, not fp32, and the per-row scales cost a little back.

Row chunking

Use context_row_chunk when a call dies building the cache rather than holding it — the projection over all N rows at once is the spike, and this processes N a slice at a time. It is the lever for “it OOMs on fit”, where a smaller budget would not help.
Nori also reaches for this automatically after an out-of-memory retry, so you rarely have to set it — see the warning in Good to know about its numerics.

Capping the budget on a shared GPU

Budgets are fractions of your hardware by default, so one setting travels between cards. Use the absolute form when a hard ceiling is the actual requirement, such as a GPU you share with another process:

Refusing a shortened context

Shrinking the context is the last resort on the ladder: Nori reaches for it only when every other fallback still leaves the request too large, and it never fails outright if there is any way to serve it — see Quickstart. Make that an error instead when a silently shortened context would invalidate your results:
allow_subsample=False raises before any prediction runs, rather than returning one computed on fewer rows than you asked for. Either way, check memory_report_["dropped_context_rows"].

How it works

One predict reads the context once and saves a key/value vector per layer, per feature group, per context row. Query rows then attend to that cache instead of re-reading the table. The cache is what grows with your data:
Query rows are processed in batches, and the cache is what makes the second batch cheap. That is also why it is only built when there is a second batch — a query set small enough to fit one pass has nothing to reuse it across, and reports no_cache. That is normal, not a degradation. When the cache does not fit, Nori takes the cheapest step that works: Only the int8 rungs quantize the cache. Ordinary BF16 offloading moves bytes unchanged: its reference is the same resident cached computation with the same context, query chunks, projection chunks, ensemble batching, model, and numerical backend. The policy can change batching eligibility, so selecting offload_bf16 alone does not guarantee identical end-to-end predictions. Changing chunk sizes, using streaming, or switching between cached and uncached execution can change floating-point rounding even without quantization. Subsampling changes the context itself. The exact preset disables cache quantization. It does not guarantee identical predictions across execution paths or prevent subsampling. Use allow_subsample=False separately when every context row must be retained.

Reading the report

memory_report_ locally, last_memory_report on the client, memory_report in the raw response. It carries the resolved policy plus what was decided: The last two are added by the server when it echoes the report, so they are present over the API and absent from memory_report_ locally. dropped_context_rows is the one number worth watching: it is the only accuracy loss here that is not a rounding-level effect. Set allow_subsample=False to make that case an error instead.

Reaching the cached path over the API

Coming soon. The cache is built only when your query set spans more than one batch, and today that threshold is more query rows than a single hosted request can carry — so a hosted call reports no_cache however much data you send. We are raising the hosted request limit; once it lifts, a normal-sized request reaches the cached path on its own, with nothing extra to set.

Choosing context rows above a threshold

memory_policy decides how the cache holds your context; large_context_policy decides which rows go into it once X_train exceeds large_context_threshold (50,000 rows by default). Above that threshold, a policy routes the request across one or more Nori calls instead of the cache silently trimming a random subset to fit. Off by defaultlarge_context_policy=None keeps behavior byte-for-byte identical to today. The same built-in policy specs work in local, hosted, and SageMaker modes:
Clusters the query rows into groups (8 by default) and gives each group its own local context pool, one Nori call per group. It is the only policy with full coverage across a broad benchmark sweep — best on nearly half the tables tested, and it never regressed below a single shared window.

Choosing between them

Here are some commonly used built-in policies: Hosted and SageMaker support built-ins from the installed Nori version. The complete current list and configuration options live in src/synthefy_nori/inference/policies.py in the Synthefy Nori repository. Custom Python policies are local-only.

Reading the report

large_context_report_ locally, last_large_context_report on the client, large_context_report in the raw response — present whenever a policy was requested, even below the threshold, so a deployment silently ignoring the field is distinguishable from an ordinary small-table request.

Configuration

Budgets are fractions of your hardware, so one setting travels from a laptop GPU to an H200. The *_absolute_gb overrides exist for a shared GPU, where a hard ceiling is the requirement rather than a share.Over the hosted API the host-RAM budgets are capped to what the container survives, and a cap is reported in clamped rather than applied silently.
large_context_cache_entries (NoriRegressor only, default 1) keeps more than one policy’s context cache resident between calls on the same estimator. It is not part of the shared client/hosted contract: hosted requests are one-shot, so there is nothing to retain between them.

Good to know

Incoherent settings fail loudly. Asking for a cache-only field alongside cache=False is rejected rather than ignored, because a policy spelled correctly that silently does nothing is worse than an error. Settings that work but probably are not what you meant are honoured and explained in notes.
The report is not a policy. Feeding a memory_report back in as memory_policy is rejected: those are decided outputs, and reusing them as configuration would skip the coherence checks.
Row chunking is mathematically equivalent, but not guaranteed bit-identical. Changing the projection shape can change floating-point accumulation order. A single chunk uses the unchunked projection shape; smaller chunks may return slightly different predictions. Row chunking can also be reached automatically after an out-of-memory retry. The size of any difference depends on the model, inputs, precision, and backend; it is not a fixed error bound.
Not on Thinking models. A Nori Thinking deployment rejects both memory_policy and large_context_policy with a 400. It predicts through a test-time-compute wrapper with no separate fit/predict step for a policy to attach to, and manages its own inference memory — so a policy passed in from outside would not reach the run that produces the answer, and accepting it silently would be worse than refusing it.
Where a cache cannot help. Preprocessing (polynomial features and the adaptive SVD) runs before any of this and has its own memory cost, which memory_policy does not govern. A table that fails there fails regardless of the policy.
large_context_policy is point-output only. output_type="mean" and "median" are supported; "quantiles" and "full" fail before inference, because a routed policy combines point predictions from several Nori calls and has no single combined predictive distribution.
Hosted requests fail closed on a stale deployment. The response always carries large_context_report when a policy was requested, even below the threshold. If a deployment omits it or reports a different policy, threshold, or seed, the client raises rather than silently returning an ordinary, unrouted prediction that looks valid.
One-shot everywhere. Every call — local, hosted, or SageMaker — supplies and fits X_train again; there is no hidden upload-once/query-many cache across requests. reused_train_state in the report reflects reuse within one client-owned NoriRegressor instance in local mode only, and is always false over the hosted API.

Next steps

Hosted client

Auth, modes, and the request contract for calling Nori over the API.

Missing values

Pass NaNs straight through — what the model does with them.