metrale_gpu_runtime/buffers/
release.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-25: `ModelResource` for `BufferArena`: free every buffer the arena owns.
4//!
5//! Owner: gpu-runtime.
6//! Invariants:
7//! - `release` calls `free` on every pointer field, whatever earlier frees
8//!   return, then sets every pointer field to `DevicePtr::NULL` and returns the
9//!   first error.
10//! - A second `release` frees only NULL pointers.
11
12use super::*;
13
14/// 2026-09-25: The destructure in `release` names every field, with no `..`, so
15/// a field added to `BufferArena` does not compile until it is handled here.
16/// Free the new buffer rather than adding a wildcard: a missed free is a leak
17/// that shows only as the next model failing to fit.
18impl metrale_core::scope::ModelResource<dyn GpuBackend> for BufferArena {
19    fn label(&self) -> &'static str {
20        "buffer arena"
21    }
22
23    fn release(&mut self, gpu: &dyn GpuBackend) -> anyhow::Result<()> {
24        let Self {
25            // 2026-09-25: Not allocations; named so the destructure stays exhaustive.
26            sizes: _,
27            max_batch_tokens: _,
28            decode_meta: _,
29            hidden_states,
30            residual,
31            norm_output,
32            qkv_output,
33            attn_output,
34            gate_logits,
35            gate_logits_f32,
36            moe_router_in_f32,
37            moe_output,
38            logits,
39            ssm_qkvz,
40            ssm_ba,
41            ssm_deinterleaved,
42            ssm_gates,
43            ssm_conv_out_f32,
44            scratch,
45            expert_gate_out,
46            expert_up_out,
47            expert_down_out,
48            splitk_workspace,
49            o_latent,
50            norm_unit_w,
51            hc_streams,
52            hc_post,
53            hc_comb,
54            hc_lowrank_scratch,
55            qsa_select_scratch,
56            gdn_fla_scratch,
57            ssd_scratch,
58            token_ids,
59            ffn_act_q8,
60            ffn_act_a,
61            ffn_act_scale,
62            ffn_act_scale_kmajor,
63            ffn_gate_up_fused,
64            fp8_act,
65            moe_fp8_scratch,
66            fp8_act_scale,
67            fp8_act_scale_kmajor,
68            lora_xa,
69            lora_delta,
70            lora_hact,
71            lora_seq_slot,
72            q2_dequant_scratch,
73            q2_act_q8,
74            ssm_rowwise_w_bf16,
75            ssm_rowwise_w_bf16_used: _,
76        } = self;
77        // 2026-09-25: Free every pointer, then set it to NULL, so a second call
78        // frees only NULL pointers: `ModelResource::release` must be idempotent.
79        let owned = [
80            *hidden_states,
81            *residual,
82            *norm_output,
83            *qkv_output,
84            *attn_output,
85            *gate_logits,
86            *gate_logits_f32,
87            *moe_router_in_f32,
88            *moe_output,
89            *logits,
90            *ssm_qkvz,
91            *ssm_ba,
92            *ssm_deinterleaved,
93            *ssm_gates,
94            *ssm_conv_out_f32,
95            *scratch,
96            *expert_gate_out,
97            *expert_up_out,
98            *expert_down_out,
99            *splitk_workspace,
100            *o_latent,
101            *norm_unit_w,
102            *hc_streams,
103            *hc_lowrank_scratch,
104            *qsa_select_scratch,
105            *hc_post,
106            *hc_comb,
107            *gdn_fla_scratch,
108            *ssd_scratch,
109            *token_ids,
110            *ffn_act_q8,
111            *ffn_act_a,
112            *ffn_act_scale,
113            *ffn_act_scale_kmajor,
114            *ffn_gate_up_fused,
115            *fp8_act,
116            *moe_fp8_scratch,
117            *fp8_act_scale,
118            *fp8_act_scale_kmajor,
119            *lora_xa,
120            *lora_delta,
121            *lora_hact,
122            *lora_seq_slot,
123            *q2_dequant_scratch,
124            *q2_act_q8,
125            *ssm_rowwise_w_bf16,
126        ];
127        let mut first_error = None;
128        for ptr in owned {
129            if let Err(e) = gpu.free(ptr)
130                && first_error.is_none()
131            {
132                first_error = Some(e);
133            }
134        }
135        *hidden_states = DevicePtr::NULL;
136        *residual = DevicePtr::NULL;
137        *norm_output = DevicePtr::NULL;
138        *qkv_output = DevicePtr::NULL;
139        *attn_output = DevicePtr::NULL;
140        *gate_logits = DevicePtr::NULL;
141        *gate_logits_f32 = DevicePtr::NULL;
142        *moe_router_in_f32 = DevicePtr::NULL;
143        *moe_output = DevicePtr::NULL;
144        *logits = DevicePtr::NULL;
145        *ssm_qkvz = DevicePtr::NULL;
146        *ssm_ba = DevicePtr::NULL;
147        *ssm_deinterleaved = DevicePtr::NULL;
148        *ssm_gates = DevicePtr::NULL;
149        *ssm_conv_out_f32 = DevicePtr::NULL;
150        *scratch = DevicePtr::NULL;
151        *expert_gate_out = DevicePtr::NULL;
152        *expert_up_out = DevicePtr::NULL;
153        *expert_down_out = DevicePtr::NULL;
154        *splitk_workspace = DevicePtr::NULL;
155        *o_latent = DevicePtr::NULL;
156        *norm_unit_w = DevicePtr::NULL;
157        *hc_streams = DevicePtr::NULL;
158        *hc_lowrank_scratch = DevicePtr::NULL;
159        *qsa_select_scratch = DevicePtr::NULL;
160        *hc_post = DevicePtr::NULL;
161        *hc_comb = DevicePtr::NULL;
162        *gdn_fla_scratch = DevicePtr::NULL;
163        *ssd_scratch = DevicePtr::NULL;
164        *token_ids = DevicePtr::NULL;
165        *ffn_act_q8 = DevicePtr::NULL;
166        *ffn_act_a = DevicePtr::NULL;
167        *ffn_act_scale = DevicePtr::NULL;
168        *ffn_act_scale_kmajor = DevicePtr::NULL;
169        *ffn_gate_up_fused = DevicePtr::NULL;
170        *fp8_act = DevicePtr::NULL;
171        *moe_fp8_scratch = DevicePtr::NULL;
172        *fp8_act_scale = DevicePtr::NULL;
173        *fp8_act_scale_kmajor = DevicePtr::NULL;
174        *lora_xa = DevicePtr::NULL;
175        *lora_delta = DevicePtr::NULL;
176        *lora_hact = DevicePtr::NULL;
177        *lora_seq_slot = DevicePtr::NULL;
178        *q2_dequant_scratch = DevicePtr::NULL;
179        *q2_act_q8 = DevicePtr::NULL;
180        *ssm_rowwise_w_bf16 = DevicePtr::NULL;
181        match first_error {
182            Some(e) => Err(e),
183            None => Ok(()),
184        }
185    }
186}