nnef: lazy weight loading, so a pruned model never reads what it discards - #2524
nnef: lazy weight loading, so a pruned model never reads what it discards#2524czoli1976 wants to merge 1 commit into
Conversation
Loading was all or nothing: every .dat was read in full before the graph was even parsed, so a caller that only wants part of a model — one shard of it, or a subgraph — still paid for the whole thing, and a model too big for the machine could not be opened at all to be cut down. The .dat header is a fixed 128 bytes and fully describes the tensor that follows, so read that alone and wire a LazyConst carrying the fact; materialize_lazy_consts then reads only the constants still in the graph. LazyConst is excluded from eager evaluation because a node with no inputs satisfies "all inputs are constant" vacuously. Lazy loading needs to seek, so it is offered for unpacked directories only; a .nnef.tgz is a gzip stream and keeps the existing eager path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@kali lazy w loading |
|
Ha. Ok, indeed, I can see the problem with TensorStorage. Frustrating. I'd like to avoid the load_lazy() pattern. I think we need a new method, not an offline knob, but I'd like to make it generic enough so it becomes a shared extension point. Same spirit as the Transforms. Maybe a load_with_options() with a JSON/RON serialized dictionary with a tract_nnef_lazy_weights: true to support your case ? WDYT ? Agree to add it to the exclusion list in PropConst. At least for now. The statefull() / stateless() flag system need some attention, it has been abused so much... and throwing errors (like TooEarly) is ugly. eval() should work, even if it is stupidly expensive (re-reading from FS), to allow evaluation of declutter-but-not-optimised graph in debug/audit context. For the materialization, I can see two options: a ModelTransform could actually do it, or it could be integrated in the codegen() and prepare phase. |
|
Reference: 2026-07-28 morning nightly run (0d old) · full report → run Speed — evaltime · prefill · decode no inference-speed regressions
|
| Δ | metric | device | main → PR |
|---|---|---|---|
| en_tdnn_pyt_15M load · pulse_120ms |
apple-m1-max |
90 ms → 125 ms | |
| en_tdnn_pyt_15M load+optimize · pulse_120ms |
apple-m1-max |
106 ms → 141 ms | |
| llama_3_2_3B_instruct_q40ef16_541 load · cuda |
jetson-orin-nx |
3.13 s → 3.3 s |
|
Could you think a bit about it and come up which one of the two sounds like the best option? |
|
I'm leaning towards codegen() time, so it's implicit and does not require extra attention from the user. Unless you hit a snag of course. |
|
ok, will give it a try, thank yoy
Il giorno dom 2 ago 2026 alle ore 07:55 Mathieu Poumeyrol <
***@***.***> ha scritto:
… *kali* left a comment (sonos/tract#2524)
<#2524 (comment)>
I'm leaning towards codegen() time, so it's implicit and does not require
extra attention from the user. Unless you hit a snag of course.
—
Reply to this email directly, view it on GitHub
<#2524?email_source=notifications&email_token=APL2Z6WKQBWMERO4JJ7QA4T5H3QWHA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMJVGYYDINRTHE42M4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KYZTPN52GK4S7MNWGSY3L#issuecomment-5156046399>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/APL2Z6QCKZAK37VHM3R7AAT5H3QWHAVCNFSNUABEKJSXA33TNF2G64TZHM4TSNJWGEZDCMZ3JFZXG5LFHM2DSOBSGQ3TIMZVHGQXMAQ>
.
Triage notifications, keep track of coding agent tasks and review pull
requests on the go with GitHub Mobile for iOS
<https://github.com/notifications/mobile/ios/APL2Z6TBR2DO35LWDVXUJAD5H3QWHA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMJVGYYDINRTHE42M4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KUZTPN52GK4S7NFXXG>
and Android
<https://github.com/notifications/mobile/android/APL2Z6RBLJPYYTHI54FJYVL5H3QWHA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMJVGYYDINRTHE42M4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2K4ZTPN52GK4S7MFXGI4TPNFSA>.
Download it today!
You are receiving this because you authored the thread.Message ID:
***@***.***>
--
Best Regards
Ckristian Zoli
Email: ***@***.***
|
|
@czoli1976 this one is yours to look at, now, right ? |
|
Back burner, will keep a note to revisit
Best Regards
Ckristian Zoli
Email: ***@***.***
…On Mon, 17 Aug 2026 at 09:20 Mathieu Poumeyrol ***@***.***> wrote:
*kali* left a comment (sonos/tract#2524)
<#2524 (comment)>
@czoli1976 <https://github.com/czoli1976> this one is yours to look at,
now, right ?
—
Reply to this email directly, view it on GitHub
<#2524?email_source=notifications&email_token=APL2Z6Q44LCZLE3NLA3NMGL5KK55NA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMZRGM2TQMZTGI3KM4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5313583326>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/APL2Z6UJJ3ZV4LDU77UPBFT5KK55NAVCNFSNUABEKJSXA33TNF2G64TZHM4TSNJWGEZDCMZ3JFZXG5LFHM2DSOBSGQ3TIMZVHGQXMAQ>
.
Triage notifications, keep track of coding agent tasks and review pull
requests on the go with GitHub Mobile for iOS
<https://github.com/notifications/mobile/ios/APL2Z6R45WGNA4J3Z32AR6D5KK55NA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMZRGM2TQMZTGI3KM4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJKTGN5XXIZLSL5UW64Y>
and Android
<https://github.com/notifications/mobile/android/APL2Z6UFXA2F3FE6ZSZPF235KK55NA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMZRGM2TQMZTGI3KM4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLTGN5XXIZLSL5QW4ZDSN5UWI>.
Download it today!
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
|
i'm putting it back in draft, the green tick is going to confuse me. |
|
Roger that
Il giorno lun 17 ago 2026 alle ore 09:28 Mathieu Poumeyrol <
***@***.***> ha scritto:
… *kali* left a comment (sonos/tract#2524)
<#2524 (comment)>
i'm putting it back in draft, the green tick is going to confuse me.
—
Reply to this email directly, view it on GitHub
<#2524?email_source=notifications&email_token=APL2Z6V4HEVQMEWKBTAMVM35KK6Z3A5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMZRGM3DKNJUGEYKM4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5313655410>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/APL2Z6TK7K5UWQPSWFIK2BL5KK6Z3AVCNFSNUABEKJSXA33TNF2G64TZHM4TSNJWGEZDCMZ3JFZXG5LFHM2DSOBSGQ3TIMZVHGQXMAQ>
.
Triage notifications, keep track of coding agent tasks and review pull
requests on the go with GitHub Mobile for iOS
<https://github.com/notifications/mobile/ios/APL2Z6RTOJR4IV43RROSNDT5KK6Z3A5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMZRGM3DKNJUGEYKM4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJKTGN5XXIZLSL5UW64Y>
and Android
<https://github.com/notifications/mobile/android/APL2Z6WRIXFFEXKZMU45A7T5KK6Z3A5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMZRGM3DKNJUGEYKM4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLTGN5XXIZLSL5QW4ZDSN5UWI>.
Download it today!
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Read a
.dat's 128-byte header instead of its payload, wire the tensor as aLazyConstcarrying only its fact, and read the payload later for the constants still in the graph —
so a caller that prunes a model first never pays for the parts it discards.
This is a second take on #1386, which I found while looking for the "stale PR" you
mentioned on #2482. Its
LazyConstProviderseam is the right one and I have kept it; whatchanged is when the value appears.
Why not lazy
TensorStorageYou suggested the exotics
TensorStorageframework, and I tried to take it that way first.It does not work for the case that motivates this: reaching concrete storage is a
type-based downcast (
storage_as::<BlockQuantStorage>()inmatmul/pack.rs), so a lazywrapper is simply not that type, and a q40 model would silently lose its packed matmul path
—
block_quant_einsum_weightsand thepack_aselection would decline to fire rather thanerror. A file-backed
TensorStoragestill looks right for plain weights and for theCUDA-direct-load case, but it cannot carry block-quant, so laziness has to sit one level up
in an op.
What changed against #1386
LazyConstis stateless and never evaluated;evalerrors telling you to materializefirst. Lazy values #1386 marked it stateful and re-read from disk on every
eval, which for a weighttouched each decode step is fatal, and which also stops it being a
Constso declutterand the block-quant fusion no longer see it.
"all inputs are constant" vacuously, so
PropConstandcompute_const_factsboth triedto evaluate it. (
wire_node's eager fold is already guarded byinput_facts.len() > 0.)LazyDatLoaderin Lazy values #1386 still calledread_tensorin full just to keepdt_shape, sothe first pass streamed the whole model through memory anyway.
read_tensor_headerstopsat 128 bytes;
read_tensoris refactored to share it, so there is one implementation..nnef.tgzisa gzip stream with no random access, matching your "so file system" caveat. The eager
path is untouched, and
ResourceLoaderneeded no signature change.Shape of it
load_lazyreturns an un-decluttered model and is an inherent method onapi/rs'sNnef,so no trait change and no impact on the proxy implementations.
Measured
On Qwen2.5-7B-Instruct-q40ef16, unpacked — 4085 MiB across 397 tensors:
(
AddAxis,Cast,Reshape), and aftermaterialize_lazy_constsplus a seconddeclutter the op histogram is identical to an eager load;
tensor exactly once.
Four tests in
nnef/tests/lazy_weights.rscover the small cases: typing without reading,parity with an eager load, pruning before materializing, and declutter leaving lazy
constants alone. Header parity across every writable dtype, and for a block-quant tensor,
is covered in
nnef/src/tensors.rs.Caveats
to an eager load plus a second declutter pass.
and I would rather not claim a number I have not taken.
graph.nnefalready carries each variable's shape, so strictly only the dtype needs theheader. I read both and cross-check them.
Happy to reshape any of this — in particular whether
materialize_lazy_constsshould be aregistered
ModelTransforminstead of a free function, and whetherload_lazybelongs onNnefInterfacerather than as an inherent method. 🍍