We run LLM inference on-chain (llama_cpp_canister), so our canisters allocate multi-GiB heaps. After the -deterministic-tracker GuestOS variant rolled out, loading our Qwen3-1.7B model with a 16K context stopped working. We load the model from stable memory (a virtual file in C++ canisters) into working memory:
IC0522: Canister attempted to perform a large memory operation that used
4_057_019_087 instructions and exceeded the slice limit 2_000_000_000.
An existing, deployed canister stopped working.
We found that using a smaller context still works fine, so we have a workaround, though not ideal.
After some digging, and with help from @Lorimer, we found the root cause: b7225383e unified page charging at 5 000 instructions/OS page (heap writes were 1 000, heap reads 0). A single bulk memory operation can’t be sliced, so its whole cost lands in one slice, and DTS rejects it once it exceeds slice_limit + max_slice_limit = 4 B instructions. Expressed as a size:
max single memory op = 2 × MAX_INSTRUCTIONS_PER_SLICE / dirty_page_overhead × 4 KiB
before: 2 × 2B / 1000 × 4KiB = 15.26 GiB (heap reads were free → effectively unbounded)
after: 2 × 2B / 5000 × 4KiB = 3.05 GiB
ours: 4_057_019_087 / 5000 × 4KiB = 3.10 GiB → just over
We’re not arguing against the new cost model, but would like to point out that this particular size limit isn’t declared anywhere. It’s an emergent result of MAX_INSTRUCTIONS_PER_SLICE (a latency knob) and dirty_page_overhead (a cost knob), so it moves whenever either changes for unrelated reasons, and it isn’t obvious from either PR that a size limit is being affected at all.
Two small things that would help canisters like ours:
- An explicit, documented constant for it — ideally with a test pinning it — so that a change in either input surfaces the effect on maximum operation size at review time.
- An error that states the size: “attempted a 3.10 GiB memory operation, limit is 3.05 GiB”. The current message gives instruction counts, and the conversion to bytes isn’t obvious unless you already know the page cost.
And a related question, if the limit does become explicit: would it be possible to raise it a little? At 3.05 GiB it now sits just below the ~6 GiB heap the platform already supports, so a canister can’t initialise its whole heap in a single operation. We don’t have a strong view on the mechanism — we appreciate MAX_INSTRUCTIONS_PER_SLICE is deliberately tied to block time — but a limit at least as large as the maximum supported heap would feel natural, and would let us go back to the 16K-context configuration.
We’ll try to chunk our allocations on our side regardless although it is not going to be easy to implement.