ArushBuilds commited on
Commit
3fb7577
·
1 Parent(s): b09f633

Delete veylon_model.py

Browse files
Files changed (1) hide show
  1. veylon_model.py +0 -515
veylon_model.py DELETED
@@ -1,515 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import numpy as np
4
- import keras
5
- from keras import layers, ops
6
- import jax
7
-
8
- from veylon_attention import flash_splash_attention, decode_swa
9
-
10
- try:
11
- from config import (
12
- CONTEXT,
13
- vocab_size as Vocab_size,
14
- D_MODEL,
15
- numberoflayers,
16
- numberofheads,
17
- d_Latent,
18
- ffn_mult,
19
- swa_window,
20
- num_kv_heads,
21
- use_moe,
22
- moe_num_experts,
23
- moe_top_k,
24
- USE_REMAT,
25
- MAX_GEN_TOKENS,
26
- )
27
- except Exception:
28
- CONTEXT = 2048
29
- Vocab_size = 32000
30
- D_MODEL = 512
31
- numberoflayers = 8
32
- numberofheads = 8
33
- d_Latent = 128
34
- ffn_mult = 3.5
35
- swa_window = 1024
36
- num_kv_heads = 2
37
- use_moe = False
38
- moe_num_experts = 8
39
- moe_top_k = 2
40
- USE_REMAT = True
41
- MAX_GEN_TOKENS = 256
42
-
43
- # NOTE: mixed precision policy is set by train.py / finetune.py before model
44
- # creation. Do NOT set it here — importing this module must not have side
45
- # effects.
46
-
47
-
48
- # ─────────────────────────────────────────────────────────────────────────────
49
- # Layers
50
- # ─────────────────────────────────────────────────────────────────────────────
51
-
52
- @keras.saving.register_keras_serializable()
53
- class RMSNorm(layers.Layer):
54
- def __init__(self, epsilon=1e-5, **kwargs):
55
- super().__init__(**kwargs)
56
- self.epsilon = epsilon
57
-
58
- def build(self, input_shape):
59
- self.weight = self.add_weight(shape=(input_shape[-1],), initializer='ones', name='gamma')
60
-
61
- def call(self, x):
62
- x_fp32 = ops.cast(x, 'float32')
63
- rms = ops.sqrt(ops.mean(ops.square(x_fp32), axis=-1, keepdims=True) + self.epsilon)
64
- out = x_fp32 / rms
65
- return ops.cast(out, x.dtype) * self.weight
66
-
67
- def get_config(self):
68
- cfg = super().get_config()
69
- cfg.update({'epsilon': self.epsilon})
70
- return cfg
71
-
72
-
73
- @keras.saving.register_keras_serializable()
74
- class RotaryEmbedding(layers.Layer):
75
- """
76
- RoPE with `max_seq_len + gen_headroom` so a full-context prompt can still
77
- decode up to `gen_headroom` tokens without overflowing the cos/sin table.
78
-
79
- `max_seq_len` is preserved in the config so checkpoints remain compatible —
80
- only the (trainable=False) table shapes grow.
81
- """
82
-
83
- def __init__(self, max_seq_len, dim, theta=10000.0, gen_headroom=0, **kwargs):
84
- super().__init__(**kwargs)
85
- if dim % 2 != 0:
86
- raise ValueError('RotaryEmbedding dim must be even.')
87
- self.max_seq_len = max_seq_len
88
- self.dim = dim
89
- self.theta = theta
90
- self.gen_headroom = int(gen_headroom)
91
- self.table_size = max_seq_len + self.gen_headroom
92
-
93
- def build(self, input_shape):
94
- half = self.dim // 2
95
- inv_freq = 1.0 / (self.theta ** (np.arange(0, self.dim, 2).astype(np.float32) / self.dim))
96
- positions = np.arange(self.table_size, dtype=np.float32)
97
- freqs = positions[:, None] * inv_freq[None, :]
98
- self.cos = self.add_weight(
99
- shape=(self.table_size, half),
100
- initializer=keras.initializers.Constant(np.cos(freqs)),
101
- trainable=False, dtype='float32', name='cos_table',
102
- )
103
- self.sin = self.add_weight(
104
- shape=(self.table_size, half),
105
- initializer=keras.initializers.Constant(np.sin(freqs)),
106
- trainable=False, dtype='float32', name='sin_table',
107
- )
108
-
109
- def call(self, x, offset=0):
110
- seq_len = x.shape[1]
111
- if seq_len is None:
112
- seq_len = ops.shape(x)[1]
113
- half = self.dim // 2
114
- if offset + seq_len > self.table_size:
115
- raise ValueError(
116
- f'RoPE table too small: offset={offset}, seq_len={seq_len}, '
117
- f'max={self.table_size} (CONTEXT={self.max_seq_len} + gen_headroom={self.gen_headroom})'
118
- )
119
- cos = self.cos[offset:offset + seq_len, :half]
120
- sin = self.sin[offset:offset + seq_len, :half]
121
- cos = ops.cast(ops.reshape(cos, (1, seq_len, 1, half)), x.dtype)
122
- sin = ops.cast(ops.reshape(sin, (1, seq_len, 1, half)), x.dtype)
123
- x1 = x[..., :half]
124
- x2 = x[..., half:]
125
- return ops.concatenate([x1 * cos - x2 * sin, x1 * sin + x2 * cos], axis=-1)
126
-
127
- def get_config(self):
128
- cfg = super().get_config()
129
- cfg.update({
130
- 'max_seq_len': self.max_seq_len,
131
- 'dim': self.dim,
132
- 'theta': self.theta,
133
- 'gen_headroom': self.gen_headroom,
134
- })
135
- return cfg
136
-
137
-
138
- @keras.saving.register_keras_serializable()
139
- class SwiGLUFFN(layers.Layer):
140
- def __init__(self, d_model, hidden_mult=3.5, **kwargs):
141
- super().__init__(**kwargs)
142
- self.d_model_arg = d_model
143
- self.hidden_mult = hidden_mult
144
- self.hidden_dim = int(d_model * hidden_mult * 2 / 3)
145
- self.hidden_dim = ((self.hidden_dim + 63) // 64) * 64
146
-
147
- def build(self, input_shape):
148
- d_model = input_shape[-1]
149
- self.gate_up_proj = self.add_weight(shape=(d_model, 2 * self.hidden_dim), initializer='glorot_uniform', name='gate_up_proj')
150
- self.down_proj = self.add_weight(shape=(self.hidden_dim, d_model), initializer='glorot_uniform', name='down_proj')
151
-
152
- def call(self, x, training=False):
153
- gate_up = ops.matmul(x, self.gate_up_proj)
154
- gate, up = ops.split(gate_up, 2, axis=-1)
155
- return ops.matmul(ops.silu(gate) * up, self.down_proj)
156
-
157
- def get_config(self):
158
- cfg = super().get_config()
159
- cfg.update({'d_model': self.d_model_arg, 'hidden_mult': self.hidden_mult})
160
- return cfg
161
-
162
-
163
- @keras.saving.register_keras_serializable()
164
- class MoE_FFN(layers.Layer):
165
- """
166
- Mixture-of-Experts FFN (top-k routing, masked dispatch).
167
-
168
- For each routing slot k in [0, top_k) we run each expert once over the
169
- full batch, zeroing inputs the expert was not selected for, then accumulate
170
- the gated expert output. The load-balancing loss uses top-1 argmax routing
171
- for the `f` term (standard Switch-Transformer formulation); the previous
172
- code used top-k membership which over-counted and produced the wrong loss.
173
- """
174
-
175
- def __init__(self, d_model, num_experts=8, top_k=2, hidden_mult=3.5, **kwargs):
176
- super().__init__(**kwargs)
177
- self.d_model_arg = d_model
178
- self.num_experts = num_experts
179
- self.top_k = top_k
180
- self.hidden_mult = hidden_mult
181
- self.experts = [SwiGLUFFN(d_model, hidden_mult) for _ in range(num_experts)]
182
- self.router = layers.Dense(num_experts, use_bias=False)
183
-
184
- def _load_balancing_loss(self, router_logits, top_idx):
185
- # Standard Switch-Transformer aux loss. `f` uses the argmax routing
186
- # decision (top_idx[..., 0]); `p` uses the router softmax mean.
187
- router_probs = ops.softmax(router_logits, axis=-1)
188
- mask = ops.one_hot(top_idx[..., 0], self.num_experts) # [..., num_experts]
189
- f = ops.mean(ops.cast(mask, 'float32'), axis=tuple(range(mask.ndim - 1)))
190
- p = ops.mean(router_probs, axis=tuple(range(router_probs.ndim - 1)))
191
- return self.num_experts * ops.sum(f * p)
192
-
193
- def call(self, x, training=False):
194
- # x: [B, S, D]
195
- B = ops.shape(x)[0]
196
- S = ops.shape(x)[1]
197
- D = x.shape[-1]
198
-
199
- router_logits = self.router(x) # [B, S, E]
200
- top_logits, top_idx = ops.top_k(router_logits, self.top_k) # [B, S, k]
201
- top_weights = ops.softmax(top_logits, axis=-1) # [B, S, k]
202
-
203
- if training:
204
- self.add_loss(self._load_balancing_loss(router_logits, top_idx))
205
-
206
- out = ops.zeros_like(x)
207
- for kk in range(self.top_k):
208
- idx_k = top_idx[..., kk] # [B, S]
209
- w_k = top_weights[..., kk][..., None] # [B, S, 1]
210
- for e in range(self.num_experts):
211
- mask_e = ops.cast(idx_k == e, x.dtype)[..., None] # [B, S, 1]
212
- # mask_e is always a tensor; skip the useless ops.is_tensor guard
213
- # (it never triggered — removed FIX BUG11)
214
- # Only run the expert where it is actually selected.
215
- x_e = x * mask_e
216
- expert_out = self.experts[e](x_e, training=training)
217
- out = out + expert_out * w_k * mask_e
218
- return out
219
-
220
- def get_config(self):
221
- cfg = super().get_config()
222
- cfg.update({
223
- 'd_model': self.d_model_arg, 'num_experts': self.num_experts,
224
- 'top_k': self.top_k, 'hidden_mult': self.hidden_mult,
225
- })
226
- return cfg
227
-
228
-
229
- @keras.saving.register_keras_serializable()
230
- class MLAttention(layers.Layer):
231
- def __init__(self, d_model, n_heads, d_latent, max_seq_len, num_kv_heads=2,
232
- swa_window=1024, attn_dropout=0.0, gen_headroom=0, **kwargs):
233
- super().__init__(**kwargs)
234
- if d_model % n_heads != 0:
235
- raise ValueError('d_model must be divisible by n_heads')
236
- if n_heads % num_kv_heads != 0:
237
- raise ValueError('n_heads must be divisible by num_kv_heads')
238
- self.d_model = d_model
239
- self.n_heads = n_heads
240
- self.num_kv_heads = num_kv_heads
241
- self.group_size = n_heads // num_kv_heads
242
- self.d_head = d_model // n_heads
243
- self.d_latent = d_latent
244
- self.max_seq_len = max_seq_len
245
- self.swa_window = swa_window
246
- self.gen_headroom = int(gen_headroom)
247
- self.dropout = layers.Dropout(attn_dropout)
248
- self.rope = RotaryEmbedding(max_seq_len, d_model // n_heads, gen_headroom=int(gen_headroom)) # FIX BUG10: must be in __init__ for Keras tracking
249
-
250
- def build(self, input_shape):
251
- self.W_qc = self.add_weight(shape=(self.d_model, self.d_model + self.d_latent), initializer='glorot_uniform', name='W_qc')
252
- self.W_kv = self.add_weight(shape=(self.d_latent, self.num_kv_heads * 2 * self.d_head), initializer='glorot_uniform', name='W_kv')
253
- self.W_o = self.add_weight(shape=(self.d_model, self.d_model), initializer='glorot_uniform', name='Wo')
254
-
255
- def _project_kv(self, c):
256
- kv = ops.matmul(c, self.W_kv)
257
- return ops.split(kv, 2, axis=-1)
258
-
259
- def call(self, x, training=False):
260
- B = ops.shape(x)[0]
261
- S = ops.shape(x)[1]
262
- qc = ops.matmul(x, self.W_qc)
263
- q_proj, c = ops.split(qc, [self.d_model], axis=-1)
264
- q = ops.reshape(q_proj, (B, S, self.n_heads, self.d_head))
265
- q = self.rope(q, offset=0)
266
- q = ops.transpose(q, (0, 2, 1, 3))
267
- k, v = self._project_kv(c)
268
- k = ops.reshape(k, (B, S, self.num_kv_heads, self.d_head))
269
- v = ops.reshape(v, (B, S, self.num_kv_heads, self.d_head))
270
- k = self.rope(k, offset=0)
271
- k = ops.transpose(k, (0, 2, 1, 3))
272
- v = ops.transpose(v, (0, 2, 1, 3))
273
-
274
- # Attention remat is REQUIRED during training: it is the per-block
275
- # jax.checkpoint(process_block) that stops XLA from staging the hidden
276
- # [S, B, Hkv, W, D] buffer (≈8.6 GB here) inside the fori_loop. Block-
277
- # level remat does NOT prevent that forward-time allocation, so this
278
- # must follow `training` regardless of config.USE_REMAT.
279
- out = flash_splash_attention(
280
- q, k, v,
281
- window_size=min(self.swa_window, self.max_seq_len),
282
- backend=jax.default_backend(),
283
- use_gqa=True,
284
- use_remat=training,
285
- )
286
- out = ops.transpose(out, (0, 2, 1, 3))
287
- out = ops.reshape(out, (B, S, self.d_model))
288
- out = self.dropout(out, training=training)
289
- return ops.matmul(out, self.W_o)
290
-
291
- def generate_step(self, x, cache_k=None, cache_v=None, cache_pos=0):
292
- B = ops.shape(x)[0]
293
- S = ops.shape(x)[1]
294
-
295
- qc = ops.matmul(x, self.W_qc)
296
- q_proj, c = ops.split(qc, [self.d_model], axis=-1)
297
-
298
- q = ops.reshape(q_proj, (B, S, self.n_heads, self.d_head))
299
- q = self.rope(q, offset=cache_pos)
300
- q = ops.transpose(q, (0, 2, 1, 3))
301
-
302
- k, v = self._project_kv(c)
303
- k = ops.reshape(k, (B, S, self.num_kv_heads, self.d_head))
304
- v = ops.reshape(v, (B, S, self.num_kv_heads, self.d_head))
305
- k = self.rope(k, offset=cache_pos)
306
- k = ops.transpose(k, (0, 2, 1, 3))
307
- v = ops.transpose(v, (0, 2, 1, 3))
308
-
309
- if cache_k is None:
310
- # Prefill: no remat (inference only), no window trim needed.
311
- out = flash_splash_attention(
312
- q, k, v,
313
- window_size=min(self.swa_window, self.max_seq_len),
314
- backend=jax.default_backend(),
315
- use_gqa=True,
316
- use_remat=False,
317
- )
318
- new_k = k[:, :, -self.swa_window:, :]
319
- new_v = v[:, :, -self.swa_window:, :]
320
- else:
321
- if S != 1:
322
- raise ValueError(f'generate_step with cache expects S=1, got S={S}')
323
-
324
- k = ops.concatenate([cache_k, k], axis=2)
325
- v = ops.concatenate([cache_v, v], axis=2)
326
-
327
- k = k[:, :, -self.swa_window:, :]
328
- v = v[:, :, -self.swa_window:, :]
329
-
330
- out = decode_swa(q, k, v)
331
- new_k = k
332
- new_v = v
333
-
334
- out = ops.transpose(out, (0, 2, 1, 3))
335
- out = ops.reshape(out, (B, S, self.d_model))
336
- out = ops.matmul(out, self.W_o)
337
- return out, new_k, new_v
338
-
339
- def get_config(self):
340
- cfg = super().get_config()
341
- cfg.update({
342
- 'd_model': self.d_model, 'n_heads': self.n_heads,
343
- 'num_kv_heads': self.num_kv_heads, 'd_latent': self.d_latent,
344
- 'max_seq_len': self.max_seq_len, 'swa_window': self.swa_window,
345
- 'attn_dropout': self.dropout.rate, 'gen_headroom': self.gen_headroom,
346
- })
347
- return cfg
348
-
349
-
350
- @keras.saving.register_keras_serializable()
351
- class TransformerBlock(layers.Layer):
352
- def __init__(self, d_model, n_heads, d_latent, ffn_layer, max_seq_len,
353
- num_kv_heads=2, swa_window=1024, use_remat=True, gen_headroom=0, **kwargs):
354
- super().__init__(**kwargs)
355
- self.d_model = d_model
356
- self.n_heads = n_heads
357
- self.d_latent = d_latent
358
- self.max_seq_len = max_seq_len
359
- self.num_kv_heads = num_kv_heads
360
- self.swa_window = swa_window
361
- self.use_remat = use_remat
362
- self.gen_headroom = int(gen_headroom)
363
- self.ffn = keras.saving.deserialize_keras_object(ffn_layer) if isinstance(ffn_layer, dict) else ffn_layer
364
- self.norm1 = RMSNorm()
365
- self.norm2 = RMSNorm()
366
- self.attn = MLAttention(
367
- d_model, n_heads, d_latent, max_seq_len,
368
- num_kv_heads=num_kv_heads, swa_window=swa_window, gen_headroom=self.gen_headroom,
369
- )
370
-
371
- def call(self, x, training=False):
372
- # Block-level activation/gradient checkpointing (optional, via
373
- # config.USE_REMAT). NOTE: this is independent of attention's own
374
- # remat, which MUST stay on whenever training=True — that's what stops
375
- # XLA staging the hidden [S,B,Hkv,W,D] buffer (the OOM we hit).
376
- if training and self.use_remat:
377
- def _fwd(x_in):
378
- attn_out = self.attn(self.norm1(x_in), training=True)
379
- ffn_out = self.ffn(self.norm2(x_in), training=True)
380
- return attn_out + ffn_out
381
- return x + jax.checkpoint(_fwd)(x)
382
- a = self.attn(self.norm1(x), training=training)
383
- f = self.ffn(self.norm2(x), training=training)
384
- return x + a + f
385
-
386
- def generate_step(self, x, cache_k=None, cache_v=None, cache_pos=0):
387
- # Must mirror call()'s PARALLEL residual structure exactly:
388
- # attn and ffn both read from norm(x) computed on the SAME
389
- # pre-block x, then both get added to that same original x.
390
- # Previously this was written as a sequential block (x=x+attn;
391
- # x=x+ffn(norm2(x))) — ffn ended up conditioned on norm2(x+attn_out)
392
- # instead of norm2(x), an input distribution the FFN weights were
393
- # never trained on. That silently produced a different model at
394
- # generation time than at training time: teacher-forced loss (via
395
- # call()) never exercises this path and looked fine, while actual
396
- # autoregressive generation compounded the mismatch every step.
397
- attn_out, nck, ncv = self.attn.generate_step(
398
- self.norm1(x), cache_k=cache_k, cache_v=cache_v, cache_pos=cache_pos,
399
- )
400
- ffn_out = self.ffn(self.norm2(x), training=False)
401
- x = x + attn_out + ffn_out
402
- return x, nck, ncv
403
-
404
- def get_config(self):
405
- cfg = super().get_config()
406
- cfg.update({
407
- 'd_model': self.d_model, 'n_heads': self.n_heads, 'd_latent': self.d_latent,
408
- 'ffn_layer': keras.saving.serialize_keras_object(self.ffn),
409
- 'max_seq_len': self.max_seq_len, 'num_kv_heads': self.num_kv_heads,
410
- 'swa_window': self.swa_window, 'use_remat': self.use_remat,
411
- 'gen_headroom': self.gen_headroom,
412
- })
413
- return cfg
414
-
415
-
416
- @keras.saving.register_keras_serializable()
417
- class VeylonModel(keras.Model):
418
- def __init__(self, vocab_size, d_model, n_layers, n_heads, d_latent, ffn_mult,
419
- max_seq_len, use_moe=False, moe_num_experts=8, moe_top_k=2,
420
- num_kv_heads=2, swa_window=1024, use_remat=True, gen_headroom=0, **kwargs):
421
- super().__init__(**kwargs)
422
- self.vocab_size = vocab_size
423
- self.d_model = d_model
424
- self.n_layers = n_layers
425
- self.n_heads = n_heads
426
- self.d_latent = d_latent
427
- self.ffn_mult = ffn_mult
428
- self.max_seq_len = max_seq_len
429
- self.use_moe = use_moe
430
- self.moe_num_experts = moe_num_experts
431
- self.moe_top_k = moe_top_k
432
- self.num_kv_heads = num_kv_heads
433
- self.swa_window = swa_window
434
- self.use_remat = use_remat
435
- self.gen_headroom = int(gen_headroom)
436
- self.embedding = layers.Embedding(vocab_size, d_model, name='token_embedding')
437
- self.blocks = []
438
- for i in range(n_layers):
439
- ffn = (MoE_FFN(d_model, moe_num_experts, moe_top_k, ffn_mult)
440
- if use_moe else SwiGLUFFN(d_model, ffn_mult))
441
- self.blocks.append(TransformerBlock(
442
- d_model, n_heads, d_latent, ffn, max_seq_len,
443
- num_kv_heads=num_kv_heads, swa_window=swa_window,
444
- use_remat=use_remat, gen_headroom=self.gen_headroom, name=f'block_{i}',
445
- ))
446
- self.norm = RMSNorm()
447
-
448
- def call(self, inputs, training=False):
449
- x = self.embedding(inputs)
450
- for block in self.blocks:
451
- x = block(x, training=training)
452
- x = self.norm(x)
453
- embedding_weights = self.embedding.embeddings
454
- logits = ops.matmul(x, ops.transpose(embedding_weights))
455
- return ops.cast(logits, 'float32')
456
-
457
- def generate_step(self, inputs, cache_k=None, cache_v=None, cache_pos=0):
458
- x = self.embedding(inputs)
459
- new_cache_k = []
460
- new_cache_v = []
461
-
462
- if cache_k is None:
463
- cache_k = [None] * len(self.blocks)
464
- cache_v = [None] * len(self.blocks)
465
-
466
- for i, block in enumerate(self.blocks):
467
- x, nck, ncv = block.generate_step(
468
- x, cache_k=cache_k[i], cache_v=cache_v[i], cache_pos=cache_pos,
469
- )
470
- new_cache_k.append(nck)
471
- new_cache_v.append(ncv)
472
-
473
- x = self.norm(x)
474
- embedding_weights = self.embedding.embeddings
475
- logits = ops.matmul(x, ops.transpose(embedding_weights))
476
- logits = ops.cast(logits, 'float32')
477
- return logits, new_cache_k, new_cache_v
478
-
479
- def get_config(self):
480
- cfg = super().get_config()
481
- cfg.update({
482
- 'vocab_size': self.vocab_size, 'd_model': self.d_model,
483
- 'n_layers': self.n_layers, 'n_heads': self.n_heads,
484
- 'd_latent': self.d_latent, 'ffn_mult': self.ffn_mult,
485
- 'max_seq_len': self.max_seq_len, 'use_moe': self.use_moe,
486
- 'moe_num_experts': self.moe_num_experts, 'moe_top_k': self.moe_top_k,
487
- 'num_kv_heads': self.num_kv_heads, 'swa_window': self.swa_window,
488
- 'use_remat': self.use_remat, 'gen_headroom': self.gen_headroom,
489
- })
490
- return cfg
491
-
492
-
493
- # ─────────────────────────────────────────────────────────────────────────────
494
- # Factory
495
- # ─────────────────────────────────────────────────────────────────────────────
496
-
497
- def create_llm(
498
- vocab_size=Vocab_size, d_model=D_MODEL, n_layers=numberoflayers, n_heads=numberofheads,
499
- d_latent=d_Latent, ffn_mult=ffn_mult, max_seq_len=CONTEXT, use_moe=use_moe,
500
- moe_num_experts=moe_num_experts, moe_top_k=moe_top_k, num_kv_heads=num_kv_heads,
501
- swa_window=swa_window, use_remat=USE_REMAT, gen_headroom=MAX_GEN_TOKENS,
502
- ):
503
- """
504
- Build a VeylonModel.
505
-
506
- `gen_headroom` sizes the RoPE table to (max_seq_len + gen_headroom) so that
507
- a full-length prompt can still generate `gen_headroom` tokens during
508
- inference without "RoPE table too small" errors. Defaults to MAX_GEN_TOKENS.
509
- """
510
- return VeylonModel(
511
- vocab_size=vocab_size, d_model=d_model, n_layers=n_layers, n_heads=n_heads,
512
- d_latent=d_latent, ffn_mult=ffn_mult, max_seq_len=max_seq_len, use_moe=use_moe,
513
- moe_num_experts=moe_num_experts, moe_top_k=moe_top_k, num_kv_heads=num_kv_heads,
514
- swa_window=swa_window, use_remat=use_remat, gen_headroom=gen_headroom,
515
- )