Avihu commited on
Commit
0da70f5
·
1 Parent(s): 7d20732

update to transformers v5: modular conformer, new class names, processor support

Browse files
README.md CHANGED
@@ -64,12 +64,12 @@ Measured RTFx of ~1820 on a single H100 GPU (batched inference, batch size 128).
64
 
65
  ### Installation
66
  We require `flash_attention_2` for inference, since this backend supports sequence packing and respects the `is_causal=False` flag.
67
- Tested with `transformers==4.57.6` and `transformers==5.5.3`, using `torch==2.9.1`.
68
 
69
  ```shell
70
  # Fresh install (CUDA 12.8, Python 3.10+)
71
  pip install torch==2.9.1 torchaudio==2.9.1 --index-url https://download.pytorch.org/whl/cu128
72
- pip install transformers==4.57.6 accelerate==1.13.0 safetensors==0.7.0 huggingface-hub==0.36.2 tokenizers==0.22.2
73
  pip install soundfile
74
  pip install flash-attn==2.8.3 --no-build-isolation
75
  ```
 
64
 
65
  ### Installation
66
  We require `flash_attention_2` for inference, since this backend supports sequence packing and respects the `is_causal=False` flag.
67
+ Requires `transformers>=5.5.3` and `torch>=2.9.1`.
68
 
69
  ```shell
70
  # Fresh install (CUDA 12.8, Python 3.10+)
71
  pip install torch==2.9.1 torchaudio==2.9.1 --index-url https://download.pytorch.org/whl/cu128
72
+ pip install transformers>=5.5.3 accelerate safetensors huggingface-hub tokenizers
73
  pip install soundfile
74
  pip install flash-attn==2.8.3 --no-build-isolation
75
  ```
__init__.py CHANGED
@@ -1,3 +1,42 @@
1
- from .configuration_nle import NLEEncoderConfig, NLEProjectorConfig, NLEConfig
2
- from .modeling_nle import NLENARDecoder
3
- from .modeling_ctc import NLECTCEncoder
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 IBM and The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from .configuration_granite_speech_nar import GraniteSpeechNarConfig, GraniteSpeechNarEncoderConfig, GraniteSpeechNarProjectorConfig
15
+ from .feature_extraction_granite_speech_nar import GraniteSpeechNarFeatureExtractor
16
+ from .modeling_granite_speech_nar import (
17
+ GraniteSpeechNarCTCEncoder,
18
+ GraniteSpeechNarEncoderOutput,
19
+ GraniteSpeechNarProjector,
20
+ GraniteSpeechNarForASR,
21
+ GraniteSpeechNarLM,
22
+ GraniteSpeechNarModel,
23
+ GraniteSpeechNarOutput,
24
+ GraniteSpeechNarPreTrainedModel,
25
+ )
26
+ from .processing_granite_speech_nar import GraniteSpeechNarProcessor
27
+
28
+ __all__ = [
29
+ "GraniteSpeechNarConfig",
30
+ "GraniteSpeechNarEncoderConfig",
31
+ "GraniteSpeechNarProjectorConfig",
32
+ "GraniteSpeechNarFeatureExtractor",
33
+ "GraniteSpeechNarCTCEncoder",
34
+ "GraniteSpeechNarEncoderOutput",
35
+ "GraniteSpeechNarProjector",
36
+ "GraniteSpeechNarForASR",
37
+ "GraniteSpeechNarLM",
38
+ "GraniteSpeechNarModel",
39
+ "GraniteSpeechNarOutput",
40
+ "GraniteSpeechNarPreTrainedModel",
41
+ "GraniteSpeechNarProcessor",
42
+ ]
config.json CHANGED
@@ -1,482 +1,84 @@
1
  {
2
- "architectures": [
3
- "NLENARDecoder"
4
- ],
5
- "attn_implementation": "flash_attention_2",
6
  "auto_map": {
7
- "AutoConfig": "configuration_nle.NLEConfig",
8
- "AutoFeatureExtractor": "feature_extraction_nle.NLEFeatureExtractor",
9
- "AutoModel": "modeling_nle.NLENARDecoder"
10
- },
11
- "ctc_tokenizer_config": {
12
- "char2idx": {
13
- " ": 32,
14
- "!": 33,
15
- "\"": 34,
16
- "#": 35,
17
- "$": 36,
18
- "%": 37,
19
- "&": 38,
20
- "'": 39,
21
- "(": 40,
22
- ")": 41,
23
- "*": 42,
24
- "+": 43,
25
- ",": 44,
26
- "-": 45,
27
- ".": 46,
28
- "/": 47,
29
- "0": 48,
30
- "1": 49,
31
- "2": 50,
32
- "3": 51,
33
- "4": 52,
34
- "5": 53,
35
- "6": 54,
36
- "7": 55,
37
- "8": 56,
38
- "9": 57,
39
- ":": 58,
40
- ";": 59,
41
- "<": 60,
42
- "=": 61,
43
- ">": 62,
44
- "?": 63,
45
- "@": 64,
46
- "A": 65,
47
- "B": 66,
48
- "C": 67,
49
- "D": 68,
50
- "E": 69,
51
- "F": 70,
52
- "G": 71,
53
- "H": 72,
54
- "I": 73,
55
- "J": 74,
56
- "K": 75,
57
- "L": 76,
58
- "M": 77,
59
- "N": 78,
60
- "O": 79,
61
- "P": 80,
62
- "Q": 81,
63
- "R": 82,
64
- "S": 83,
65
- "T": 84,
66
- "U": 85,
67
- "V": 86,
68
- "W": 87,
69
- "X": 88,
70
- "Y": 89,
71
- "Z": 90,
72
- "[": 91,
73
- "\\": 92,
74
- "]": 93,
75
- "^": 94,
76
- "_": 95,
77
- "`": 96,
78
- "a": 97,
79
- "b": 98,
80
- "c": 99,
81
- "d": 100,
82
- "e": 101,
83
- "f": 102,
84
- "g": 103,
85
- "h": 104,
86
- "i": 105,
87
- "j": 106,
88
- "k": 107,
89
- "l": 108,
90
- "m": 109,
91
- "n": 110,
92
- "o": 111,
93
- "p": 112,
94
- "q": 113,
95
- "r": 114,
96
- "s": 115,
97
- "t": 116,
98
- "u": 117,
99
- "v": 118,
100
- "w": 119,
101
- "x": 120,
102
- "y": 121,
103
- "z": 122,
104
- "{": 123,
105
- "|": 124,
106
- "}": 125,
107
- "~": 126,
108
- "\u007f": 127,
109
- "\u0080": 128,
110
- "\u0081": 129,
111
- "\u0082": 130,
112
- "\u0083": 131,
113
- "\u0084": 132,
114
- "\u0085": 133,
115
- "\u0086": 134,
116
- "\u0087": 135,
117
- "\u0088": 136,
118
- "\u0089": 137,
119
- "\u008a": 138,
120
- "\u008b": 139,
121
- "\u008c": 140,
122
- "\u008d": 141,
123
- "\u008e": 142,
124
- "\u008f": 143,
125
- "\u0090": 144,
126
- "\u0091": 145,
127
- "\u0092": 146,
128
- "\u0093": 147,
129
- "\u0094": 148,
130
- "\u0095": 149,
131
- "\u0096": 150,
132
- "\u0097": 151,
133
- "\u0098": 152,
134
- "\u0099": 153,
135
- "\u009a": 154,
136
- "\u009b": 155,
137
- "\u009c": 156,
138
- "\u009d": 157,
139
- "\u009e": 158,
140
- "\u009f": 159,
141
- "\u00a0": 160,
142
- "\u00a1": 161,
143
- "\u00a2": 162,
144
- "\u00a3": 163,
145
- "\u00a4": 164,
146
- "\u00a5": 165,
147
- "\u00a6": 166,
148
- "\u00a7": 167,
149
- "\u00a8": 168,
150
- "\u00a9": 169,
151
- "\u00aa": 170,
152
- "\u00ab": 171,
153
- "\u00ac": 172,
154
- "\u00ad": 173,
155
- "\u00ae": 174,
156
- "\u00af": 175,
157
- "\u00b0": 176,
158
- "\u00b1": 177,
159
- "\u00b2": 178,
160
- "\u00b3": 179,
161
- "\u00b4": 180,
162
- "\u00b5": 181,
163
- "\u00b6": 182,
164
- "\u00b7": 183,
165
- "\u00b8": 184,
166
- "\u00b9": 185,
167
- "\u00ba": 186,
168
- "\u00bb": 187,
169
- "\u00bc": 188,
170
- "\u00bd": 189,
171
- "\u00be": 190,
172
- "\u00bf": 191,
173
- "\u00c0": 192,
174
- "\u00c1": 193,
175
- "\u00c2": 194,
176
- "\u00c3": 195,
177
- "\u00c4": 196,
178
- "\u00c5": 197,
179
- "\u00c6": 198,
180
- "\u00c7": 199,
181
- "\u00c8": 200,
182
- "\u00c9": 201,
183
- "\u00ca": 202,
184
- "\u00cb": 203,
185
- "\u00cc": 204,
186
- "\u00cd": 205,
187
- "\u00ce": 206,
188
- "\u00cf": 207,
189
- "\u00d0": 208,
190
- "\u00d1": 209,
191
- "\u00d2": 210,
192
- "\u00d3": 211,
193
- "\u00d4": 212,
194
- "\u00d5": 213,
195
- "\u00d6": 214,
196
- "\u00d7": 215,
197
- "\u00d8": 216,
198
- "\u00d9": 217,
199
- "\u00da": 218,
200
- "\u00db": 219,
201
- "\u00dc": 220,
202
- "\u00dd": 221,
203
- "\u00de": 222,
204
- "\u00df": 223,
205
- "\u00e0": 224,
206
- "\u00e1": 225,
207
- "\u00e2": 226,
208
- "\u00e3": 227,
209
- "\u00e4": 228,
210
- "\u00e5": 229,
211
- "\u00e6": 230,
212
- "\u00e7": 231,
213
- "\u00e8": 232,
214
- "\u00e9": 233,
215
- "\u00ea": 234,
216
- "\u00eb": 235,
217
- "\u00ec": 236,
218
- "\u00ed": 237,
219
- "\u00ee": 238,
220
- "\u00ef": 239,
221
- "\u00f0": 240,
222
- "\u00f1": 241,
223
- "\u00f2": 242,
224
- "\u00f3": 243,
225
- "\u00f4": 244,
226
- "\u00f5": 245,
227
- "\u00f6": 246,
228
- "\u00f7": 247,
229
- "\u00f8": 248,
230
- "\u00f9": 249,
231
- "\u00fa": 250,
232
- "\u00fb": 251,
233
- "\u00fc": 252,
234
- "\u00fd": 253,
235
- "\u00fe": 254,
236
- "\u00ff": 255,
237
- "\u30a1": 256,
238
- "\u30a2": 257,
239
- "\u30a3": 258,
240
- "\u30a4": 259,
241
- "\u30a5": 260,
242
- "\u30a6": 261,
243
- "\u30a7": 262,
244
- "\u30a8": 263,
245
- "\u30a9": 264,
246
- "\u30aa": 265,
247
- "\u30ab": 266,
248
- "\u30ac": 267,
249
- "\u30ad": 268,
250
- "\u30ae": 269,
251
- "\u30af": 270,
252
- "\u30b0": 271,
253
- "\u30b1": 272,
254
- "\u30b2": 273,
255
- "\u30b3": 274,
256
- "\u30b4": 275,
257
- "\u30b5": 276,
258
- "\u30b6": 277,
259
- "\u30b7": 278,
260
- "\u30b8": 279,
261
- "\u30b9": 280,
262
- "\u30ba": 281,
263
- "\u30bb": 282,
264
- "\u30bc": 283,
265
- "\u30bd": 284,
266
- "\u30be": 285,
267
- "\u30bf": 286,
268
- "\u30c0": 287,
269
- "\u30c1": 288,
270
- "\u30c2": 289,
271
- "\u30c3": 290,
272
- "\u30c4": 291,
273
- "\u30c5": 292,
274
- "\u30c6": 293,
275
- "\u30c7": 294,
276
- "\u30c8": 295,
277
- "\u30c9": 296,
278
- "\u30ca": 297,
279
- "\u30cb": 298,
280
- "\u30cc": 299,
281
- "\u30cd": 300,
282
- "\u30ce": 301,
283
- "\u30cf": 302,
284
- "\u30d0": 303,
285
- "\u30d1": 304,
286
- "\u30d2": 305,
287
- "\u30d3": 306,
288
- "\u30d4": 307,
289
- "\u30d5": 308,
290
- "\u30d6": 309,
291
- "\u30d7": 310,
292
- "\u30d8": 311,
293
- "\u30d9": 312,
294
- "\u30da": 313,
295
- "\u30db": 314,
296
- "\u30dc": 315,
297
- "\u30dd": 316,
298
- "\u30de": 317,
299
- "\u30df": 318,
300
- "\u30e0": 319,
301
- "\u30e1": 320,
302
- "\u30e2": 321,
303
- "\u30e3": 322,
304
- "\u30e4": 323,
305
- "\u30e5": 324,
306
- "\u30e6": 325,
307
- "\u30e7": 326,
308
- "\u30e8": 327,
309
- "\u30e9": 328,
310
- "\u30ea": 329,
311
- "\u30eb": 330,
312
- "\u30ec": 331,
313
- "\u30ed": 332,
314
- "\u30ee": 333,
315
- "\u30ef": 334,
316
- "\u30f0": 335,
317
- "\u30f1": 336,
318
- "\u30f2": 337,
319
- "\u30f3": 338,
320
- "\u30f4": 339,
321
- "\u30f5": 340,
322
- "\u30f6": 341,
323
- "\u30f7": 342,
324
- "\u30f8": 343,
325
- "\u30f9": 344,
326
- "\u30fa": 345,
327
- "\u30fb": 346,
328
- "\u30fc": 347
329
- }
330
  },
331
- "dtype": "bfloat16",
 
 
 
 
332
  "encoder_config": {
333
- "attn_type": "block",
334
- "backbone": null,
335
- "backbone_requires_grad": false,
336
- "bpe_output_dim": 100353,
337
- "bpe_pooling_window": 4,
338
- "context_size": 200,
339
- "conv_expansion_factor": 2,
340
- "conv_kernel_size": 15,
341
- "dim_head": 128,
342
- "dropout": 0.1,
343
- "feedforward_mult": 4,
344
- "fmask_F": 6,
345
- "fmask_m": 2,
346
- "fmask_prob": 0.9,
347
- "hidden_dim": 1024,
348
- "initializer_range": 0.02,
349
  "input_dim": 160,
350
- "loss_lambda": 0.2,
351
- "max_pos_emb": 512,
352
- "model.bpe_pooling_window": 4,
353
- "model_type": "nle_encoder",
354
- "num_heads": 8,
355
  "num_layers": 16,
356
- "old_encoder_mask": true,
 
 
 
357
  "output_dim": 348,
 
 
 
358
  "pred_dropout": 0.25,
 
 
359
  "self_conditioning_layer": 8,
360
- "tmask_T": 50,
361
- "tmask_m": 2,
362
- "tmask_m_relative_max": 0.02,
363
- "tmask_prob": 0.9
364
  },
365
- "encoder_layer_indices": [
366
- 4,
367
- 8,
368
- 12,
369
- -1
370
- ],
371
- "initializer_range": 0.02,
372
- "llm_config": {
373
- "_name_or_path": "ibm-granite/granite-4.0-1b-base",
374
- "add_cross_attention": false,
375
- "architectures": [
376
- "GraniteForCausalLM"
377
- ],
378
- "attention_bias": false,
379
- "attention_dropout": 0.0,
380
- "attention_multiplier": 0.0078125,
381
- "bad_words_ids": null,
382
- "begin_suppress_tokens": null,
383
- "bos_token_id": 100257,
384
- "chunk_size_feed_forward": 0,
385
- "cross_attention_hidden_size": null,
386
- "decoder_start_token_id": null,
387
- "diversity_penalty": 0.0,
388
- "do_sample": false,
389
- "dtype": "bfloat16",
390
- "early_stopping": false,
391
- "embedding_multiplier": 12,
392
- "encoder_no_repeat_ngram_size": 0,
393
- "eos_token_id": 100257,
394
- "exponential_decay_length_penalty": null,
395
- "finetuning_task": null,
396
- "forced_bos_token_id": null,
397
- "forced_eos_token_id": null,
398
- "hidden_act": "silu",
399
  "hidden_size": 2048,
400
- "id2label": {
401
- "0": "LABEL_0",
402
- "1": "LABEL_1"
403
- },
404
- "initializer_range": 0.1,
405
- "intermediate_size": 4096,
406
- "is_decoder": false,
407
- "is_encoder_decoder": false,
408
- "label2id": {
409
- "LABEL_0": 0,
410
- "LABEL_1": 1
411
- },
412
- "length_penalty": 1.0,
413
- "logits_scaling": 8,
414
- "max_length": 20,
415
- "max_position_embeddings": 4096,
416
- "min_length": 0,
417
- "mlp_bias": false,
418
  "model_type": "granite",
419
- "no_repeat_ngram_size": 0,
420
- "num_attention_heads": 16,
421
- "num_beam_groups": 1,
422
- "num_beams": 1,
423
  "num_hidden_layers": 40,
 
424
  "num_key_value_heads": 4,
425
- "num_return_sequences": 1,
426
- "output_attentions": false,
427
- "output_hidden_states": false,
428
- "output_scores": false,
429
- "pad_token_id": 100256,
430
- "prefix": null,
431
- "problem_type": null,
432
- "pruned_heads": {},
433
- "remove_invalid_values": false,
434
- "repetition_penalty": 1.0,
435
- "residual_multiplier": 0.22,
436
- "return_dict": true,
437
- "return_dict_in_generate": false,
438
  "rms_norm_eps": 1e-05,
 
 
 
 
 
439
  "rope_parameters": {
440
  "rope_theta": 10000,
441
  "rope_type": "default"
442
  },
443
- "rope_scaling": null,
444
- "rope_theta": 10000.0,
445
- "sep_token_id": null,
446
- "suppress_tokens": null,
447
- "task_specific_params": null,
448
- "temperature": 1.0,
449
- "tf_legacy_loss": false,
450
- "tie_encoder_decoder": false,
451
- "tie_word_embeddings": true,
452
- "tokenizer_class": null,
453
- "top_k": 50,
454
- "top_p": 1.0,
455
- "torchscript": false,
456
- "transformers_version": "4.57.6",
457
- "typical_p": 1.0,
458
- "use_bfloat16": false,
459
- "use_cache": true,
460
- "vocab_size": 100352
461
- },
462
- "llm_name": "ibm-granite/granite-4.0-1b-base",
463
- "model_type": "nle",
464
- "projector_config": {
465
- "attn_bias": true,
466
- "block_size": 15,
467
- "downsample_rate": 5,
468
- "dropout_prob": 0.1,
469
- "encoder_dim": 1024,
470
- "hidden_size": 2048,
471
- "layernorm_eps": 1e-06,
472
- "llm_dim": 2048,
473
- "mlp_bias": true,
474
- "mlp_ratio": 2,
475
- "model_type": "nle_projector",
476
- "num_encoder_layers": 4,
477
- "num_heads": 32,
478
- "num_layers": 2
479
  },
 
480
  "scale_projected_embeddings": true,
481
- "transformers_version": "4.57.6"
 
 
 
482
  }
 
1
  {
 
 
 
 
2
  "auto_map": {
3
+ "AutoConfig": "configuration_granite_speech_nar.GraniteSpeechNarConfig",
4
+ "AutoFeatureExtractor": "feature_extraction_granite_speech_nar.GraniteSpeechNarFeatureExtractor",
5
+ "AutoProcessor": "processing_granite_speech_nar.GraniteSpeechNarProcessor",
6
+ "AutoModel": "modeling_granite_speech_nar.GraniteSpeechNarForASR"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  },
8
+ "architectures": [
9
+ "GraniteSpeechNarForASR"
10
+ ],
11
+ "model_type": "granite_speech_nar",
12
+ "torch_dtype": "bfloat16",
13
  "encoder_config": {
14
+ "model_type": "granite_speech_nar_encoder",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  "input_dim": 160,
 
 
 
 
 
16
  "num_layers": 16,
17
+ "hidden_dim": 1024,
18
+ "feedforward_mult": 4,
19
+ "num_heads": 8,
20
+ "dim_head": 128,
21
  "output_dim": 348,
22
+ "context_size": 200,
23
+ "max_pos_emb": 512,
24
+ "dropout": 0.1,
25
  "pred_dropout": 0.25,
26
+ "conv_kernel_size": 15,
27
+ "conv_expansion_factor": 2,
28
  "self_conditioning_layer": 8,
29
+ "bpe_output_dim": 100353,
30
+ "bpe_pooling_window": 4,
31
+ "initializer_range": 0.02
 
32
  },
33
+ "projector_config": {
34
+ "model_type": "granite_speech_nar_projector",
35
+ "encoder_dim": 1024,
36
+ "llm_dim": 2048,
37
+ "downsample_rate": 5,
38
+ "num_encoder_layers": 4,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  "hidden_size": 2048,
40
+ "num_heads": 32,
41
+ "num_layers": 2,
42
+ "dropout_prob": 0.1,
43
+ "block_size": 15,
44
+ "mlp_ratio": 2,
45
+ "layernorm_eps": 1e-06,
46
+ "attn_bias": true,
47
+ "mlp_bias": true
48
+ },
49
+ "text_config": {
 
 
 
 
 
 
 
 
50
  "model_type": "granite",
51
+ "vocab_size": 100352,
52
+ "hidden_size": 2048,
53
+ "intermediate_size": 4096,
 
54
  "num_hidden_layers": 40,
55
+ "num_attention_heads": 16,
56
  "num_key_value_heads": 4,
57
+ "hidden_act": "silu",
58
+ "max_position_embeddings": 4096,
59
+ "initializer_range": 0.1,
 
 
 
 
 
 
 
 
 
 
60
  "rms_norm_eps": 1e-05,
61
+ "use_cache": true,
62
+ "pad_token_id": 100256,
63
+ "bos_token_id": 100257,
64
+ "eos_token_id": 100257,
65
+ "tie_word_embeddings": true,
66
  "rope_parameters": {
67
  "rope_theta": 10000,
68
  "rope_type": "default"
69
  },
70
+ "attention_bias": false,
71
+ "attention_dropout": 0.0,
72
+ "mlp_bias": false,
73
+ "embedding_multiplier": 12,
74
+ "logits_scaling": 8,
75
+ "residual_multiplier": 0.22,
76
+ "attention_multiplier": 0.0078125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  },
78
+ "encoder_layer_indices": [4, 8, 12, -1],
79
  "scale_projected_embeddings": true,
80
+ "blank_token_id": 100257,
81
+ "min_edit_sequence_length": 8,
82
+ "ce_loss_lambda": 0.0,
83
+ "encoder_ctc_loss_lambda": 0.0
84
  }
configuration_granite_speech_nar.py ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 IBM and The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """Config classes for Granite Speech NAR (Non-Autoregressive ASR)."""
15
+
16
+ from huggingface_hub.dataclasses import strict
17
+
18
+ from transformers.configuration_utils import PreTrainedConfig
19
+ from transformers.models.auto import CONFIG_MAPPING
20
+
21
+
22
+ @strict
23
+ class GraniteSpeechNarEncoderConfig(PreTrainedConfig):
24
+ r"""
25
+ Configuration for the conformer encoder component of GraniteSpeechNar.
26
+
27
+ feedforward_mult (`int`, *optional*, defaults to 4):
28
+ Multiplier for the feedforward layers; intermediate dim = `hidden_dim * feedforward_mult`.
29
+ output_dim (`int`, *optional*, defaults to 348):
30
+ Output dimension of the mid-layer CTC prediction head.
31
+ context_size (`int`, *optional*, defaults to 200):
32
+ Context size for block-wise conformer attention.
33
+ max_pos_emb (`int`, *optional*, defaults to 512):
34
+ Maximum relative positional embedding index (Shaw's relative positional encoding).
35
+ pred_dropout (`float`, *optional*, defaults to 0.25):
36
+ Dropout applied to encoder hidden states before prediction heads.
37
+ conv_expansion_factor (`int`, *optional*, defaults to 2):
38
+ Expansion factor for conformer convolution module.
39
+ self_conditioning_layer (`int`, *optional*):
40
+ Layer index at which self-conditioning (mid-layer CTC feedback) is applied.
41
+ Defaults to `num_layers // 2`.
42
+ bpe_output_dim (`int`, *optional*):
43
+ Vocabulary size for the BPE CTC head (shifted by +1 for blank). If None, BPE head is disabled.
44
+ bpe_pooling_window (`int`, *optional*, defaults to 4):
45
+ Window size for posterior-weighted pooling before the BPE CTC head.
46
+
47
+ Example:
48
+
49
+ ```python
50
+ >>> from transformers import GraniteSpeechNarEncoderConfig
51
+
52
+ >>> configuration = GraniteSpeechNarEncoderConfig()
53
+ >>> print(configuration.hidden_dim)
54
+ 1024
55
+ ```"""
56
+
57
+ model_type = "granite_speech_nar_encoder"
58
+ attribute_map = {
59
+ "hidden_size": "hidden_dim",
60
+ "num_hidden_layers": "num_layers",
61
+ "num_attention_heads": "num_heads",
62
+ "num_mel_bins": "input_dim",
63
+ }
64
+
65
+ input_dim: int = 160
66
+ num_layers: int = 16
67
+ hidden_dim: int = 1024
68
+ feedforward_mult: int = 4
69
+ num_heads: int = 8
70
+ dim_head: int | None = None
71
+ output_dim: int = 348
72
+ context_size: int = 200
73
+ max_pos_emb: int = 512
74
+ dropout: float = 0.1
75
+ pred_dropout: float = 0.25
76
+ conv_kernel_size: int = 15
77
+ conv_expansion_factor: int = 2
78
+ self_conditioning_layer: int | None = None
79
+ bpe_output_dim: int | None = None
80
+ bpe_pooling_window: int = 4
81
+ initializer_range: float = 0.02
82
+
83
+ def __post_init__(self, **kwargs):
84
+ super().__post_init__(**kwargs)
85
+ if self.dim_head is None:
86
+ self.dim_head = self.hidden_dim // self.num_heads
87
+ if self.self_conditioning_layer is None:
88
+ self.self_conditioning_layer = self.num_layers // 2
89
+
90
+
91
+ @strict
92
+ class GraniteSpeechNarProjectorConfig(PreTrainedConfig):
93
+ r"""
94
+ Configuration for the QFormer-based audio projector in GraniteSpeechNar.
95
+
96
+ encoder_dim (`int`, *optional*, defaults to 1024):
97
+ Hidden dimension of the encoder (per layer).
98
+ llm_dim (`int`, *optional*, defaults to 2048):
99
+ Hidden dimension of the language model.
100
+ downsample_rate (`int`, *optional*, defaults to 5):
101
+ Temporal downsampling rate within each window block.
102
+ num_encoder_layers (`int`, *optional*, defaults to 4):
103
+ Number of encoder layers concatenated as projector input.
104
+ block_size (`int`, *optional*, defaults to 15):
105
+ Window size for blocked cross-attention in the projector.
106
+ layernorm_eps (`float`, *optional*, defaults to 1e-6):
107
+ Epsilon for layer normalization.
108
+ attn_bias (`bool`, *optional*, defaults to `True`):
109
+ Whether to use bias in attention projections.
110
+
111
+ Example:
112
+
113
+ ```python
114
+ >>> from transformers import GraniteSpeechNarProjectorConfig
115
+
116
+ >>> configuration = GraniteSpeechNarProjectorConfig()
117
+ >>> print(configuration.hidden_size)
118
+ 2048
119
+ ```"""
120
+
121
+ model_type = "granite_speech_nar_projector"
122
+
123
+ encoder_dim: int = 1024
124
+ llm_dim: int = 2048
125
+ downsample_rate: int = 5
126
+ num_encoder_layers: int = 4
127
+ hidden_size: int = 2048
128
+ num_heads: int = 32
129
+ num_layers: int = 2
130
+ dropout_prob: float = 0.1
131
+ block_size: int = 15
132
+ mlp_ratio: int = 2
133
+ layernorm_eps: float = 1e-6
134
+ attn_bias: bool = True
135
+ mlp_bias: bool = True
136
+
137
+
138
+ @strict
139
+ class GraniteSpeechNarConfig(PreTrainedConfig):
140
+ r"""
141
+ Configuration for the GraniteSpeechNar non-autoregressive ASR model.
142
+
143
+ This model uses a conformer encoder with BPE CTC head, a QFormer-based projector,
144
+ and a bidirectional Granite LLM backbone for single-pass speech recognition.
145
+
146
+ projector_config (`GraniteSpeechNarProjectorConfig` or `dict`, *optional*):
147
+ Configuration for the QFormer-based audio projector.
148
+ tie_word_embeddings (`bool`, *optional*, defaults to `True`):
149
+ Whether the LLM's input and output word embeddings should be tied.
150
+ encoder_layer_indices (`list[int]`, *optional*, defaults to `[4, 8, 12, -1]`):
151
+ Indices of encoder layers whose hidden states are concatenated as projector input.
152
+ scale_projected_embeddings (`bool`, *optional*, defaults to `True`):
153
+ Whether to divide projected audio embeddings by the LLM's embedding multiplier.
154
+ blank_token_id (`int`, *optional*):
155
+ Token ID used as the CTC blank symbol. Defaults to `text_config.eos_token_id`.
156
+ min_edit_sequence_length (`int`, *optional*, defaults to 8):
157
+ Minimum length of the edit sequence (CTC tokens + insertion slots) fed to the LLM.
158
+ ce_loss_lambda (`float`, *optional*, defaults to 0.0):
159
+ Weight for auxiliary cross-entropy loss on the LLM output.
160
+ encoder_ctc_loss_lambda (`float`, *optional*, defaults to 0.0):
161
+ Weight for auxiliary encoder BPE CTC loss.
162
+
163
+ Example:
164
+
165
+ ```python
166
+ >>> from transformers import GraniteSpeechNarConfig, GraniteSpeechNarForASR
167
+
168
+ >>> configuration = GraniteSpeechNarConfig()
169
+ >>> model = GraniteSpeechNarForASR(configuration)
170
+ >>> print(configuration.model_type)
171
+ 'granite_speech_nar'
172
+ ```"""
173
+
174
+ model_type = "granite_speech_nar"
175
+ sub_configs = {
176
+ "encoder_config": GraniteSpeechNarEncoderConfig,
177
+ "projector_config": GraniteSpeechNarProjectorConfig,
178
+ "text_config": "AutoConfig",
179
+ }
180
+
181
+ encoder_config: dict | PreTrainedConfig | None = None
182
+ projector_config: dict | PreTrainedConfig | None = None
183
+ text_config: dict | PreTrainedConfig | None = None
184
+ tie_word_embeddings: bool = True
185
+ encoder_layer_indices: list[int] | None = None
186
+ scale_projected_embeddings: bool = True
187
+ blank_token_id: int | None = None
188
+ min_edit_sequence_length: int = 8
189
+ ce_loss_lambda: float = 0.0
190
+ encoder_ctc_loss_lambda: float = 0.0
191
+
192
+ def __post_init__(self, **kwargs):
193
+ if isinstance(self.text_config, dict):
194
+ self.text_config["model_type"] = self.text_config.get("model_type", "granite")
195
+ self.text_config = CONFIG_MAPPING[self.text_config["model_type"]](**self.text_config)
196
+ elif self.text_config is None:
197
+ self.text_config = CONFIG_MAPPING["granite"]()
198
+
199
+ if not isinstance(self.encoder_config, GraniteSpeechNarEncoderConfig):
200
+ self.encoder_config = GraniteSpeechNarEncoderConfig(**(self.encoder_config or {}))
201
+
202
+ if not isinstance(self.projector_config, GraniteSpeechNarProjectorConfig):
203
+ self.projector_config = GraniteSpeechNarProjectorConfig(**(self.projector_config or {}))
204
+
205
+ if self.encoder_layer_indices is None:
206
+ self.encoder_layer_indices = [4, 8, 12, -1]
207
+
208
+ if self.blank_token_id is None:
209
+ self.blank_token_id = self.text_config.eos_token_id
210
+
211
+ super().__post_init__(**kwargs)
212
+
213
+
214
+ __all__ = ["GraniteSpeechNarEncoderConfig", "GraniteSpeechNarProjectorConfig", "GraniteSpeechNarConfig"]
configuration_nle.py DELETED
@@ -1,151 +0,0 @@
1
- from typing import List, Optional, Union, Any
2
-
3
- from transformers.configuration_utils import PretrainedConfig
4
-
5
-
6
- class NLEEncoderConfig(PretrainedConfig):
7
- model_type = "nle_encoder"
8
-
9
- def __init__(
10
- self,
11
- input_dim=160,
12
- num_layers=10,
13
- hidden_dim=1024,
14
- feedforward_mult=4,
15
- num_heads=8,
16
- dim_head=128,
17
- output_dim=42,
18
- context_size=200,
19
- max_pos_emb=512,
20
- dropout=0.1,
21
- pred_dropout=0.25,
22
- conv_kernel_size=15,
23
- conv_expansion_factor=2,
24
- loss_lambda=0.2,
25
- initializer_range=0.02,
26
- self_conditioning_layer=None,
27
- old_encoder_mask=True,
28
- bpe_output_dim=None,
29
- bpe_pooling_window=4,
30
- **kwargs,
31
- ):
32
- super().__init__(**kwargs)
33
- self.input_dim = input_dim
34
- self.num_layers = num_layers
35
- self.hidden_dim = hidden_dim
36
- self.feedforward_mult = feedforward_mult
37
- self.num_heads = num_heads
38
- self.dim_head = dim_head
39
- self.output_dim = output_dim
40
- self.context_size = context_size
41
- self.dropout = dropout
42
- self.pred_dropout = pred_dropout
43
- self.conv_kernel_size = conv_kernel_size
44
- self.conv_expansion_factor = conv_expansion_factor
45
- self.max_pos_emb = max_pos_emb
46
- self.loss_lambda = loss_lambda
47
- self.initializer_range = initializer_range
48
- if self_conditioning_layer is None:
49
- self_conditioning_layer = num_layers // 2
50
- self.self_conditioning_layer = self_conditioning_layer
51
- self.old_encoder_mask = old_encoder_mask
52
- self.bpe_output_dim = bpe_output_dim
53
- self.bpe_pooling_window = bpe_pooling_window
54
-
55
-
56
- class NLEProjectorConfig(PretrainedConfig):
57
- """Config for the QFormer-based encoder-to-LLM projector."""
58
- model_type = "nle_projector"
59
-
60
- def __init__(
61
- self,
62
- encoder_dim: int = 1024,
63
- llm_dim: int = 2048,
64
- downsample_rate: int = 5,
65
- num_encoder_layers: int = 1,
66
- hidden_size: Optional[int] = None,
67
- num_heads: Optional[int] = None,
68
- num_layers: int = 1,
69
- dropout_prob: float = 0.0,
70
- block_size: int = 15,
71
- mlp_ratio: int = 2,
72
- layernorm_eps: float = 1e-6,
73
- attn_bias: bool = True,
74
- mlp_bias: bool = True,
75
- **kwargs,
76
- ):
77
- super().__init__(**kwargs)
78
- self.encoder_dim = encoder_dim
79
- self.llm_dim = llm_dim
80
- self.downsample_rate = downsample_rate
81
- self.num_encoder_layers = num_encoder_layers
82
- self.hidden_size = hidden_size if hidden_size is not None else encoder_dim
83
- self.num_heads = num_heads if num_heads is not None else self.hidden_size // 64
84
- self.num_layers = num_layers
85
- self.dropout_prob = dropout_prob
86
- self.block_size = block_size
87
- self.mlp_ratio = mlp_ratio
88
- self.layernorm_eps = layernorm_eps
89
- self.attn_bias = attn_bias
90
- self.mlp_bias = mlp_bias
91
-
92
-
93
- class NLEConfig(PretrainedConfig):
94
- model_type = "nle"
95
-
96
- def __init__(
97
- self,
98
- encoder_config: Union[NLEEncoderConfig, dict, None] = None,
99
- projector_config: Union[NLEProjectorConfig, dict, None] = None,
100
- llm_name: str = "ibm-granite/granite-3.3-2b-base",
101
- llm_config: Optional[dict] = None,
102
- attn_implementation: str = "flash_attention_2",
103
- initializer_range: float = 0.02,
104
- encoder_layer_indices: Optional[List[int]] = None,
105
- scale_projected_embeddings: bool = False,
106
- ctc_tokenizer_config: Optional[dict] = None,
107
- **kwargs,
108
- ):
109
- super().__init__(**kwargs)
110
-
111
- if isinstance(encoder_config, dict):
112
- self.encoder_config = NLEEncoderConfig(**encoder_config)
113
- elif isinstance(encoder_config, NLEEncoderConfig):
114
- self.encoder_config = encoder_config
115
- elif encoder_config is None:
116
- self.encoder_config = NLEEncoderConfig()
117
- else:
118
- raise TypeError("encoder_config must be NLEEncoderConfig or dict")
119
-
120
- if isinstance(projector_config, dict):
121
- self.projector_config = NLEProjectorConfig(**projector_config)
122
- elif isinstance(projector_config, NLEProjectorConfig):
123
- self.projector_config = projector_config
124
- elif projector_config is None:
125
- self.projector_config = NLEProjectorConfig()
126
- else:
127
- raise TypeError("projector_config must be NLEProjectorConfig or dict")
128
-
129
- self.llm_name = llm_name
130
- self.llm_config = llm_config
131
- self.attn_implementation = attn_implementation
132
- self.initializer_range = initializer_range
133
- self.encoder_layer_indices = list(encoder_layer_indices) if encoder_layer_indices is not None else [-1]
134
- self.scale_projected_embeddings = scale_projected_embeddings
135
- self.ctc_tokenizer_config = ctc_tokenizer_config
136
- self.auto_map = {
137
- "AutoConfig": "configuration_nle.NLEConfig",
138
- "AutoModel": "modeling_nle.NLENARDecoder",
139
- "AutoFeatureExtractor": "feature_extraction_nle.NLEFeatureExtractor",
140
- }
141
-
142
- def to_dict(self):
143
- d = super().to_dict()
144
- d["encoder_config"] = self.encoder_config.to_dict()
145
- d["projector_config"] = self.projector_config.to_dict()
146
- if self.llm_config is not None:
147
- d["llm_config"] = self.llm_config
148
- return d
149
-
150
-
151
- __all__ = ["NLEEncoderConfig", "NLEProjectorConfig", "NLEConfig"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
feature_extraction_nle.py → feature_extraction_granite_speech_nar.py RENAMED
@@ -1,32 +1,36 @@
1
- """Feature extractor for NLE models.
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- Handles mel spectrogram extraction, frame stacking, batching, and computing
4
- the correct attention_mask / x_sizes at encoder-frame rate.
5
-
6
- Usage:
7
- from feature_extraction_nle import NLEFeatureExtractor
8
-
9
- feature_extractor = NLEFeatureExtractor()
10
- inputs = feature_extractor([waveform1, waveform2])
11
- output = model.generate(**inputs)
12
- """
13
 
14
- from typing import List, Optional, Union
15
 
16
- import torch
17
- import torchaudio
18
- from transformers.feature_extraction_utils import FeatureExtractionMixin
19
 
 
 
20
 
21
- class NLEFeatureExtractor(FeatureExtractionMixin):
22
- """Prepares raw audio for the NLENARDecoder.
23
 
24
- Pipeline: raw 16kHz audio -> MelSpectrogram -> log-mel normalize -> stack 2 frames.
25
- Encoder frame rate = sample_rate / (hop_length * 2) = 50 fps, i.e. T_samples // 320.
26
 
27
- Returns:
28
- - input_features: [B, T_enc, 160] stacked log-mel features
29
- - attention_mask: [B, T_enc] bool mask at encoder-frame rate
30
  """
31
 
32
  model_input_names = ["input_features", "attention_mask"]
@@ -40,56 +44,50 @@ class NLEFeatureExtractor(FeatureExtractionMixin):
40
  n_mels: int = 80,
41
  **kwargs,
42
  ):
 
43
  super().__init__(**kwargs)
44
- self.auto_map = {"AutoFeatureExtractor": "feature_extraction_nle.NLEFeatureExtractor"}
45
  self.sampling_rate = sampling_rate
46
  self.n_fft = n_fft
47
  self.win_length = win_length
48
  self.hop_length = hop_length
49
  self.n_mels = n_mels
50
  self.mel_filters = torchaudio.transforms.MelSpectrogram(
51
- sample_rate=sampling_rate, n_fft=n_fft, win_length=win_length,
52
- hop_length=hop_length, n_mels=n_mels,
 
 
 
53
  )
54
 
55
- @torch.no_grad()
56
- def _extract_features(self, raw_audio: torch.Tensor) -> torch.Tensor:
57
- """Convert raw waveform batch to stacked log-mel features.
58
-
59
- Args:
60
- raw_audio: [B, T] raw 16kHz waveform
61
-
62
- Returns:
63
- [B, T_enc, n_mels * 2] stacked log-mel features
64
- """
65
- melspec = self.mel_filters.to(raw_audio.device)
66
- B, T = raw_audio.shape
67
- # Ensure even number of mel frames for stacking
68
- l = 2 * (T // (2 * self.hop_length))
69
- mel = melspec(raw_audio.float())[..., :l]
70
- logmel = mel.transpose(-1, -2).clamp_min_(1e-10).log10_()
71
- mx = logmel.amax(dim=(-2, -1), keepdim=True)
72
- logmel = torch.maximum(logmel, mx - 8.0).div_(4).add_(1)
73
- # Stack 2 consecutive frames
74
- return logmel.reshape(B, -1, 2 * self.n_mels)
 
 
 
75
 
76
  def __call__(
77
  self,
78
- audios: Union[torch.Tensor, List[torch.Tensor]],
79
- device: Optional[Union[str, torch.device]] = None,
80
  ) -> dict:
81
- """Prepare a batch of raw audio waveforms for the model.
82
-
83
- Args:
84
- audios: A single tensor [T] or [B, T], or a list of 1-D tensors
85
- (variable length). Expected 16 kHz float waveforms.
86
- device: Target device for the output tensors.
87
-
88
- Returns:
89
- Dict with keys: input_features, attention_mask — ready to
90
- unpack into model.generate(**inputs).
91
- """
92
- # Normalise to list of 1-D tensors
93
  if isinstance(audios, torch.Tensor):
94
  if audios.ndim == 1:
95
  audios = [audios]
@@ -101,7 +99,6 @@ class NLEFeatureExtractor(FeatureExtractionMixin):
101
  raw_lengths = [a.shape[-1] for a in audios]
102
  encoder_frame_counts = [l // (2 * self.hop_length) for l in raw_lengths]
103
 
104
- # Pad waveforms to same length
105
  raw_audio = torch.nn.utils.rnn.pad_sequence(
106
  [a.squeeze(0) if a.ndim > 1 else a for a in audios],
107
  batch_first=True,
@@ -110,10 +107,8 @@ class NLEFeatureExtractor(FeatureExtractionMixin):
110
  if device is not None:
111
  raw_audio = raw_audio.to(device)
112
 
113
- # Extract mel features on the padded batch
114
  input_features = self._extract_features(raw_audio)
115
 
116
- # Build attention_mask at encoder-frame rate
117
  max_enc_frames = input_features.shape[1]
118
  x_sizes = torch.tensor(encoder_frame_counts, dtype=torch.long)
119
  attention_mask = torch.arange(max_enc_frames).unsqueeze(0) < x_sizes.unsqueeze(1)
@@ -128,4 +123,4 @@ class NLEFeatureExtractor(FeatureExtractionMixin):
128
  }
129
 
130
 
131
- __all__ = ["NLEFeatureExtractor"]
 
1
+ # Copyright 2026 IBM and The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """Feature extraction for Granite Speech NAR."""
15
 
16
+ from transformers.feature_extraction_utils import FeatureExtractionMixin
17
+ from transformers.tokenization_utils_base import AudioInput
18
+ from transformers.utils import is_torch_available, is_torchaudio_available
19
+ from transformers.utils.import_utils import requires_backends
 
 
 
 
 
 
20
 
 
21
 
22
+ if is_torch_available():
23
+ import torch
 
24
 
25
+ if is_torchaudio_available():
26
+ import torchaudio
27
 
 
 
28
 
29
+ class GraniteSpeechNarFeatureExtractor(FeatureExtractionMixin):
30
+ """Extracts log-mel spectrogram features for GraniteSpeechNar.
31
 
32
+ Produces stacked pairs of 80-band mel frames, yielding 160-dim features
33
+ at half the original frame rate.
 
34
  """
35
 
36
  model_input_names = ["input_features", "attention_mask"]
 
44
  n_mels: int = 80,
45
  **kwargs,
46
  ):
47
+ requires_backends(self, ["torch", "torchaudio"])
48
  super().__init__(**kwargs)
 
49
  self.sampling_rate = sampling_rate
50
  self.n_fft = n_fft
51
  self.win_length = win_length
52
  self.hop_length = hop_length
53
  self.n_mels = n_mels
54
  self.mel_filters = torchaudio.transforms.MelSpectrogram(
55
+ sample_rate=sampling_rate,
56
+ n_fft=n_fft,
57
+ win_length=win_length,
58
+ hop_length=hop_length,
59
+ n_mels=n_mels,
60
  )
61
 
62
+ def to_dict(self):
63
+ output = super().to_dict()
64
+ output.pop("mel_filters", None)
65
+ return output
66
+
67
+ def to_json_string(self):
68
+ import json
69
+ dictionary = self.to_dict()
70
+ return json.dumps(dictionary, indent=2, sort_keys=True) + "\n"
71
+
72
+ def __repr__(self):
73
+ return f"{self.__class__.__name__} {self.to_json_string()}"
74
+
75
+ def _extract_features(self, raw_audio: "torch.Tensor") -> "torch.Tensor":
76
+ with torch.no_grad():
77
+ mel_filters = self.mel_filters.to(raw_audio.device)
78
+ B, T = raw_audio.shape
79
+ l = 2 * (T // (2 * self.hop_length))
80
+ mel = mel_filters(raw_audio.float())[..., :l]
81
+ logmel = mel.transpose(-1, -2).clamp_min_(1e-10).log10_()
82
+ mx = logmel.amax(dim=(-2, -1), keepdim=True)
83
+ logmel = torch.maximum(logmel, mx - 8.0).div_(4).add_(1)
84
+ return logmel.reshape(B, -1, 2 * self.n_mels)
85
 
86
  def __call__(
87
  self,
88
+ audios: AudioInput,
89
+ device: str | None = None,
90
  ) -> dict:
 
 
 
 
 
 
 
 
 
 
 
 
91
  if isinstance(audios, torch.Tensor):
92
  if audios.ndim == 1:
93
  audios = [audios]
 
99
  raw_lengths = [a.shape[-1] for a in audios]
100
  encoder_frame_counts = [l // (2 * self.hop_length) for l in raw_lengths]
101
 
 
102
  raw_audio = torch.nn.utils.rnn.pad_sequence(
103
  [a.squeeze(0) if a.ndim > 1 else a for a in audios],
104
  batch_first=True,
 
107
  if device is not None:
108
  raw_audio = raw_audio.to(device)
109
 
 
110
  input_features = self._extract_features(raw_audio)
111
 
 
112
  max_enc_frames = input_features.shape[1]
113
  x_sizes = torch.tensor(encoder_frame_counts, dtype=torch.long)
114
  attention_mask = torch.arange(max_enc_frames).unsqueeze(0) < x_sizes.unsqueeze(1)
 
123
  }
124
 
125
 
126
+ __all__ = ["GraniteSpeechNarFeatureExtractor"]
generation_config.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "transformers_version": "5.8.1"
3
+ }
model.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:ae13622e43596054266cfd773dd2f411d608da268e036f9fd0c640adf9b66e1c
3
- size 4509380570
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:668b9fb26c417a4a8b78e3b99cb2fa3c52335f2cdcaafe99a9ad76f19df04fbb
3
+ size 4509384522
modeling_conformer.py DELETED
@@ -1,156 +0,0 @@
1
-
2
- import math, torch
3
- import torch.nn.functional as F
4
- from torch import nn
5
- from .configuration_nle import NLEEncoderConfig
6
-
7
-
8
- class NLEConformerFeedForward(nn.Module):
9
- """Feedforward module for conformer encoder blocks."""
10
-
11
- def __init__(self, config: NLEEncoderConfig):
12
- super().__init__()
13
- self.pre_norm = nn.LayerNorm(config.hidden_dim)
14
- self.up_proj = nn.Linear(config.hidden_dim, config.hidden_dim * config.feedforward_mult)
15
- self.silu = nn.SiLU()
16
- self.dropout = nn.Dropout(config.dropout)
17
- self.down_proj = nn.Linear(config.hidden_dim * config.feedforward_mult, config.hidden_dim)
18
-
19
- def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
20
- hidden_states = self.pre_norm(hidden_states)
21
- hidden_states = self.up_proj(hidden_states)
22
- hidden_states = self.dropout(self.silu(hidden_states))
23
- hidden_states = self.down_proj(hidden_states)
24
- hidden_states = self.dropout(hidden_states)
25
- return hidden_states
26
-
27
-
28
- class NLEConformerAttention(nn.Module):
29
- """Attention for conformer blocks using Shaw's relative positional embeddings.
30
- See the following [paper](https://arxiv.org/pdf/1803.02155) for more details.
31
- """
32
-
33
- def __init__(self, config: NLEEncoderConfig):
34
- super().__init__()
35
- self.config = config
36
- inner_dim = config.dim_head * config.num_heads
37
- self.max_pos_emb = config.max_pos_emb
38
- self.context_size = config.context_size
39
- self.num_heads = config.num_heads
40
- self.dim_head = config.dim_head
41
- self.scale = self.dim_head**-0.5
42
- self.pre_norm = nn.LayerNorm(config.hidden_dim)
43
- self.to_q = nn.Linear(config.hidden_dim, inner_dim, bias=False)
44
- self.to_kv = nn.Linear(config.hidden_dim, inner_dim * 2, bias=False)
45
- self.to_out = nn.Linear(inner_dim, config.hidden_dim)
46
- self.rel_pos_emb = nn.Embedding(2 * self.max_pos_emb + 1, self.dim_head)
47
- self.dropout = nn.Dropout(config.dropout)
48
-
49
- if self.context_size <= 0 or self.context_size > self.max_pos_emb:
50
- raise ValueError("Context size is either less than 0 or exceeds the max_pos_emb")
51
-
52
- def forward(self, hidden_states: torch.Tensor,
53
- attention_mask: torch.Tensor) -> torch.Tensor:
54
-
55
- hidden_states = self.pre_norm(hidden_states)
56
- bsz, num_features, _ = hidden_states.shape
57
- num_blocks = math.ceil(num_features / self.context_size)
58
- remainder = num_features % self.context_size
59
- if self.config.old_encoder_mask:
60
- attention_mask = torch.ones_like(attention_mask)
61
- if remainder > 0:
62
- # right padding to reach block size
63
- hidden_states = torch.nn.functional.pad(hidden_states, (0, 0, 0, self.context_size - remainder))
64
- attention_mask = torch.nn.functional.pad(attention_mask, (0, self.context_size - remainder))
65
-
66
- query_states = self.to_q(hidden_states)
67
- key_states, value_states = self.to_kv(hidden_states).chunk(2, dim=-1)
68
-
69
- query_states = query_states.reshape(bsz, num_blocks, self.context_size, self.num_heads, -1).transpose(2, 3)
70
- key_states = key_states.reshape(bsz, num_blocks, self.context_size, self.num_heads, -1).transpose(2, 3)
71
- value_states = value_states.reshape(bsz, num_blocks, self.context_size, self.num_heads, -1).transpose(2, 3)
72
- seq = torch.arange(self.config.context_size, device=hidden_states.device)
73
- dist = seq.view(-1, 1) - seq.view(1, -1) + self.config.max_pos_emb
74
- rel_pos_emb = self.rel_pos_emb(dist).to(query_states.dtype)
75
- pos_attn = torch.einsum('b m h c d, c r d -> b m h c r', query_states, rel_pos_emb) * self.scale
76
- mask_value = -torch.finfo(pos_attn.dtype).max
77
- expanded_attention_mask = attention_mask.reshape(bsz, num_blocks, 1, 1, -1)
78
- pos_attn.masked_fill_(~expanded_attention_mask, mask_value)
79
-
80
- with torch.nn.attention.sdpa_kernel(torch.nn.attention.SDPBackend.MATH):
81
- out = F.scaled_dot_product_attention(
82
- query_states, key_states, value_states, attn_mask=pos_attn, scale=self.scale
83
- )
84
- out = out.transpose(2, 3).reshape(bsz, hidden_states.shape[1], -1)
85
- out = self.to_out(out[:, :num_features, :])
86
- return self.dropout(out)
87
-
88
-
89
- class NLEConformerDepthWiseConv1d(nn.Module):
90
- """Wrapper for padded 1D pointwise convolution."""
91
-
92
- def __init__(self, chan_in: int, chan_out: int, kernel_size: int):
93
- super().__init__()
94
- # Padding for the 1D conv is symmetric or close (i.e., offset by one).
95
- pad = kernel_size // 2
96
- pad_offset = (kernel_size + 1) % 2
97
- self.padding = (pad, pad - pad_offset)
98
-
99
- self.conv = nn.Conv1d(chan_in, chan_out, kernel_size, groups=chan_in, bias=False)
100
-
101
- def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
102
- hidden_states = F.pad(hidden_states, self.padding)
103
- return self.conv(hidden_states)
104
-
105
-
106
- class NLEConformerConvModule(nn.Module):
107
- """Conformer conv module consisting of several 1D/depthwise 1D convolutional layers."""
108
-
109
- def __init__(self, config: NLEEncoderConfig):
110
- super().__init__()
111
- inner_dim = config.hidden_dim * config.conv_expansion_factor
112
-
113
- self.norm = nn.LayerNorm(config.hidden_dim)
114
- self.up_conv = nn.Conv1d(config.hidden_dim, inner_dim * 2, 1)
115
- self.glu = nn.GLU(dim=1)
116
- self.depth_conv = NLEConformerDepthWiseConv1d(
117
- inner_dim,
118
- inner_dim,
119
- kernel_size=config.conv_kernel_size,
120
- )
121
- self.silu = nn.SiLU()
122
- self.batch_norm = nn.BatchNorm1d(inner_dim)
123
- self.down_conv = nn.Conv1d(inner_dim, config.hidden_dim, 1)
124
- self.dropout = nn.Dropout(config.dropout)
125
-
126
- def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
127
- hidden_states = self.norm(hidden_states)
128
- hidden_states = self.up_conv(hidden_states.permute(0, 2, 1))
129
- hidden_states = self.glu(hidden_states)
130
- hidden_states = self.depth_conv(hidden_states)
131
- hidden_states = self.silu(self.batch_norm(hidden_states))
132
- hidden_states = self.down_conv(hidden_states).permute(0, 2, 1)
133
- hidden_states = self.dropout(hidden_states)
134
- return hidden_states
135
-
136
-
137
- class NLEConformerBlock(nn.Module):
138
- """Conformer block, consisting largely of linear layers, attention, and convolutional layers."""
139
-
140
- def __init__(self, config: NLEEncoderConfig):
141
- super().__init__()
142
- self.ff1 = NLEConformerFeedForward(config)
143
- self.attn = NLEConformerAttention(config)
144
- self.conv = NLEConformerConvModule(config)
145
- self.ff2 = NLEConformerFeedForward(config)
146
- self.post_norm = nn.LayerNorm(config.hidden_dim)
147
-
148
- def forward(self, hidden_states: torch.Tensor,
149
- attention_mask: torch.Tensor) -> torch.Tensor:
150
- hidden_states = 0.5 * self.ff1(hidden_states) + hidden_states
151
- hidden_states = self.attn(hidden_states,
152
- attention_mask=attention_mask) + hidden_states
153
- hidden_states = self.conv(hidden_states) + hidden_states
154
- hidden_states = 0.5 * self.ff2(hidden_states) + hidden_states
155
- hidden_states = self.post_norm(hidden_states)
156
- return hidden_states
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
modeling_ctc.py DELETED
@@ -1,135 +0,0 @@
1
- from dataclasses import dataclass
2
- from typing import Optional, Tuple
3
-
4
- import torch
5
- import torch.nn.functional as F
6
- from torch import nn
7
- from transformers.modeling_outputs import ModelOutput
8
- from transformers.modeling_utils import PreTrainedModel
9
- from .configuration_nle import NLEEncoderConfig
10
- from .modeling_conformer import NLEConformerBlock
11
-
12
-
13
- def posterior_weighted_pool(hidden, importance, window_size=4):
14
- """
15
- Downsample hidden states using importance-weighted pooling.
16
-
17
- Args:
18
- hidden: [B, T, D] hidden states
19
- importance: [B, T] importance weights (e.g., 1 - blank_prob)
20
- window_size: number of frames to pool together
21
-
22
- Returns:
23
- pooled: [B, T//window_size, D] downsampled hidden states
24
- """
25
- B, T, D = hidden.shape
26
- pad_len = (window_size - T % window_size) % window_size
27
- if pad_len > 0:
28
- hidden = F.pad(hidden, (0, 0, 0, pad_len))
29
- importance = F.pad(importance, (0, pad_len))
30
-
31
- num_windows = hidden.shape[1] // window_size
32
- hidden = hidden.view(B, num_windows, window_size, D)
33
- importance = importance.view(B, num_windows, window_size)
34
-
35
- weights = importance / (importance.sum(dim=-1, keepdim=True) + 1e-8)
36
- pooled = (hidden * weights.unsqueeze(-1)).sum(dim=2)
37
- return pooled
38
-
39
-
40
- @dataclass
41
- class NLEEncoderOutput(ModelOutput):
42
- logits: Optional[torch.FloatTensor] = None
43
- logits_bpe: Optional[torch.FloatTensor] = None
44
- last_hidden_state: Optional[torch.FloatTensor] = None
45
- all_hidden_states: Optional[Tuple[torch.FloatTensor]] = None
46
-
47
-
48
- class NLECTCEncoder(PreTrainedModel):
49
- config_class = NLEEncoderConfig
50
-
51
- def __init__(self, config: NLEEncoderConfig):
52
- super().__init__(config)
53
- self.config = config
54
- self.input_linear = nn.Linear(config.input_dim, config.hidden_dim, bias=True)
55
- self.layers = nn.ModuleList([NLEConformerBlock(config) for _ in range(config.num_layers)])
56
- self.out = nn.Linear(config.hidden_dim, config.output_dim, bias=True)
57
- self.out_mid = nn.Linear(config.output_dim, config.hidden_dim, bias=True)
58
- self.out_bpe = None
59
- if config.bpe_output_dim is not None:
60
- self.out_bpe = nn.Linear(config.hidden_dim, config.bpe_output_dim, bias=True)
61
-
62
- self.dropout = nn.Dropout(config.pred_dropout)
63
-
64
- self.post_init()
65
-
66
- def _init_weights(self, module: nn.Module):
67
- std = self.config.initializer_range
68
- if isinstance(module, (nn.Linear, nn.Conv1d)):
69
- module.weight.data.normal_(mean=0.0, std=std)
70
- if module.bias is not None:
71
- module.bias.data.zero_()
72
- elif isinstance(module, nn.Embedding):
73
- module.weight.data.normal_(mean=0.0, std=std)
74
- elif isinstance(module, (nn.LayerNorm, nn.BatchNorm1d)):
75
- module.weight.data.fill_(1.0)
76
- module.bias.data.zero_()
77
-
78
- def forward(
79
- self,
80
- input_features: torch.Tensor,
81
- attention_mask: Optional[torch.Tensor] = None, # [B, T_enc] bool after stacking
82
- output_hidden_states: Optional[bool] = None,
83
- ) -> NLEEncoderOutput:
84
-
85
- inputs_embeds = input_features
86
- if attention_mask is None:
87
- mask_shape = inputs_embeds.shape[:-1]
88
- attention_mask = torch.ones(mask_shape, dtype=bool, device=inputs_embeds.device)
89
-
90
- output_hidden_states = (
91
- output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
92
- )
93
-
94
- hidden_states = self.input_linear(inputs_embeds.to(self.dtype))
95
- all_hidden_states = (hidden_states,) if output_hidden_states else None
96
- blank_prob_mid = None
97
-
98
- for idx, layer in enumerate(self.layers, start=1):
99
- hidden_states = layer(hidden_states, attention_mask=attention_mask)
100
-
101
- if idx == self.config.self_conditioning_layer:
102
- logits_mid_plain = self.out(self.dropout(hidden_states))
103
- probs_mid = torch.softmax(logits_mid_plain.float(), dim=-1)
104
- blank_prob_mid = probs_mid[:, :, 0]
105
- hidden_states = hidden_states + self.out_mid(probs_mid.to(hidden_states.dtype))
106
-
107
- if output_hidden_states:
108
- all_hidden_states += (hidden_states,)
109
-
110
- hidden_states = self.dropout(hidden_states)
111
- logits = self.out(hidden_states)
112
-
113
- logits_bpe = None
114
- if self.out_bpe is not None and blank_prob_mid is not None:
115
- importance = 1.0 - blank_prob_mid
116
- pooled = posterior_weighted_pool(
117
- hidden_states.float(), importance, window_size=self.config.bpe_pooling_window
118
- ).to(hidden_states.dtype)
119
- # Downsample attention_mask to pooled resolution
120
- pool_window = self.config.bpe_pooling_window
121
- T = attention_mask.shape[1]
122
- pad_len = (pool_window - T % pool_window) % pool_window
123
- bpe_mask = F.pad(attention_mask, (0, pad_len), value=False)[:, ::pool_window]
124
- # Only compute BPE projection on valid frames (100K vocab is expensive)
125
- # Returns flat [N_valid, bpe_output_dim] — caller must unpack using bpe_mask
126
- valid_pooled = pooled[bpe_mask] # [N_valid, D]
127
- logits_bpe = self.out_bpe(valid_pooled) # [N_valid, bpe_output_dim]
128
-
129
- return NLEEncoderOutput(
130
- logits=logits,
131
- logits_bpe=logits_bpe,
132
- last_hidden_state=hidden_states,
133
- all_hidden_states=all_hidden_states
134
- )
135
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
modeling_granite_speech_nar.py ADDED
@@ -0,0 +1,1149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 IBM and The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import math
16
+ from collections.abc import Callable
17
+ from dataclasses import dataclass
18
+ from typing import Optional
19
+
20
+ import torch
21
+ import torch.nn.functional as F
22
+ from torch import nn
23
+
24
+ from transformers.activations import ACT2FN
25
+ from transformers.cache_utils import Cache
26
+ from transformers.generation import GenerationMixin
27
+ from transformers.integrations import use_kernel_forward_from_hub, use_kernel_func_from_hub, use_kernelized_func
28
+ from transformers.masking_utils import create_bidirectional_mask, find_packed_sequence_indices, packed_sequence_mask_function
29
+ from transformers.modeling_layers import GradientCheckpointingLayer
30
+ from transformers.modeling_outputs import BaseModelOutputWithPast, CausalLMOutputWithPast
31
+ from transformers.modeling_rope_utils import ROPE_INIT_FUNCTIONS, dynamic_rope_update
32
+ from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel
33
+ from transformers.processing_utils import Unpack
34
+ from transformers.utils import ModelOutput, TransformersKwargs
35
+ from transformers.utils.generic import maybe_autocast
36
+ from .configuration_granite_speech_nar import (
37
+ GraniteSpeechNarConfig,
38
+ GraniteSpeechNarEncoderConfig,
39
+ GraniteSpeechNarProjectorConfig,
40
+ )
41
+
42
+
43
+ @dataclass
44
+ class GraniteSpeechNarEncoderOutput(ModelOutput):
45
+ """Output of the GraniteSpeechNar encoder."""
46
+
47
+ loss: torch.Tensor | None = None
48
+ logits: torch.FloatTensor | None = None
49
+ last_hidden_state: torch.FloatTensor | None = None
50
+ all_hidden_states: tuple[torch.FloatTensor, ...] | None = None
51
+
52
+
53
+ @dataclass
54
+ class GraniteSpeechNarOutput(ModelOutput):
55
+ """Output of the GraniteSpeechNarForASR model.
56
+
57
+ Attributes:
58
+ loss: Combined CTC + auxiliary losses (only when labels provided).
59
+ preds: List of predicted token ID tensors per sample (after CTC collapse, inference only).
60
+ logits: List of per-sample logit tensors from the LLM head.
61
+ encoder_logits: Flat BPE CTC logits from the encoder.
62
+ encoder_preds: List of CTC-collapsed encoder predictions per sample.
63
+ """
64
+
65
+ loss: torch.Tensor | None = None
66
+ preds: list[torch.Tensor] | None = None
67
+ logits: list[torch.Tensor] | None = None
68
+ encoder_logits: torch.Tensor | None = None
69
+ encoder_preds: list[torch.Tensor] | None = None
70
+
71
+
72
+ ### Encoder - conformer is adapted from: https://github.com/lucidrains/conformer.git
73
+ class GraniteSpeechNarConformerFeedForward(nn.Module):
74
+ """Feedforward module for conformer encoder blocks."""
75
+
76
+ def __init__(self, config: GraniteSpeechNarEncoderConfig):
77
+ super().__init__()
78
+ self.pre_norm = nn.LayerNorm(config.hidden_dim)
79
+ self.up_proj = nn.Linear(config.hidden_dim, config.hidden_dim * config.feedforward_mult)
80
+ self.silu = nn.SiLU()
81
+ self.dropout = nn.Dropout(config.dropout)
82
+ self.down_proj = nn.Linear(config.hidden_dim * config.feedforward_mult, config.hidden_dim)
83
+
84
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
85
+ hidden_states = self.pre_norm(hidden_states)
86
+ hidden_states = self.up_proj(hidden_states)
87
+ hidden_states = self.dropout(self.silu(hidden_states))
88
+ hidden_states = self.down_proj(hidden_states)
89
+ hidden_states = self.dropout(hidden_states)
90
+ return hidden_states
91
+
92
+
93
+ class GraniteSpeechNarConformerAttention(nn.Module):
94
+ """Attention for conformer blocks using Shaw's relative positional embeddings.
95
+ See the following [paper](https://huggingface.co/papers/1803.02155) for more details.
96
+ """
97
+
98
+ def __init__(self, config: GraniteSpeechNarEncoderConfig):
99
+ super().__init__()
100
+
101
+ inner_dim = config.dim_head * config.num_heads
102
+ self.max_pos_emb = config.max_pos_emb
103
+ self.context_size = config.context_size
104
+ self.num_heads = config.num_heads
105
+ self.dim_head = config.dim_head
106
+ self.scale = self.dim_head**-0.5
107
+ self.pre_norm = nn.LayerNorm(config.hidden_dim)
108
+ self.to_q = nn.Linear(config.hidden_dim, inner_dim, bias=False)
109
+ self.to_kv = nn.Linear(config.hidden_dim, inner_dim * 2, bias=False)
110
+ self.to_out = nn.Linear(inner_dim, config.hidden_dim)
111
+ self.rel_pos_emb = nn.Embedding(2 * self.max_pos_emb + 1, self.dim_head)
112
+ self.dropout = nn.Dropout(config.dropout)
113
+
114
+ if self.context_size <= 0 or self.context_size > self.max_pos_emb:
115
+ raise ValueError("Context size is either less than 0 or exceeds the max_pos_emb")
116
+
117
+ def forward(self, hidden_states: torch.Tensor, attention_dists: torch.Tensor) -> torch.Tensor:
118
+ hidden_states = self.pre_norm(hidden_states)
119
+ bsz, num_features, _ = hidden_states.shape
120
+
121
+ num_blocks = math.ceil(num_features / self.context_size)
122
+ remainder = num_features % self.context_size
123
+ if remainder > 0:
124
+ # right padding to reach block size
125
+ hidden_states = torch.nn.functional.pad(hidden_states, (0, 0, 0, self.context_size - remainder))
126
+
127
+ query_states = self.to_q(hidden_states)
128
+ key_states, value_states = self.to_kv(hidden_states).chunk(2, dim=-1)
129
+
130
+ query_states = query_states.reshape(bsz, num_blocks, self.context_size, self.num_heads, -1).transpose(2, 3)
131
+ key_states = key_states.reshape(bsz, num_blocks, self.context_size, self.num_heads, -1).transpose(2, 3)
132
+ value_states = value_states.reshape(bsz, num_blocks, self.context_size, self.num_heads, -1).transpose(2, 3)
133
+
134
+ # shaw's relative positional embedding
135
+ rel_pos_emb = self.rel_pos_emb(attention_dists)
136
+ # alternative computation of `pos_attn` - for readability
137
+ # rel_pos_emb_expanded = rel_pos_emb.view([1, 1, 1] + list(rel_pos_emb.shape))
138
+ # pos_attn = torch.sum(query_states.unsqueeze(-2) * rel_pos_emb_expanded, dim=-1) * self.scale
139
+ # einsum implementation of pos_attn - gives x30 speedup over the alternative
140
+ # TODO (@avihu111) find a fast alternative to einsum
141
+ pos_attn = torch.einsum("b m h c d, c r d -> b m h c r", query_states, rel_pos_emb) * self.scale
142
+
143
+ if remainder > 0:
144
+ # masked attention in the extended block
145
+ mask = torch.ones(self.context_size, self.context_size, dtype=bool, device=hidden_states.device)
146
+ mask[:remainder, :remainder] = 0
147
+ mask_value = -torch.finfo(pos_attn.dtype).max
148
+ pos_attn[:, -1, :].masked_fill_(mask, mask_value)
149
+
150
+ with torch.nn.attention.sdpa_kernel(torch.nn.attention.SDPBackend.MATH):
151
+ out = F.scaled_dot_product_attention(
152
+ query_states, key_states, value_states, attn_mask=pos_attn, scale=self.scale
153
+ )
154
+ out = out.transpose(2, 3).reshape(bsz, hidden_states.shape[1], -1)
155
+ out = self.to_out(out[:, :num_features, :])
156
+ return self.dropout(out)
157
+
158
+
159
+ class GraniteSpeechNarConformerDepthWiseConv1d(nn.Module):
160
+ """Wrapper for padded 1D pointwise convolution."""
161
+
162
+ def __init__(self, chan_in: int, chan_out: int, kernel_size: int):
163
+ super().__init__()
164
+ # Padding for the 1D conv is symmetric or close (i.e., offset by one).
165
+ pad = kernel_size // 2
166
+ pad_offset = (kernel_size + 1) % 2
167
+ self.padding = (pad, pad - pad_offset)
168
+
169
+ self.conv = nn.Conv1d(chan_in, chan_out, kernel_size, groups=chan_in, bias=False)
170
+
171
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
172
+ hidden_states = F.pad(hidden_states, self.padding)
173
+ return self.conv(hidden_states)
174
+
175
+
176
+ class GraniteSpeechNarConformerConvModule(nn.Module):
177
+ """Conformer conv module consisting of several 1D/depthwise 1D convolutional layers."""
178
+
179
+ def __init__(self, config: GraniteSpeechNarEncoderConfig):
180
+ super().__init__()
181
+ inner_dim = config.hidden_dim * config.conv_expansion_factor
182
+
183
+ self.norm = nn.LayerNorm(config.hidden_dim)
184
+ self.up_conv = nn.Conv1d(config.hidden_dim, inner_dim * 2, 1)
185
+ self.glu = nn.GLU(dim=1)
186
+ self.depth_conv = GraniteSpeechNarConformerDepthWiseConv1d(
187
+ inner_dim,
188
+ inner_dim,
189
+ kernel_size=config.conv_kernel_size,
190
+ )
191
+ self.silu = nn.SiLU()
192
+ self.batch_norm = nn.BatchNorm1d(inner_dim)
193
+ self.down_conv = nn.Conv1d(inner_dim, config.hidden_dim, 1)
194
+ self.dropout = nn.Dropout(config.dropout)
195
+
196
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
197
+ hidden_states = self.norm(hidden_states)
198
+ hidden_states = self.up_conv(hidden_states.permute(0, 2, 1))
199
+ hidden_states = self.glu(hidden_states)
200
+ hidden_states = self.depth_conv(hidden_states)
201
+ hidden_states = self.silu(self.batch_norm(hidden_states))
202
+ hidden_states = self.down_conv(hidden_states).permute(0, 2, 1)
203
+ hidden_states = self.dropout(hidden_states)
204
+ return hidden_states
205
+
206
+
207
+ class GraniteSpeechNarConformerBlock(nn.Module):
208
+ """Conformer block, consisting largely of linear layers, attention, and convolutional layers."""
209
+
210
+ def __init__(self, config: GraniteSpeechNarEncoderConfig):
211
+ super().__init__()
212
+ self.ff1 = GraniteSpeechNarConformerFeedForward(config)
213
+ self.attn = GraniteSpeechNarConformerAttention(config)
214
+ self.conv = GraniteSpeechNarConformerConvModule(config)
215
+ self.ff2 = GraniteSpeechNarConformerFeedForward(config)
216
+ self.post_norm = nn.LayerNorm(config.hidden_dim)
217
+
218
+ def forward(self, hidden_states: torch.Tensor, attention_dists: torch.Tensor) -> torch.Tensor:
219
+ hidden_states = 0.5 * self.ff1(hidden_states) + hidden_states
220
+ hidden_states = self.attn(hidden_states, attention_dists=attention_dists) + hidden_states
221
+ hidden_states = self.conv(hidden_states) + hidden_states
222
+ hidden_states = 0.5 * self.ff2(hidden_states) + hidden_states
223
+ hidden_states = self.post_norm(hidden_states)
224
+ return hidden_states
225
+
226
+
227
+ class GraniteSpeechNarQFormerCrossAttention(nn.Module):
228
+ def __init__(self, config: GraniteSpeechNarProjectorConfig):
229
+ super().__init__()
230
+ self.num_heads = config.num_heads
231
+ self.head_dim = config.hidden_size // config.num_heads
232
+ self.hidden_size = config.hidden_size
233
+ self.q_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=config.attn_bias)
234
+ self.k_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=config.attn_bias)
235
+ self.v_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=config.attn_bias)
236
+ self.o_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=config.attn_bias)
237
+
238
+ def forward(self, hidden_states: torch.Tensor, encoder_hidden_states: torch.Tensor) -> torch.Tensor:
239
+ batch_size, query_len, _ = hidden_states.shape
240
+ encoder_len = encoder_hidden_states.shape[1]
241
+
242
+ query_states = (
243
+ self.q_proj(hidden_states).view(batch_size, query_len, self.num_heads, self.head_dim).transpose(1, 2)
244
+ )
245
+ key_states = (
246
+ self.k_proj(encoder_hidden_states)
247
+ .view(batch_size, encoder_len, self.num_heads, self.head_dim)
248
+ .transpose(1, 2)
249
+ )
250
+ value_states = (
251
+ self.v_proj(encoder_hidden_states)
252
+ .view(batch_size, encoder_len, self.num_heads, self.head_dim)
253
+ .transpose(1, 2)
254
+ )
255
+
256
+ attn_output = F.scaled_dot_product_attention(query_states, key_states, value_states, is_causal=False)
257
+ attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, query_len, self.hidden_size)
258
+ return self.o_proj(attn_output)
259
+
260
+
261
+ class GraniteSpeechNarQFormerMLP(nn.Module):
262
+ def __init__(self, config: GraniteSpeechNarProjectorConfig):
263
+ super().__init__()
264
+ mlp_hidden_size = int(config.hidden_size * config.mlp_ratio)
265
+ self.fc1 = nn.Linear(config.hidden_size, mlp_hidden_size, bias=config.mlp_bias)
266
+ self.act = nn.SiLU()
267
+ self.fc2 = nn.Linear(mlp_hidden_size, config.hidden_size, bias=config.mlp_bias)
268
+
269
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
270
+ return self.fc2(self.act(self.fc1(hidden_states)))
271
+
272
+
273
+ class GraniteSpeechNarQFormerLayer(nn.Module):
274
+ def __init__(self, config: GraniteSpeechNarProjectorConfig):
275
+ super().__init__()
276
+ self.attn_norm = nn.LayerNorm(config.hidden_size, eps=config.layernorm_eps)
277
+ self.cross_attention = GraniteSpeechNarQFormerCrossAttention(config)
278
+ self.mlp_norm = nn.LayerNorm(config.hidden_size, eps=config.layernorm_eps)
279
+ self.mlp = GraniteSpeechNarQFormerMLP(config)
280
+
281
+ def forward(self, hidden_states: torch.Tensor, encoder_hidden_states: torch.Tensor) -> torch.Tensor:
282
+ hidden_states = hidden_states + self.cross_attention(self.attn_norm(hidden_states), encoder_hidden_states)
283
+ hidden_states = hidden_states + self.mlp(self.mlp_norm(hidden_states))
284
+ return hidden_states
285
+
286
+
287
+ class GraniteSpeechNarQFormer(nn.Module):
288
+ def __init__(self, config: GraniteSpeechNarProjectorConfig):
289
+ super().__init__()
290
+ self.layers = nn.ModuleList([GraniteSpeechNarQFormerLayer(config) for _ in range(config.num_layers)])
291
+
292
+ def forward(self, query_embeds: torch.Tensor, encoder_hidden_states: torch.Tensor) -> torch.Tensor:
293
+ hidden_states = query_embeds
294
+ for layer in self.layers:
295
+ hidden_states = layer(hidden_states, encoder_hidden_states)
296
+ return hidden_states
297
+
298
+
299
+ class GraniteSpeechNarProjector(nn.Module):
300
+ """Windowed QFormer projector that maps multi-layer encoder features to LLM embedding space."""
301
+
302
+ def __init__(self, config: GraniteSpeechNarProjectorConfig):
303
+ super().__init__()
304
+ self.config = config
305
+ self.layer_norms = nn.ModuleList(
306
+ [nn.LayerNorm(config.encoder_dim, eps=config.layernorm_eps) for _ in range(config.num_encoder_layers)]
307
+ )
308
+ self.layer_projector = nn.Linear(config.encoder_dim * config.num_encoder_layers, config.hidden_size)
309
+ self.dropout = nn.Dropout(config.dropout_prob)
310
+ self.projector_act = nn.GELU()
311
+ self.qformer = GraniteSpeechNarQFormer(config)
312
+
313
+ query_length = config.block_size // config.downsample_rate
314
+ embed_std = config.hidden_size**-0.5
315
+ self.query = nn.Parameter(torch.randn(1, query_length, config.hidden_size) * embed_std)
316
+ self.window_positions = nn.Parameter(torch.randn(1, config.block_size, config.hidden_size) * embed_std)
317
+ self.out_norm = nn.LayerNorm(config.hidden_size, eps=config.layernorm_eps)
318
+ self.out_linear = nn.Linear(config.hidden_size, config.llm_dim)
319
+
320
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
321
+ batch_size, seq_len, dim = hidden_states.size()
322
+
323
+ hidden_states = hidden_states.view(
324
+ batch_size, seq_len, self.config.num_encoder_layers, self.config.encoder_dim
325
+ )
326
+ normalized_layers = []
327
+ for i, layer_norm in enumerate(self.layer_norms):
328
+ normalized_layers.append(layer_norm(hidden_states[:, :, i]))
329
+ hidden_states = torch.cat(normalized_layers, dim=-1)
330
+
331
+ hidden_states = self.projector_act(self.layer_projector(hidden_states))
332
+
333
+ block_size = self.config.block_size
334
+ nblocks = seq_len // block_size
335
+ rest = seq_len % block_size
336
+ if rest > 0:
337
+ hidden_states = F.pad(hidden_states, (0, 0, 0, block_size - rest), "constant", 0)
338
+ nblocks += 1
339
+
340
+ hidden_states = hidden_states.view(batch_size * nblocks, block_size, self.config.hidden_size)
341
+ query_length = self.query.shape[1]
342
+ mean_pool = hidden_states.view(
343
+ batch_size * nblocks, query_length, self.config.downsample_rate, self.config.hidden_size
344
+ ).mean(dim=-2)
345
+
346
+ hidden_states = self.qformer(
347
+ query_embeds=self.dropout(self.query + mean_pool),
348
+ encoder_hidden_states=self.dropout(hidden_states + self.window_positions),
349
+ )
350
+
351
+ hidden_states = hidden_states.view(batch_size, nblocks * query_length, -1)
352
+ hidden_states = self.dropout(self.out_norm(hidden_states))
353
+ return self.out_linear(hidden_states)
354
+
355
+
356
+ class GraniteSpeechNarPreTrainedModel(PreTrainedModel):
357
+ config_class = GraniteSpeechNarConfig
358
+ base_model_prefix = "encoder"
359
+ supports_gradient_checkpointing = True
360
+ _supports_flash_attn = True
361
+ _supports_flash_attn_2 = True
362
+ _supports_sdpa = True
363
+ _no_split_modules = ["GraniteSpeechNarConformerBlock", "GraniteDecoderLayer"]
364
+ input_modalities = ("audio",)
365
+
366
+
367
+ def rotate_half(x):
368
+ """Rotates half the hidden dims of the input."""
369
+ x1 = x[..., : x.shape[-1] // 2]
370
+ x2 = x[..., x.shape[-1] // 2 :]
371
+ return torch.cat((-x2, x1), dim=-1)
372
+
373
+
374
+ @use_kernel_func_from_hub("rotary_pos_emb")
375
+ def apply_rotary_pos_emb(q, k, cos, sin, unsqueeze_dim=1):
376
+ """Applies Rotary Position Embedding to the query and key tensors.
377
+
378
+ Args:
379
+ q (`torch.Tensor`): The query tensor.
380
+ k (`torch.Tensor`): The key tensor.
381
+ cos (`torch.Tensor`): The cosine part of the rotary embedding.
382
+ sin (`torch.Tensor`): The sine part of the rotary embedding.
383
+ unsqueeze_dim (`int`, *optional*, defaults to 1):
384
+ The 'unsqueeze_dim' argument specifies the dimension along which to unsqueeze cos[position_ids] and
385
+ sin[position_ids] so that they can be properly broadcasted to the dimensions of q and k. For example, note
386
+ that cos[position_ids] and sin[position_ids] have the shape [batch_size, seq_len, head_dim]. Then, if q and
387
+ k have the shape [batch_size, heads, seq_len, head_dim], then setting unsqueeze_dim=1 makes
388
+ cos[position_ids] and sin[position_ids] broadcastable to the shapes of q and k. Similarly, if q and k have
389
+ the shape [batch_size, seq_len, heads, head_dim], then set unsqueeze_dim=2.
390
+ Returns:
391
+ `tuple(torch.Tensor)` comprising of the query and key tensors rotated using the Rotary Position Embedding.
392
+ """
393
+ cos = cos.unsqueeze(unsqueeze_dim)
394
+ sin = sin.unsqueeze(unsqueeze_dim)
395
+ q_embed = (q * cos) + (rotate_half(q) * sin)
396
+ k_embed = (k * cos) + (rotate_half(k) * sin)
397
+ return q_embed, k_embed
398
+
399
+
400
+ def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
401
+ """
402
+ This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch,
403
+ num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim)
404
+ """
405
+ batch, num_key_value_heads, slen, head_dim = hidden_states.shape
406
+ if n_rep == 1:
407
+ return hidden_states
408
+ hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim)
409
+ return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)
410
+
411
+
412
+ def eager_attention_forward(
413
+ module: nn.Module,
414
+ query: torch.Tensor,
415
+ key: torch.Tensor,
416
+ value: torch.Tensor,
417
+ attention_mask: torch.Tensor | None,
418
+ scaling: float,
419
+ dropout: float = 0.0,
420
+ **kwargs: Unpack[TransformersKwargs],
421
+ ):
422
+ key_states = repeat_kv(key, module.num_key_value_groups)
423
+ value_states = repeat_kv(value, module.num_key_value_groups)
424
+
425
+ attn_weights = torch.matmul(query, key_states.transpose(2, 3)) * scaling
426
+ if attention_mask is not None:
427
+ attn_weights = attn_weights + attention_mask
428
+
429
+ attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query.dtype)
430
+ attn_weights = nn.functional.dropout(attn_weights, p=dropout, training=module.training)
431
+ attn_output = torch.matmul(attn_weights, value_states)
432
+ attn_output = attn_output.transpose(1, 2).contiguous()
433
+
434
+ return attn_output, attn_weights
435
+
436
+
437
+ @use_kernelized_func(apply_rotary_pos_emb)
438
+ class GraniteSpeechNarAttention(nn.Module):
439
+ """GraniteAttention with is_causal=False for bidirectional attention."""
440
+
441
+ is_causal = False
442
+
443
+ def __init__(self, config, layer_idx=None):
444
+ super().__init__()
445
+ self.config = config
446
+ self.layer_idx = layer_idx
447
+ self.head_dim = getattr(config, "head_dim", config.hidden_size // config.num_attention_heads)
448
+ self.num_key_value_groups = config.num_attention_heads // config.num_key_value_heads
449
+ self.scaling = config.attention_multiplier
450
+ self.attention_dropout = config.attention_dropout
451
+ self.is_causal = False
452
+
453
+ self.q_proj = nn.Linear(
454
+ config.hidden_size, config.num_attention_heads * self.head_dim, bias=config.attention_bias
455
+ )
456
+ self.k_proj = nn.Linear(
457
+ config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
458
+ )
459
+ self.v_proj = nn.Linear(
460
+ config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
461
+ )
462
+ self.o_proj = nn.Linear(
463
+ config.num_attention_heads * self.head_dim, config.hidden_size, bias=config.attention_bias
464
+ )
465
+
466
+ def forward(
467
+ self,
468
+ hidden_states: torch.Tensor,
469
+ position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
470
+ attention_mask: torch.Tensor | None = None,
471
+ past_key_values: Cache | None = None,
472
+ **kwargs: Unpack[TransformersKwargs],
473
+ ) -> tuple[torch.Tensor, torch.Tensor]:
474
+ input_shape = hidden_states.shape[:-1]
475
+ hidden_shape = (*input_shape, -1, self.head_dim)
476
+
477
+ query_states = self.q_proj(hidden_states).view(hidden_shape).transpose(1, 2)
478
+ key_states = self.k_proj(hidden_states).view(hidden_shape).transpose(1, 2)
479
+ value_states = self.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)
480
+
481
+ cos, sin = position_embeddings
482
+ query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)
483
+
484
+ if past_key_values is not None:
485
+ key_states, value_states = past_key_values.update(key_states, value_states, self.layer_idx)
486
+
487
+ attention_interface: Callable = ALL_ATTENTION_FUNCTIONS.get_interface(
488
+ self.config._attn_implementation, eager_attention_forward
489
+ )
490
+
491
+ attn_output, attn_weights = attention_interface(
492
+ self,
493
+ query_states,
494
+ key_states,
495
+ value_states,
496
+ attention_mask,
497
+ dropout=0.0 if not self.training else self.attention_dropout,
498
+ scaling=self.scaling,
499
+ **kwargs,
500
+ )
501
+
502
+ attn_output = attn_output.reshape(*input_shape, -1).contiguous()
503
+ attn_output = self.o_proj(attn_output)
504
+ return attn_output, attn_weights
505
+
506
+
507
+ @use_kernel_forward_from_hub("RMSNorm")
508
+ class GraniteSpeechNarRMSNorm(nn.Module):
509
+ def __init__(self, hidden_size, eps: float = 1e-6) -> None:
510
+ """
511
+ GraniteSpeechNarRMSNorm is equivalent to T5LayerNorm
512
+ """
513
+ super().__init__()
514
+ self.weight = nn.Parameter(torch.ones(hidden_size))
515
+ self.variance_epsilon = eps
516
+
517
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
518
+ input_dtype = hidden_states.dtype
519
+ hidden_states = hidden_states.to(torch.float32)
520
+ variance = hidden_states.pow(2).mean(-1, keepdim=True)
521
+ hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
522
+ return self.weight * hidden_states.to(input_dtype)
523
+
524
+ def extra_repr(self):
525
+ return f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}"
526
+
527
+
528
+ class GraniteSpeechNarMLP(nn.Module):
529
+ def __init__(self, config):
530
+ super().__init__()
531
+ self.config = config
532
+ self.hidden_size = config.hidden_size
533
+ self.intermediate_size = config.intermediate_size
534
+ self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=config.mlp_bias)
535
+ self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=config.mlp_bias)
536
+ self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=config.mlp_bias)
537
+ self.act_fn = ACT2FN[config.hidden_act]
538
+
539
+ def forward(self, x):
540
+ down_proj = self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))
541
+ return down_proj
542
+
543
+
544
+ class GraniteSpeechNarDecoderLayer(GradientCheckpointingLayer):
545
+ """GraniteDecoderLayer using bidirectional attention."""
546
+
547
+ def __init__(self, config, layer_idx: int):
548
+ super().__init__()
549
+ self.hidden_size = config.hidden_size
550
+ self.self_attn = GraniteSpeechNarAttention(config=config, layer_idx=layer_idx)
551
+
552
+ self.mlp = GraniteSpeechNarMLP(config)
553
+ self.input_layernorm = GraniteSpeechNarRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
554
+ self.post_attention_layernorm = GraniteSpeechNarRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
555
+ self.residual_multiplier = config.residual_multiplier
556
+
557
+ def forward(
558
+ self,
559
+ hidden_states: torch.Tensor,
560
+ attention_mask: torch.Tensor | None = None,
561
+ position_ids: torch.LongTensor | None = None,
562
+ past_key_values: Cache | None = None,
563
+ use_cache: bool | None = False,
564
+ position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
565
+ **kwargs: Unpack[TransformersKwargs],
566
+ ) -> torch.Tensor:
567
+ """
568
+ Args:
569
+ hidden_states (`torch.FloatTensor`): input to the layer of shape `(batch, seq_len, embed_dim)`
570
+ attention_mask (`torch.FloatTensor`, *optional*):
571
+ attention mask of size `(batch_size, sequence_length)` if flash attention is used or `(batch_size, 1,
572
+ query_sequence_length, key_sequence_length)` if default attention is used.
573
+ output_attentions (`bool`, *optional*):
574
+ Whether or not to return the attentions tensors of all attention layers. See `attentions` under
575
+ returned tensors for more detail.
576
+ use_cache (`bool`, *optional*):
577
+ If set to `True`, `past_key_values` key value states are returned and can be used to speed up decoding
578
+ (see `past_key_values`).
579
+ past_key_values (`Cache`, *optional*): cached past key and value projection states
580
+ position_embeddings (`tuple[torch.FloatTensor, torch.FloatTensor]`, *optional*):
581
+ Tuple containing the cosine and sine positional embeddings of shape `(batch_size, seq_len, head_dim)`,
582
+ with `head_dim` being the embedding dimension of each attention head.
583
+ kwargs (`dict`, *optional*):
584
+ Arbitrary kwargs to be ignored, used for FSDP and other methods that injects code
585
+ into the model
586
+ """
587
+ residual = hidden_states
588
+
589
+ hidden_states = self.input_layernorm(hidden_states)
590
+
591
+ hidden_states, _ = self.self_attn(
592
+ hidden_states=hidden_states,
593
+ attention_mask=attention_mask,
594
+ position_ids=position_ids,
595
+ past_key_values=past_key_values,
596
+ use_cache=use_cache,
597
+ position_embeddings=position_embeddings,
598
+ **kwargs,
599
+ )
600
+ hidden_states = residual + hidden_states * self.residual_multiplier
601
+
602
+ residual = hidden_states
603
+ hidden_states = self.post_attention_layernorm(hidden_states)
604
+ hidden_states = self.mlp(hidden_states)
605
+ hidden_states = residual + hidden_states * self.residual_multiplier
606
+
607
+ return hidden_states
608
+
609
+
610
+ class GraniteSpeechNarRotaryEmbedding(nn.Module):
611
+ inv_freq: torch.Tensor # fix linting for `register_buffer`
612
+
613
+ def __init__(self, config: GraniteSpeechNarConfig, device=None):
614
+ super().__init__()
615
+ self.max_seq_len_cached = config.max_position_embeddings
616
+ self.original_max_seq_len = config.max_position_embeddings
617
+
618
+ self.config = config
619
+
620
+ self.rope_type = self.config.rope_parameters["rope_type"]
621
+ rope_init_fn: Callable = self.compute_default_rope_parameters
622
+ if self.rope_type != "default":
623
+ rope_init_fn = ROPE_INIT_FUNCTIONS[self.rope_type]
624
+ inv_freq, self.attention_scaling = rope_init_fn(self.config, device)
625
+
626
+ self.register_buffer("inv_freq", inv_freq, persistent=False)
627
+ self.register_buffer("original_inv_freq", inv_freq.clone(), persistent=False)
628
+
629
+ @staticmethod
630
+ def compute_default_rope_parameters(
631
+ config: GraniteSpeechNarConfig | None = None,
632
+ device: Optional["torch.device"] = None,
633
+ seq_len: int | None = None,
634
+ ) -> tuple["torch.Tensor", float]:
635
+ """
636
+ Computes the inverse frequencies according to the original RoPE implementation
637
+ Args:
638
+ config ([`~transformers.PreTrainedConfig`]):
639
+ The model configuration.
640
+ device (`torch.device`):
641
+ The device to use for initialization of the inverse frequencies.
642
+ seq_len (`int`, *optional*):
643
+ The current sequence length. Unused for this type of RoPE.
644
+ Returns:
645
+ Tuple of (`torch.Tensor`, `float`), containing the inverse frequencies for the RoPE embeddings and the
646
+ post-processing scaling factor applied to the computed cos/sin (unused in this type of RoPE).
647
+ """
648
+ base = config.rope_parameters["rope_theta"]
649
+ dim = getattr(config, "head_dim", None) or config.hidden_size // config.num_attention_heads
650
+
651
+ attention_factor = 1.0 # Unused in this type of RoPE
652
+
653
+ # Compute the inverse frequencies
654
+ inv_freq = 1.0 / (
655
+ base ** (torch.arange(0, dim, 2, dtype=torch.int64).to(device=device, dtype=torch.float) / dim)
656
+ )
657
+ return inv_freq, attention_factor
658
+
659
+ @torch.no_grad()
660
+ @dynamic_rope_update # power user: used with advanced RoPE types (e.g. dynamic rope)
661
+ def forward(self, x, position_ids):
662
+ inv_freq_expanded = self.inv_freq[None, :, None].float().expand(position_ids.shape[0], -1, 1).to(x.device)
663
+ position_ids_expanded = position_ids[:, None, :].float()
664
+
665
+ device_type = x.device.type if isinstance(x.device.type, str) and x.device.type != "mps" else "cpu"
666
+ with maybe_autocast(device_type=device_type, enabled=False): # Force float32
667
+ freqs = (inv_freq_expanded.float() @ position_ids_expanded.float()).transpose(1, 2)
668
+ emb = torch.cat((freqs, freqs), dim=-1)
669
+ cos = emb.cos() * self.attention_scaling
670
+ sin = emb.sin() * self.attention_scaling
671
+
672
+ return cos.to(dtype=x.dtype), sin.to(dtype=x.dtype)
673
+
674
+
675
+ class GraniteSpeechNarModel(GraniteSpeechNarPreTrainedModel):
676
+ """GraniteModel with bidirectional (non-causal) attention.
677
+
678
+ Uses GraniteSpeechNarDecoderLayer which sets is_causal=False,
679
+ and replaces create_causal_mask() with create_bidirectional_mask() so all
680
+ attention backends (SDPA, FA2, eager, flex) get a proper non-causal mask.
681
+ """
682
+
683
+ def __init__(self, config):
684
+ super().__init__(config)
685
+ self.padding_idx = config.pad_token_id
686
+ self.vocab_size = config.vocab_size
687
+
688
+ self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx)
689
+ self.layers = nn.ModuleList(
690
+ [GraniteSpeechNarDecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
691
+ )
692
+ self.norm = GraniteSpeechNarRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
693
+ self.rotary_emb = GraniteSpeechNarRotaryEmbedding(config=config)
694
+ self.gradient_checkpointing = False
695
+ self.embedding_multiplier = config.embedding_multiplier
696
+
697
+ # Initialize weights and apply final processing
698
+ self.post_init()
699
+
700
+ def forward(
701
+ self,
702
+ input_ids: torch.LongTensor | None = None,
703
+ attention_mask: torch.Tensor | None = None,
704
+ position_ids: torch.LongTensor | None = None,
705
+ inputs_embeds: torch.FloatTensor | None = None,
706
+ **kwargs,
707
+ ) -> BaseModelOutputWithPast:
708
+ if (input_ids is None) ^ (inputs_embeds is not None):
709
+ raise ValueError("You must specify exactly one of input_ids or inputs_embeds")
710
+
711
+ if inputs_embeds is None:
712
+ inputs_embeds = self.embed_tokens(input_ids)
713
+
714
+ inputs_embeds = inputs_embeds * self.embedding_multiplier
715
+
716
+ if position_ids is None:
717
+ position_ids = torch.arange(inputs_embeds.shape[1], device=inputs_embeds.device).unsqueeze(0)
718
+
719
+ packed_seq_mask = find_packed_sequence_indices(position_ids)
720
+ and_mask_fn = packed_sequence_mask_function(packed_seq_mask) if packed_seq_mask is not None else None
721
+ bidirectional_mask = create_bidirectional_mask(
722
+ config=self.config,
723
+ inputs_embeds=inputs_embeds,
724
+ attention_mask=attention_mask,
725
+ and_mask_function=and_mask_fn,
726
+ )
727
+
728
+ hidden_states = inputs_embeds
729
+ position_embeddings = self.rotary_emb(hidden_states, position_ids=position_ids)
730
+
731
+ # KV cache is not needed in a non-autoregressive model
732
+ kwargs["use_cache"] = False
733
+ for decoder_layer in self.layers:
734
+ hidden_states = decoder_layer(
735
+ hidden_states,
736
+ attention_mask=bidirectional_mask,
737
+ position_ids=position_ids,
738
+ position_embeddings=position_embeddings,
739
+ **kwargs,
740
+ )
741
+
742
+ hidden_states = self.norm(hidden_states)
743
+
744
+ return BaseModelOutputWithPast(last_hidden_state=hidden_states)
745
+
746
+
747
+ def _posterior_weighted_pool(hidden: torch.Tensor, importance: torch.Tensor, window_size: int = 4) -> torch.Tensor:
748
+ batch_size, seq_len, hidden_dim = hidden.shape
749
+ pad_len = (window_size - seq_len % window_size) % window_size
750
+ if pad_len > 0:
751
+ hidden = F.pad(hidden, (0, 0, 0, pad_len))
752
+ importance = F.pad(importance, (0, pad_len))
753
+ num_windows = hidden.shape[1] // window_size
754
+ hidden = hidden.view(batch_size, num_windows, window_size, hidden_dim)
755
+ importance = importance.view(batch_size, num_windows, window_size)
756
+ weights = importance / (importance.sum(dim=-1, keepdim=True) + 1e-8)
757
+ pooled = (hidden * weights.unsqueeze(-1)).sum(dim=2)
758
+ return pooled
759
+
760
+
761
+ class GraniteSpeechNarCTCEncoder(GraniteSpeechNarPreTrainedModel):
762
+ """Conformer encoder with BPE CTC head and multi-layer output."""
763
+
764
+ config_class = GraniteSpeechNarEncoderConfig
765
+
766
+ def __init__(self, config: GraniteSpeechNarEncoderConfig):
767
+ super().__init__(config)
768
+ self.input_linear = nn.Linear(config.input_dim, config.hidden_dim, bias=True)
769
+ self.layers = nn.ModuleList([GraniteSpeechNarConformerBlock(config) for _ in range(config.num_layers)])
770
+ self.out = nn.Linear(config.hidden_dim, config.output_dim, bias=True)
771
+ self.out_mid = nn.Linear(config.output_dim, config.hidden_dim, bias=True)
772
+ self.out_bpe = None
773
+ if config.bpe_output_dim is not None:
774
+ self.out_bpe = nn.Linear(config.hidden_dim, config.bpe_output_dim, bias=True)
775
+ self.dropout = nn.Dropout(config.pred_dropout)
776
+ self.post_init()
777
+
778
+ def forward(
779
+ self,
780
+ input_features: torch.Tensor,
781
+ attention_mask: torch.Tensor | None = None,
782
+ output_hidden_states: bool | None = None,
783
+ labels: torch.Tensor | None = None,
784
+ label_lengths: torch.Tensor | None = None,
785
+ **kwargs,
786
+ ) -> GraniteSpeechNarEncoderOutput:
787
+ if attention_mask is None:
788
+ attention_mask = torch.ones(input_features.shape[:-1], dtype=torch.bool, device=input_features.device)
789
+
790
+ hidden_states = self.input_linear(input_features.to(self.dtype))
791
+ all_hidden_states = (hidden_states,) if output_hidden_states else None
792
+ blank_probs = None
793
+
794
+ context_size = self.config.context_size
795
+ seq = torch.arange(context_size, device=hidden_states.device)
796
+ relpos_dist = seq.view(-1, 1) - seq.view(1, -1)
797
+ attention_dists = torch.clamp(relpos_dist, -context_size, context_size) + self.config.max_pos_emb
798
+
799
+ for layer_idx, layer in enumerate(self.layers, start=1):
800
+ hidden_states = layer(hidden_states, attention_dists=attention_dists)
801
+
802
+ if layer_idx == self.config.self_conditioning_layer:
803
+ mid_logits = self.out(self.dropout(hidden_states))
804
+ mid_probs = torch.softmax(mid_logits.float(), dim=-1)
805
+ blank_probs = mid_probs[:, :, 0]
806
+ hidden_states = hidden_states + self.out_mid(mid_probs.to(hidden_states.dtype))
807
+
808
+ if output_hidden_states:
809
+ all_hidden_states += (hidden_states,)
810
+
811
+ hidden_states = self.dropout(hidden_states)
812
+
813
+ logits = None
814
+ loss = None
815
+ if self.out_bpe is not None and blank_probs is not None:
816
+ pool_window = self.config.bpe_pooling_window
817
+ importance = 1.0 - blank_probs
818
+ pooled = _posterior_weighted_pool(hidden_states.float(), importance, window_size=pool_window).to(
819
+ hidden_states.dtype
820
+ )
821
+ encoder_lengths = attention_mask.sum(dim=1)
822
+ lengths = -(encoder_lengths // -pool_window)
823
+ lengths_list = lengths.tolist()
824
+ logits = self.out_bpe(torch.cat([pooled[i, :length] for i, length in enumerate(lengths_list)]))
825
+
826
+ if labels is not None:
827
+ logits_padded = logits.new_zeros(len(lengths_list), max(lengths_list), logits.shape[-1])
828
+ offset = 0
829
+ for i, length in enumerate(lengths_list):
830
+ logits_padded[i, :length] = logits[offset : offset + length]
831
+ offset += length
832
+
833
+ log_probs = torch.log_softmax(logits_padded.float(), dim=-1)
834
+ loss = (
835
+ F.ctc_loss(
836
+ log_probs.transpose(0, 1),
837
+ labels + 1,
838
+ lengths,
839
+ label_lengths,
840
+ blank=0,
841
+ reduction="sum",
842
+ zero_infinity=True,
843
+ )
844
+ / lengths.sum()
845
+ )
846
+
847
+ return GraniteSpeechNarEncoderOutput(
848
+ loss=loss,
849
+ logits=logits,
850
+ last_hidden_state=hidden_states,
851
+ all_hidden_states=all_hidden_states,
852
+ )
853
+
854
+
855
+ class GraniteSpeechNarLM(GraniteSpeechNarPreTrainedModel, GenerationMixin):
856
+ """GraniteForCausalLM with a bidirectional (non-causal) backbone."""
857
+
858
+ _tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"}
859
+ _tp_plan = {"lm_head": "colwise_gather_output"}
860
+ _pp_plan = {"lm_head": (["hidden_states"], ["logits"])}
861
+
862
+ def __init__(self, config):
863
+ super().__init__(config)
864
+ self.model = GraniteSpeechNarModel(config)
865
+ self.vocab_size = config.vocab_size
866
+ self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
867
+
868
+ # Initialize weights and apply final processing
869
+ self.post_init()
870
+
871
+ def forward(
872
+ self,
873
+ input_ids: torch.LongTensor | None = None,
874
+ attention_mask: torch.Tensor | None = None,
875
+ position_ids: torch.LongTensor | None = None,
876
+ past_key_values: Cache | None = None,
877
+ inputs_embeds: torch.FloatTensor | None = None,
878
+ labels: torch.LongTensor | None = None,
879
+ use_cache: bool | None = None,
880
+ logits_to_keep: int | torch.Tensor = 0,
881
+ **kwargs: Unpack[TransformersKwargs],
882
+ ) -> CausalLMOutputWithPast:
883
+ r"""
884
+ Example:
885
+
886
+ ```python
887
+ >>> from transformers import AutoTokenizer, GraniteSpeechNarLM
888
+
889
+ >>> model = GraniteSpeechNarLM.from_pretrained("meta-granite_speech_nar/GraniteSpeechNar-2-7b-hf")
890
+ >>> tokenizer = AutoTokenizer.from_pretrained("meta-granite_speech_nar/GraniteSpeechNar-2-7b-hf")
891
+
892
+ >>> prompt = "Hey, are you conscious? Can you talk to me?"
893
+ >>> inputs = tokenizer(prompt, return_tensors="pt")
894
+
895
+ >>> # Generate
896
+ >>> generate_ids = model.generate(inputs.input_ids, max_length=30)
897
+ >>> tokenizer.batch_decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False)[0]
898
+ "Hey, are you conscious? Can you talk to me?\nI'm not conscious, but I can talk to you."
899
+ ```"""
900
+ outputs: BaseModelOutputWithPast = self.model(
901
+ input_ids=input_ids,
902
+ attention_mask=attention_mask,
903
+ position_ids=position_ids,
904
+ past_key_values=past_key_values,
905
+ inputs_embeds=inputs_embeds,
906
+ use_cache=use_cache,
907
+ **kwargs,
908
+ )
909
+
910
+ hidden_states = outputs.last_hidden_state
911
+ slice_indices = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep
912
+ logits = self.lm_head(hidden_states[:, slice_indices, :])
913
+ logits = logits / self.config.logits_scaling # main diff with Llama
914
+
915
+ loss = None
916
+ if labels is not None:
917
+ loss = self.loss_function(logits=logits, labels=labels, vocab_size=self.config.vocab_size, **kwargs)
918
+
919
+ return CausalLMOutputWithPast(
920
+ loss=loss,
921
+ logits=logits,
922
+ past_key_values=outputs.past_key_values,
923
+ hidden_states=outputs.hidden_states,
924
+ attentions=outputs.attentions,
925
+ )
926
+
927
+
928
+ class GraniteSpeechNarForASR(GraniteSpeechNarPreTrainedModel):
929
+ def __init__(self, config: GraniteSpeechNarConfig):
930
+ super().__init__(config)
931
+
932
+ self.encoder = GraniteSpeechNarCTCEncoder(config.encoder_config)
933
+ self.projector = GraniteSpeechNarProjector(config.projector_config)
934
+
935
+ text_config = config.text_config
936
+ if hasattr(config, "_attn_implementation"):
937
+ text_config._attn_implementation = config._attn_implementation
938
+ self.language_model = GraniteSpeechNarLM._from_config(text_config)
939
+
940
+ self.post_init()
941
+
942
+ def _ctc_collapse_decode(
943
+ self,
944
+ bpe_logits_flat: torch.Tensor,
945
+ bpe_lengths: list[int],
946
+ ) -> list[torch.Tensor]:
947
+ """GPU CTC greedy decode: argmax -> unique_consecutive -> remove blank -> shift."""
948
+ preds_flat = bpe_logits_flat.argmax(dim=-1)
949
+ per_sample = preds_flat.split(bpe_lengths)
950
+ return [(collapsed := torch.unique_consecutive(seq))[collapsed != 0] - 1 for seq in per_sample]
951
+
952
+ def _add_insertion_slots(self, token_ids: torch.Tensor) -> torch.Tensor:
953
+ """Insert blank tokens between each CTC token as editing slots for the LLM."""
954
+ blank_id = self.config.blank_token_id
955
+ n = token_ids.numel()
956
+ total_len = max(2 * n + 1, self.config.min_edit_sequence_length)
957
+ idx = torch.arange(n, device=token_ids.device)
958
+ out_idx = 2 * idx + 1
959
+ out = torch.full((total_len,), fill_value=blank_id, dtype=token_ids.dtype, device=token_ids.device)
960
+ out[out_idx] = token_ids
961
+ return out
962
+
963
+ def _build_flat_inputs(
964
+ self,
965
+ ctc_token_ids: list[torch.Tensor],
966
+ audio_embeds: torch.Tensor,
967
+ audio_lengths: list[int],
968
+ ) -> tuple[torch.Tensor, torch.Tensor, list[int]]:
969
+ """Build flat (pad-free) LLM input: [audio_0, text_0, audio_1, text_1, ...]"""
970
+ embed_tokens = self.language_model.model.embed_tokens
971
+
972
+ embeds_list = []
973
+ position_ids_list = []
974
+ text_lengths = []
975
+
976
+ for i, audio_len in enumerate(audio_lengths):
977
+ audio_emb = audio_embeds[i, :audio_len]
978
+ text_ids_with_slots = self._add_insertion_slots(ctc_token_ids[i])
979
+ text_emb = embed_tokens(text_ids_with_slots)
980
+ sample_embeds = torch.cat([audio_emb, text_emb], dim=0)
981
+ embeds_list.append(sample_embeds)
982
+ position_ids_list.append(torch.arange(sample_embeds.shape[0], device=audio_embeds.device))
983
+ text_lengths.append(text_ids_with_slots.shape[0])
984
+
985
+ flat_embeds = torch.cat(embeds_list, dim=0).unsqueeze(0)
986
+ flat_position_ids = torch.cat(position_ids_list, dim=0).unsqueeze(0)
987
+ return flat_embeds, flat_position_ids, text_lengths
988
+
989
+ def forward(
990
+ self,
991
+ *,
992
+ input_features: torch.Tensor,
993
+ attention_mask: torch.Tensor | None = None,
994
+ labels: torch.Tensor | None = None,
995
+ label_lengths: torch.Tensor | None = None,
996
+ output_encoder_logits: bool = False,
997
+ **kwargs,
998
+ ) -> GraniteSpeechNarOutput:
999
+ r"""
1000
+ Args:
1001
+ input_features (`torch.Tensor` of shape `(batch_size, seq_len, input_dim)`):
1002
+ Mel spectrogram features.
1003
+ attention_mask (`torch.Tensor` of shape `(batch_size, seq_len)`, *optional*):
1004
+ Encoder attention mask (1 for valid frames, 0 for padding).
1005
+ labels (`torch.Tensor` of shape `(batch_size, max_label_len)`, *optional*):
1006
+ Ground truth LLM token IDs for training.
1007
+ label_lengths (`torch.Tensor` of shape `(batch_size,)`, *optional*):
1008
+ Number of valid tokens per sample in `labels`.
1009
+ output_encoder_logits (`bool`, *optional*, defaults to `False`):
1010
+ Whether to return encoder BPE logits. When False, the large logits
1011
+ tensor is freed early to reduce peak memory.
1012
+
1013
+ Returns:
1014
+ [`GraniteSpeechNarOutput`]
1015
+ """
1016
+ encoder_labels = labels if self.config.encoder_ctc_loss_lambda else None
1017
+ enc_out = self.encoder(
1018
+ input_features=input_features,
1019
+ attention_mask=attention_mask,
1020
+ output_hidden_states=True,
1021
+ labels=encoder_labels,
1022
+ label_lengths=label_lengths if encoder_labels is not None else None,
1023
+ )
1024
+
1025
+ if attention_mask is None:
1026
+ attention_mask = torch.ones(input_features.shape[:-1], dtype=torch.bool, device=input_features.device)
1027
+
1028
+ encoder_lengths = attention_mask.sum(dim=1)
1029
+
1030
+ pool_window = self.encoder.config.bpe_pooling_window
1031
+ bpe_lengths = (-(encoder_lengths // -pool_window)).tolist()
1032
+ ctc_token_ids = self._ctc_collapse_decode(enc_out.logits, bpe_lengths)
1033
+
1034
+ multilayer_features = torch.cat(
1035
+ [enc_out.all_hidden_states[idx] for idx in self.config.encoder_layer_indices], dim=-1
1036
+ )
1037
+
1038
+ encoder_loss = enc_out.loss
1039
+ encoder_logits = enc_out.logits if output_encoder_logits else None
1040
+ del enc_out
1041
+
1042
+ audio_embeds = self.projector(multilayer_features)
1043
+ del multilayer_features
1044
+ if self.config.scale_projected_embeddings:
1045
+ embedding_multiplier = getattr(self.config.text_config, "embedding_multiplier", 1.0)
1046
+ audio_embeds = audio_embeds / embedding_multiplier
1047
+ audio_embeds = audio_embeds.to(self.language_model.model.embed_tokens.weight.dtype)
1048
+
1049
+ audio_lengths = (encoder_lengths // self.projector.config.downsample_rate).cpu().tolist()
1050
+
1051
+ flat_embeds, flat_position_ids, text_lengths = self._build_flat_inputs(
1052
+ ctc_token_ids, audio_embeds, audio_lengths
1053
+ )
1054
+
1055
+ llm_out = self.language_model(
1056
+ inputs_embeds=flat_embeds,
1057
+ position_ids=flat_position_ids,
1058
+ )
1059
+ all_logits = llm_out.logits.squeeze(0)
1060
+
1061
+ segment_lengths = [l for a, t in zip(audio_lengths, text_lengths) for l in (a, t)]
1062
+ text_logits = torch.cat(list(all_logits.split(segment_lengths)[1::2]))
1063
+ logits_per_sample = list(text_logits.split(text_lengths))
1064
+
1065
+ loss = None
1066
+ if labels is not None:
1067
+ log_probs = torch.log_softmax(text_logits.float(), dim=-1)
1068
+
1069
+ log_probs_padded = log_probs.new_zeros(len(text_lengths), max(text_lengths), log_probs.shape[-1])
1070
+ offset = 0
1071
+ for i, tl in enumerate(text_lengths):
1072
+ log_probs_padded[i, :tl] = log_probs[offset : offset + tl]
1073
+ offset += tl
1074
+
1075
+ input_lengths = torch.tensor(text_lengths, device=text_logits.device)
1076
+
1077
+ loss = (
1078
+ F.ctc_loss(
1079
+ log_probs_padded.transpose(0, 1),
1080
+ labels,
1081
+ input_lengths,
1082
+ label_lengths,
1083
+ blank=self.config.blank_token_id,
1084
+ reduction="sum",
1085
+ zero_infinity=True,
1086
+ )
1087
+ / input_lengths.sum()
1088
+ )
1089
+
1090
+ if self.config.ce_loss_lambda > 0.0:
1091
+ ce_targets = torch.cat([self._add_insertion_slots(ids) for ids in ctc_token_ids])
1092
+ ce_loss = F.cross_entropy(
1093
+ text_logits,
1094
+ ce_targets.long(),
1095
+ reduction="mean",
1096
+ ignore_index=-100,
1097
+ )
1098
+ loss = loss + self.config.ce_loss_lambda * ce_loss
1099
+
1100
+ if encoder_loss is not None:
1101
+ loss = loss + self.config.encoder_ctc_loss_lambda * encoder_loss
1102
+
1103
+ return GraniteSpeechNarOutput(
1104
+ loss=loss,
1105
+ logits=logits_per_sample,
1106
+ encoder_logits=encoder_logits,
1107
+ encoder_preds=ctc_token_ids,
1108
+ )
1109
+
1110
+ @torch.inference_mode()
1111
+ def transcribe(
1112
+ self,
1113
+ input_features: torch.Tensor,
1114
+ attention_mask: torch.Tensor | None = None,
1115
+ output_encoder_logits: bool = False,
1116
+ ) -> GraniteSpeechNarOutput:
1117
+ """Single-pass non-autoregressive inference: forward + CTC collapse on LLM output.
1118
+
1119
+ Returns token ID tensors in `preds`. Use `GraniteSpeechNarProcessor.batch_decode()`
1120
+ to convert to strings.
1121
+ """
1122
+ output = self.forward(
1123
+ input_features=input_features,
1124
+ attention_mask=attention_mask,
1125
+ output_encoder_logits=output_encoder_logits,
1126
+ )
1127
+
1128
+ blank_id = self.config.blank_token_id
1129
+ preds = []
1130
+ for sample_logits in output.logits:
1131
+ pred = torch.unique_consecutive(sample_logits.argmax(-1))
1132
+ pred = pred[pred != blank_id]
1133
+ preds.append(pred)
1134
+
1135
+ return GraniteSpeechNarOutput(
1136
+ preds=preds,
1137
+ logits=output.logits,
1138
+ encoder_logits=output.encoder_logits,
1139
+ encoder_preds=output.encoder_preds,
1140
+ )
1141
+
1142
+
1143
+ __all__ = [
1144
+ "GraniteSpeechNarModel",
1145
+ "GraniteSpeechNarCTCEncoder",
1146
+ "GraniteSpeechNarForASR",
1147
+ "GraniteSpeechNarLM",
1148
+ "GraniteSpeechNarPreTrainedModel",
1149
+ ]
modeling_nle.py DELETED
@@ -1,267 +0,0 @@
1
- from dataclasses import dataclass
2
- from pathlib import Path
3
- from typing import List, Optional
4
- import shutil
5
-
6
- import torch
7
- from transformers import (
8
- PreTrainedModel,
9
- AutoTokenizer,
10
- AutoModelForCausalLM,
11
- AutoConfig,
12
- )
13
- from transformers.utils import ModelOutput
14
-
15
- from .modeling_ctc import NLECTCEncoder
16
- from .modeling_projector import EncoderProjectorQFormer
17
- from .configuration_nle import NLEConfig
18
- from .tokenizer import Tokenizer
19
- from .modeling_conformer import NLEConformerBlock # noqa: F401 — needed so HF auto-import finds this module
20
-
21
-
22
- @dataclass
23
- class NLENARDecoderOutput(ModelOutput):
24
- loss: Optional[torch.Tensor] = None
25
- text_preds: Optional[List[str]] = None
26
- text_ctc_preds: Optional[List[str]] = None
27
- editing_logits: Optional[torch.Tensor] = None
28
- encoder_logits: Optional[torch.Tensor] = None
29
-
30
-
31
- class NLENARDecoder(PreTrainedModel):
32
- config_class = NLEConfig
33
-
34
- def __init__(self, config: NLEConfig):
35
- super().__init__(config)
36
-
37
- self.encoder = NLECTCEncoder(config.encoder_config)
38
-
39
- if config.ctc_tokenizer_config is not None:
40
- self.ctc_tokenizer = Tokenizer(**config.ctc_tokenizer_config)
41
- else:
42
- self.ctc_tokenizer = Tokenizer()
43
-
44
- if config.llm_config is not None:
45
- llm_cfg = AutoConfig.for_model(**config.llm_config)
46
- if config.attn_implementation is not None:
47
- llm_cfg._attn_implementation = config.attn_implementation
48
- self.llm = AutoModelForCausalLM.from_config(llm_cfg)
49
- else:
50
- llm_kwargs = {"device_map": "cpu", "torch_dtype": torch.bfloat16}
51
- if config.attn_implementation is not None:
52
- llm_kwargs["attn_implementation"] = config.attn_implementation
53
- self.llm = AutoModelForCausalLM.from_pretrained(config.llm_name, **llm_kwargs)
54
-
55
- for layer in self.llm.model.layers:
56
- layer.self_attn.is_causal = False
57
-
58
- self.llm_tokenizer = AutoTokenizer.from_pretrained(config.llm_name)
59
- self.projector = EncoderProjectorQFormer(config.projector_config)
60
-
61
- self.post_init()
62
-
63
- def save_pretrained(self, save_directory, **kwargs):
64
- save_directory = Path(save_directory)
65
- save_directory.mkdir(parents=True, exist_ok=True)
66
-
67
- self.config.llm_config = self.llm.config.to_dict()
68
- if self.config.ctc_tokenizer_config is None and self.ctc_tokenizer is not None:
69
- self.config.ctc_tokenizer_config = {"char2idx": self.ctc_tokenizer.char2idx}
70
-
71
- self.llm_tokenizer.save_pretrained(save_directory)
72
- super().save_pretrained(save_directory, **kwargs)
73
-
74
- src_dir = Path(__file__).parent
75
- for py_file in src_dir.glob("*.py"):
76
- shutil.copy2(py_file, save_directory / py_file.name)
77
-
78
- def add_insertion_slots(self, x: torch.Tensor) -> torch.Tensor:
79
- """Inserts pad_id (EOS) tokens between each CTC token."""
80
- pad_id = self.llm.config.eos_token_id
81
- n = x.numel()
82
- total_len = max(2 * n + 1, 8)
83
- idx = torch.arange(n, device=x.device)
84
- out_idx = 2 * idx + 1
85
- out = torch.full((total_len,), fill_value=pad_id, dtype=x.dtype, device=x.device)
86
- out[out_idx] = x
87
- return out
88
-
89
- def _decode_encoder_greedy(
90
- self,
91
- encoder_logits: torch.Tensor,
92
- attention_mask: torch.Tensor
93
- ) -> List[str]:
94
- ctc_preds = torch.where(attention_mask, encoder_logits.argmax(dim=-1), 0).cpu().numpy()
95
- text_ctc_preds = [self.ctc_tokenizer.decode(pred).strip() for pred in ctc_preds]
96
- text_ctc_preds = [x if x != "" else " " for x in text_ctc_preds]
97
- return text_ctc_preds
98
-
99
- def _decode_bpe_ctc_greedy(
100
- self,
101
- bpe_logits_flat: torch.Tensor,
102
- bpe_lengths: List[int],
103
- ) -> List[str]:
104
- """
105
- Decode BPE CTC logits to text using greedy decoding.
106
-
107
- BPE label mapping: index 0 = CTC blank, index i = llm_token_id (i-1).
108
-
109
- Args:
110
- bpe_logits_flat: [N_valid, V_bpe] raw logits (flat, valid positions only)
111
- bpe_lengths: per-sample lengths in pooled resolution
112
- """
113
- preds_flat = bpe_logits_flat.argmax(dim=-1) # [N_valid]
114
-
115
- text_ctc_preds = []
116
- offset = 0
117
- for length in bpe_lengths:
118
- seq = preds_flat[offset:offset + length]
119
- offset += length
120
- collapsed = torch.unique_consecutive(seq)
121
- collapsed = collapsed[collapsed != 0] - 1 # remove blank, shift to LLM token IDs
122
- if collapsed.numel() > 0:
123
- text = self.llm_tokenizer.decode(collapsed.tolist(), skip_special_tokens=True)
124
- else:
125
- text = " "
126
- text = text.strip().lower() or " "
127
- text_ctc_preds.append(text)
128
- return text_ctc_preds
129
-
130
- def _build_flat_llm_inputs(
131
- self,
132
- text_ctc_preds: List[str],
133
- projected_lengths: List[int],
134
- encoder_embs: torch.Tensor,
135
- ):
136
- """
137
- Build a flat (pad-free) LLM input sequence and position IDs.
138
-
139
- Layout per sample: [audio_embeds_i, text_embeds_i]
140
- All samples are concatenated into a single flat sequence.
141
-
142
- Returns:
143
- flat_embeds: [1, N_total, D] — ready for LLM forward
144
- flat_position_ids: [1, N_total] — per-sample cumulative positions
145
- text_lengths: List[int] — length of each text segment (for slicing output)
146
- """
147
- projected = self.projector(encoder_embs)
148
- if self.config.scale_projected_embeddings and hasattr(self.llm.config, "embedding_multiplier"):
149
- projected = projected / self.llm.config.embedding_multiplier
150
- projected = projected.to(self.llm.model.embed_tokens.weight.dtype)
151
-
152
- pred_text_llm_tokens = self.llm_tokenizer(text_ctc_preds)
153
- text_ids_with_slots = [
154
- self.add_insertion_slots(torch.tensor(x, device=encoder_embs.device))
155
- for x in pred_text_llm_tokens.input_ids
156
- ]
157
-
158
- embed_tokens = self.llm.model.embed_tokens
159
- embeds_list = []
160
- position_ids_list = []
161
- text_lengths = []
162
-
163
- for i, audio_len in enumerate(projected_lengths):
164
- audio_embs = projected[i, :audio_len] # [audio_len, D]
165
- text_embs = embed_tokens(text_ids_with_slots[i]) # [text_len, D]
166
- sample_embeds = torch.cat([audio_embs, text_embs], dim=0)
167
- embeds_list.append(sample_embeds)
168
- position_ids_list.append(torch.arange(sample_embeds.shape[0], device=encoder_embs.device))
169
- text_lengths.append(text_ids_with_slots[i].shape[0])
170
-
171
- flat_embeds = torch.cat(embeds_list, dim=0).unsqueeze(0) # [1, N_total, D]
172
- flat_position_ids = torch.cat(position_ids_list, dim=0).unsqueeze(0) # [1, N_total]
173
-
174
- return flat_embeds, flat_position_ids, text_lengths
175
-
176
- def forward(
177
- self,
178
- *,
179
- input_features: Optional[torch.Tensor] = None,
180
- attention_mask: Optional[torch.Tensor] = None,
181
- ) -> NLENARDecoderOutput:
182
-
183
- need_hidden_states = self.config.encoder_layer_indices != [-1]
184
- enc_out = self.encoder(
185
- input_features=input_features,
186
- attention_mask=attention_mask,
187
- output_hidden_states=need_hidden_states,
188
- )
189
-
190
- encoder_logits = enc_out.logits
191
- encoder_logits_bpe = enc_out.logits_bpe
192
-
193
- if enc_out.all_hidden_states is not None and len(self.config.encoder_layer_indices) > 0:
194
- selected_list = [enc_out.all_hidden_states[idx] for idx in self.config.encoder_layer_indices]
195
- encoder_embs = torch.cat(selected_list, dim=-1)
196
- else:
197
- encoder_embs = enc_out.last_hidden_state
198
- enc_out = None
199
-
200
- if attention_mask is None:
201
- attention_mask = torch.ones_like(encoder_logits[..., 0], dtype=torch.bool)
202
-
203
- encoder_lengths = attention_mask.sum(dim=1)
204
- projected_lengths = (encoder_lengths // self.config.projector_config.downsample_rate).cpu().tolist()
205
-
206
- if encoder_logits_bpe is not None:
207
- pool_window = self.encoder.config.bpe_pooling_window
208
- bpe_lengths = (-(encoder_lengths // -pool_window)).tolist() # ceil division
209
- text_ctc_preds = self._decode_bpe_ctc_greedy(encoder_logits_bpe, bpe_lengths)
210
- encoder_logits_bpe = None
211
- else:
212
- text_ctc_preds = self._decode_encoder_greedy(encoder_logits, attention_mask)
213
-
214
- assert self.config.attn_implementation == "flash_attention_2", (
215
- "NLENARDecoder requires flash_attention_2. Other backends produce "
216
- "causal attention even though is_causal=False"
217
- )
218
-
219
- flat_embeds, flat_position_ids, text_lengths = self._build_flat_llm_inputs(
220
- text_ctc_preds, projected_lengths, encoder_embs
221
- )
222
- encoder_embs = None
223
-
224
- # Run LLM backbone (without lm_head) on the full flat sequence
225
- llm_hidden = self.llm.model(
226
- inputs_embeds=flat_embeds,
227
- position_ids=flat_position_ids,
228
- use_cache=False,
229
- ).last_hidden_state.squeeze(0) # [N_total, D]
230
- flat_embeds = None
231
-
232
- # Slice text hidden states, apply lm_head once on the concatenation
233
- # Flat layout: [audio_0, text_0, audio_1, text_1, ...]
234
- text_hidden_list = []
235
- offset = 0
236
- for i in range(len(projected_lengths)):
237
- offset += projected_lengths[i]
238
- text_hidden_list.append(llm_hidden[offset:offset + text_lengths[i]])
239
- offset += text_lengths[i]
240
- llm_hidden = None
241
-
242
- text_logits = self.llm.lm_head(torch.cat(text_hidden_list)) # [N_text_total, V]
243
- editing_logits_list = text_logits.split(text_lengths)
244
-
245
- return NLENARDecoderOutput(
246
- editing_logits=editing_logits_list,
247
- encoder_logits=encoder_logits,
248
- text_ctc_preds=text_ctc_preds,
249
- )
250
-
251
- @torch.inference_mode()
252
- def generate(self, input_features, attention_mask):
253
- """Single-pass inference: forward + argmax decoding."""
254
- output = self.forward(input_features=input_features, attention_mask=attention_mask)
255
-
256
- eos_id = self.llm.config.eos_token_id
257
- text_preds = []
258
- for logits in output.editing_logits:
259
- cur_pred = torch.unique_consecutive(logits.argmax(-1))
260
- cur_pred = cur_pred[cur_pred != eos_id]
261
- text_preds.append(self.llm_tokenizer.decode(cur_pred, skip_special_tokens=True))
262
-
263
- return NLENARDecoderOutput(
264
- text_preds=text_preds,
265
- text_ctc_preds=output.text_ctc_preds,
266
- encoder_logits=output.encoder_logits,
267
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
modeling_projector.py DELETED
@@ -1,147 +0,0 @@
1
- import torch
2
- import torch.nn as nn
3
-
4
- from .configuration_nle import NLEProjectorConfig
5
-
6
-
7
- class QFormerCrossAttention(nn.Module):
8
- def __init__(self, config: NLEProjectorConfig):
9
- super().__init__()
10
- self.num_heads = config.num_heads
11
- self.head_dim = config.hidden_size // config.num_heads
12
- self.hidden_size = config.hidden_size
13
-
14
- self.q_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=config.attn_bias)
15
- self.k_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=config.attn_bias)
16
- self.v_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=config.attn_bias)
17
- self.o_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=config.attn_bias)
18
-
19
- def forward(self, hidden_states, encoder_hidden_states):
20
- batch_size, query_len, _ = hidden_states.shape
21
- encoder_len = encoder_hidden_states.shape[1]
22
-
23
- query_states = self.q_proj(hidden_states).view(
24
- batch_size, query_len, self.num_heads, self.head_dim
25
- ).transpose(1, 2)
26
- key_states = self.k_proj(encoder_hidden_states).view(
27
- batch_size, encoder_len, self.num_heads, self.head_dim
28
- ).transpose(1, 2)
29
- value_states = self.v_proj(encoder_hidden_states).view(
30
- batch_size, encoder_len, self.num_heads, self.head_dim
31
- ).transpose(1, 2)
32
- with torch.nn.attention.sdpa_kernel([
33
- torch.nn.attention.SDPBackend.FLASH_ATTENTION,
34
- torch.nn.attention.SDPBackend.EFFICIENT_ATTENTION,
35
- torch.nn.attention.SDPBackend.MATH,
36
- ]):
37
- attn_output = torch.nn.functional.scaled_dot_product_attention(
38
- query_states, key_states, value_states, is_causal=False,
39
- )
40
-
41
- attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, query_len, self.hidden_size)
42
- return self.o_proj(attn_output)
43
-
44
-
45
- class QFormerMLP(nn.Module):
46
- def __init__(self, config: NLEProjectorConfig):
47
- super().__init__()
48
- mlp_hidden_size = int(config.hidden_size * config.mlp_ratio)
49
- self.fc1 = nn.Linear(config.hidden_size, mlp_hidden_size, bias=config.mlp_bias)
50
- self.act = nn.SiLU()
51
- self.fc2 = nn.Linear(mlp_hidden_size, config.hidden_size, bias=config.mlp_bias)
52
-
53
- def forward(self, hidden_states):
54
- return self.fc2(self.act(self.fc1(hidden_states)))
55
-
56
-
57
- class QFormerLayer(nn.Module):
58
- def __init__(self, config: NLEProjectorConfig):
59
- super().__init__()
60
- self.attn_norm = nn.LayerNorm(config.hidden_size, eps=config.layernorm_eps)
61
- self.cross_attention = QFormerCrossAttention(config)
62
- self.mlp_norm = nn.LayerNorm(config.hidden_size, eps=config.layernorm_eps)
63
- self.mlp = QFormerMLP(config)
64
-
65
- def forward(self, hidden_states, encoder_hidden_states):
66
- hidden_states = hidden_states + self.cross_attention(
67
- self.attn_norm(hidden_states), encoder_hidden_states
68
- )
69
- hidden_states = hidden_states + self.mlp(self.mlp_norm(hidden_states))
70
- return hidden_states
71
-
72
-
73
- class SimplifiedQFormer(nn.Module):
74
- def __init__(self, config: NLEProjectorConfig):
75
- super().__init__()
76
- self.layers = nn.ModuleList([
77
- QFormerLayer(config) for _ in range(config.num_layers)
78
- ])
79
-
80
- def forward(self, query_embeds, encoder_hidden_states):
81
- hidden_states = query_embeds
82
- for layer in self.layers:
83
- hidden_states = layer(hidden_states, encoder_hidden_states)
84
- return hidden_states
85
-
86
-
87
- class EncoderProjectorQFormer(nn.Module):
88
- def __init__(self, config: NLEProjectorConfig):
89
- super().__init__()
90
- self.config = config
91
-
92
- self.layer_norms = nn.ModuleList([
93
- nn.LayerNorm(config.encoder_dim, eps=config.layernorm_eps)
94
- for _ in range(config.num_encoder_layers)
95
- ])
96
-
97
- self.layer_projector = nn.Linear(
98
- config.encoder_dim * config.num_encoder_layers, config.hidden_size
99
- )
100
- self.dropout = nn.Dropout(config.dropout_prob)
101
- self.projector_act = nn.GELU()
102
-
103
- self.qformer = SimplifiedQFormer(config)
104
-
105
- query_length = config.block_size // config.downsample_rate
106
- embed_std = config.hidden_size ** -0.5
107
- self.query = nn.Parameter(
108
- torch.randn(1, query_length, config.hidden_size) * embed_std
109
- )
110
- self.window_positions = nn.Parameter(
111
- torch.randn(1, config.block_size, config.hidden_size) * embed_std
112
- )
113
- self.out_norm = nn.LayerNorm(config.hidden_size, eps=config.layernorm_eps)
114
- self.out_linear = nn.Linear(config.hidden_size, config.llm_dim)
115
-
116
- def forward(self, x):
117
- batch_size, seq_len, dim = x.size()
118
-
119
- x = x.view(batch_size, seq_len, self.config.num_encoder_layers, self.config.encoder_dim)
120
- normalized_layers = []
121
- for i, layer_norm in enumerate(self.layer_norms):
122
- normalized_layers.append(layer_norm(x[:, :, i]))
123
- x = torch.cat(normalized_layers, dim=-1)
124
-
125
- x = self.projector_act(self.layer_projector(x))
126
-
127
- block_size = self.config.block_size
128
- nblocks = seq_len // block_size
129
- rest = seq_len % block_size
130
- if rest > 0:
131
- x = nn.functional.pad(x, (0, 0, 0, block_size - rest), 'constant', 0)
132
- nblocks += 1
133
-
134
- x = x.view(batch_size * nblocks, block_size, self.config.hidden_size)
135
- query_length = self.query.shape[1]
136
- mean_pool = x.view(
137
- batch_size * nblocks, query_length, self.config.downsample_rate, self.config.hidden_size
138
- ).mean(dim=-2)
139
-
140
- query_output = self.qformer(
141
- query_embeds=self.dropout(self.query + mean_pool),
142
- encoder_hidden_states=self.dropout(x + self.window_positions),
143
- )
144
-
145
- query_output = query_output.view(batch_size, nblocks * query_length, -1)
146
- query_output = self.dropout(self.out_norm(query_output))
147
- return self.out_linear(query_output)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
preprocessor_config.json CHANGED
@@ -1,8 +1,8 @@
1
  {
2
  "auto_map": {
3
- "AutoFeatureExtractor": "feature_extraction_nle.NLEFeatureExtractor"
4
  },
5
- "feature_extractor_type": "NLEFeatureExtractor",
6
  "hop_length": 160,
7
  "n_fft": 512,
8
  "n_mels": 80,
 
1
  {
2
  "auto_map": {
3
+ "AutoFeatureExtractor": "feature_extraction_granite_speech_nar.GraniteSpeechNarFeatureExtractor"
4
  },
5
+ "feature_extractor_type": "GraniteSpeechNarFeatureExtractor",
6
  "hop_length": 160,
7
  "n_fft": 512,
8
  "n_mels": 80,
processing_granite_speech_nar.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 IBM and The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """Processor for Granite Speech NAR."""
15
+
16
+ from transformers.processing_utils import ProcessorMixin
17
+ from transformers.tokenization_utils_base import AudioInput
18
+ from transformers.utils import is_torch_available
19
+ from .feature_extraction_granite_speech_nar import GraniteSpeechNarFeatureExtractor
20
+
21
+
22
+ if is_torch_available():
23
+ import torch
24
+
25
+
26
+ class GraniteSpeechNarProcessor(ProcessorMixin):
27
+ """Processor combining audio feature extraction and tokenizer for GraniteSpeechNar."""
28
+
29
+ feature_extractor_class = "AutoFeatureExtractor"
30
+ tokenizer_class = "AutoTokenizer"
31
+
32
+ def __init__(self, feature_extractor: GraniteSpeechNarFeatureExtractor, tokenizer=None, **kwargs):
33
+ super().__init__(feature_extractor=feature_extractor, tokenizer=tokenizer, **kwargs)
34
+
35
+ def __call__(
36
+ self,
37
+ audios: AudioInput,
38
+ device: str | None = None,
39
+ **kwargs,
40
+ ) -> dict:
41
+ return self.feature_extractor(audios, device=device)
42
+
43
+ def batch_decode(self, token_ids_list: list["torch.Tensor"], **kwargs) -> list[str]:
44
+ if self.tokenizer is None:
45
+ raise ValueError("Tokenizer not set. Pass tokenizer to GraniteSpeechNarProcessor.")
46
+ return [self.tokenizer.decode(ids, skip_special_tokens=True) for ids in token_ids_list]
47
+
48
+
49
+ __all__ = ["GraniteSpeechNarProcessor"]
processor_config.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "processor_class": "GraniteSpeechNarProcessor",
3
+ "auto_map": {
4
+ "AutoProcessor": "processing_granite_speech_nar.GraniteSpeechNarProcessor"
5
+ }
6
+ }
tokenizer.py DELETED
@@ -1,30 +0,0 @@
1
- """Minimal CTC tokenizer for Granite Speech."""
2
-
3
- import numpy as np
4
-
5
-
6
- class Tokenizer:
7
- """
8
- CTC tokenizer with char2idx mapping. Index 0 is always blank.
9
- Default vocab: latin256_kana92 (348 tokens).
10
- """
11
-
12
- def __init__(self, char2idx=None, **kwargs):
13
- if char2idx is None:
14
- # Default: latin256_kana92
15
- char2idx = {chr(n): n for n in range(32, 256)}
16
- char2idx |= {chr(0x30A1 + n): 256 + n for n in range(92)}
17
-
18
- # char2idx values may be strings after JSON roundtrip
19
- self.char2idx = {k: int(v) for k, v in char2idx.items()}
20
- self.idx2char = {v: k for k, v in self.char2idx.items()}
21
- self.vocab_size = len(self.char2idx) + 1
22
-
23
- def encode(self, text: str) -> np.ndarray:
24
- return np.array([self.char2idx[c] for c in text if c in self.char2idx], dtype=np.int64)
25
-
26
- def decode(self, tokens: np.ndarray) -> str:
27
- """Decode CTC output: unique_consecutive + remove blanks."""
28
- pred = tokens[np.insert(tokens[1:] != tokens[:-1], 0, True)]
29
- pred = pred[pred != 0]
30
- return "".join([self.idx2char[idx] for idx in pred.tolist()])