nullHawk commited on
Commit
c22516b
·
verified ·
1 Parent(s): 539391b

refactor: rename TinyAya to RumikOSS

Browse files

Renames the model and config classes, the modules that hold them, and
model_type. Weight tensor names are unchanged, so the safetensors shards
and their index are untouched.

Also folds in three behaviour changes to the model class:

- _sample is renamed to _constrained_sample. It was shadowing
GenerationMixin._sample, so every call to model.generate() failed with
a TypeError on logits_processor.
- allowed_ids is now optional and derived from the config, removing the
need to regex-scan the tokenizer vocabulary for unit tokens.
- audio_tokens_to_codes() regroups generated ids into [1, 8, T] codec
frames without pulling in the codec.

config.json gains the audio vocabulary layout (first_unit_id 261008,
last_unit_id 277391, text_start_token_id 277392, frame_rate_hz 12.5),
verified against this repository's own tokenizer.

---

Mirrors the changes made to rumik-oss 1, so the two repositories
present the same API.

Renames the model and config classes to RumikOSS, moves the audio token
layout into `config.json`, unblocks `model.generate()`, adds a chat
template matching the trained prompt layout, and adds LICENSE.

Weight files are not touched: tensor names are unchanged, so the
safetensors shards and their index are identical.

Verified against the published weights on an RTX PRO 6000:

- loads on transformers 5.16.1 as RumikOSSForCausalLM, vocab 277,395
- the chat template is token-exact vs the prompt `inference.py` built
- audio generates and decodes for English and Hindi
- `inference.py` writes a wav and rejects an unknown speaker

config.json CHANGED
@@ -1,21 +1,54 @@
1
  {
2
- "vocab_size": 277395,
3
- "max_position_embeddings": 500000,
4
- "hidden_size": 2048,
5
- "logit_scale": 1.0,
6
- "intermediate_size": 11008,
7
- "num_hidden_layers": 36,
8
- "num_attention_heads": 16,
9
- "num_key_value_heads": 4,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  "hidden_act": "silu",
 
 
 
 
 
11
  "initializer_range": 0.02,
 
 
 
 
 
 
 
 
12
  "layer_norm_eps": 1e-05,
13
- "use_cache": true,
14
- "rope_theta": 50000,
15
- "rope_scaling": null,
16
- "attention_bias": false,
17
- "attention_dropout": 0.0,
18
- "sliding_window": 4096,
19
  "layer_types": [
20
  "sliding_attention",
21
  "sliding_attention",
@@ -54,94 +87,64 @@
54
  "sliding_attention",
55
  "full_attention"
56
  ],
57
- "head_dim": 128,
58
- "return_dict": true,
59
- "output_hidden_states": false,
60
- "torchscript": false,
61
- "dtype": "bfloat16",
62
- "pruned_heads": {},
63
- "tie_word_embeddings": true,
64
- "chunk_size_feed_forward": 0,
65
- "is_encoder_decoder": false,
66
- "is_decoder": false,
67
- "cross_attention_hidden_size": null,
68
- "add_cross_attention": false,
69
- "tie_encoder_decoder": false,
70
- "architectures": [
71
- "TinyAyaForCausalLM"
72
- ],
73
- "finetuning_task": null,
74
- "id2label": {
75
- "0": "LABEL_0",
76
- "1": "LABEL_1"
77
- },
78
- "label2id": {
79
- "LABEL_0": 0,
80
- "LABEL_1": 1
81
- },
82
- "task_specific_params": null,
83
- "problem_type": null,
84
- "tokenizer_class": null,
85
- "prefix": null,
86
- "bos_token_id": 2,
87
- "pad_token_id": 0,
88
- "eos_token_id": 3,
89
- "sep_token_id": null,
90
- "decoder_start_token_id": null,
91
  "max_length": 20,
 
92
  "min_length": 0,
93
- "do_sample": false,
94
- "early_stopping": false,
95
- "num_beams": 1,
96
- "temperature": 1.0,
97
- "top_k": 50,
98
- "top_p": 1.0,
99
- "typical_p": 1.0,
100
- "repetition_penalty": 1.0,
101
- "length_penalty": 1.0,
102
  "no_repeat_ngram_size": 0,
103
- "encoder_no_repeat_ngram_size": 0,
104
- "bad_words_ids": null,
105
- "num_return_sequences": 1,
106
- "output_scores": false,
107
- "return_dict_in_generate": false,
108
- "forced_bos_token_id": null,
109
- "forced_eos_token_id": null,
110
- "remove_invalid_values": false,
111
- "exponential_decay_length_penalty": null,
112
- "suppress_tokens": null,
113
- "begin_suppress_tokens": null,
114
  "num_beam_groups": 1,
115
- "diversity_penalty": 0.0,
116
- "_sliding_window_pattern": 4,
117
- "cache_implementation": "hybrid",
118
- "layer_switch": 4,
119
- "model_type": "tinyaya_mimi",
120
  "order_of_interleaved_layers": "local_attn_first",
 
 
 
 
121
  "position_embedding_type": "rope_gptj",
 
 
 
 
 
 
 
 
 
122
  "rotary_pct": 1.0,
 
 
123
  "sliding_window_pattern": 4,
124
- "use_embedding_sharing": true,
125
- "use_gated_activation": true,
126
- "use_parallel_block": true,
127
- "use_parallel_embedding": false,
128
- "use_qk_norm": false,
129
- "tf_legacy_loss": false,
130
- "use_bfloat16": false,
131
- "output_attentions": false,
132
- "num_quantizers": 8,
133
- "codebook_size": 2048,
134
- "audio_start_token_id": 277393,
135
- "audio_end_token_id": 277394,
136
  "speakers": [
137
  "Ira",
138
  "Aisha",
139
  "Siya",
140
  "Zoya"
141
  ],
142
- "auto_map": {
143
- "AutoConfig": "configuration_tinyaya.TinyAyaConfig",
144
- "AutoModelForCausalLM": "modeling_tinyaya.TinyAyaForCausalLM"
145
- },
146
- "torch_dtype": "bfloat16"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  }
 
1
  {
2
+ "_sliding_window_pattern": 4,
3
+ "add_cross_attention": false,
4
+ "architectures": [
5
+ "RumikOSSForCausalLM"
6
+ ],
7
+ "attention_bias": false,
8
+ "attention_dropout": 0.0,
9
+ "audio_end_token_id": 277394,
10
+ "audio_start_token_id": 277393,
11
+ "auto_map": {
12
+ "AutoConfig": "configuration_rumik_oss.RumikOSSConfig",
13
+ "AutoModelForCausalLM": "modeling_rumik_oss.RumikOSSForCausalLM"
14
+ },
15
+ "bad_words_ids": null,
16
+ "begin_suppress_tokens": null,
17
+ "bos_token_id": 2,
18
+ "chunk_size_feed_forward": 0,
19
+ "codebook_size": 2048,
20
+ "cross_attention_hidden_size": null,
21
+ "decoder_start_token_id": null,
22
+ "diversity_penalty": 0.0,
23
+ "do_sample": false,
24
+ "dtype": "bfloat16",
25
+ "early_stopping": false,
26
+ "encoder_no_repeat_ngram_size": 0,
27
+ "eos_token_id": 3,
28
+ "exponential_decay_length_penalty": null,
29
+ "finetuning_task": null,
30
+ "first_unit_id": 261008,
31
+ "forced_bos_token_id": null,
32
+ "forced_eos_token_id": null,
33
+ "frame_rate_hz": 12.5,
34
+ "head_dim": 128,
35
  "hidden_act": "silu",
36
+ "hidden_size": 2048,
37
+ "id2label": {
38
+ "0": "LABEL_0",
39
+ "1": "LABEL_1"
40
+ },
41
  "initializer_range": 0.02,
42
+ "intermediate_size": 11008,
43
+ "is_decoder": false,
44
+ "is_encoder_decoder": false,
45
+ "label2id": {
46
+ "LABEL_0": 0,
47
+ "LABEL_1": 1
48
+ },
49
+ "last_unit_id": 277391,
50
  "layer_norm_eps": 1e-05,
51
+ "layer_switch": 4,
 
 
 
 
 
52
  "layer_types": [
53
  "sliding_attention",
54
  "sliding_attention",
 
87
  "sliding_attention",
88
  "full_attention"
89
  ],
90
+ "length_penalty": 1.0,
91
+ "logit_scale": 1.0,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  "max_length": 20,
93
+ "max_position_embeddings": 500000,
94
  "min_length": 0,
95
+ "model_type": "rumik_oss",
 
 
 
 
 
 
 
 
96
  "no_repeat_ngram_size": 0,
97
+ "num_attention_heads": 16,
 
 
 
 
 
 
 
 
 
 
98
  "num_beam_groups": 1,
99
+ "num_beams": 1,
100
+ "num_hidden_layers": 36,
101
+ "num_key_value_heads": 4,
102
+ "num_quantizers": 8,
103
+ "num_return_sequences": 1,
104
  "order_of_interleaved_layers": "local_attn_first",
105
+ "output_attentions": false,
106
+ "output_hidden_states": false,
107
+ "output_scores": false,
108
+ "pad_token_id": 0,
109
  "position_embedding_type": "rope_gptj",
110
+ "prefix": null,
111
+ "problem_type": null,
112
+ "pruned_heads": {},
113
+ "remove_invalid_values": false,
114
+ "repetition_penalty": 1.0,
115
+ "return_dict": true,
116
+ "return_dict_in_generate": false,
117
+ "rope_scaling": null,
118
+ "rope_theta": 50000,
119
  "rotary_pct": 1.0,
120
+ "sep_token_id": null,
121
+ "sliding_window": 4096,
122
  "sliding_window_pattern": 4,
 
 
 
 
 
 
 
 
 
 
 
 
123
  "speakers": [
124
  "Ira",
125
  "Aisha",
126
  "Siya",
127
  "Zoya"
128
  ],
129
+ "suppress_tokens": null,
130
+ "task_specific_params": null,
131
+ "temperature": 1.0,
132
+ "text_start_token_id": 277392,
133
+ "tf_legacy_loss": false,
134
+ "tie_encoder_decoder": false,
135
+ "tie_word_embeddings": true,
136
+ "tokenizer_class": null,
137
+ "top_k": 50,
138
+ "top_p": 1.0,
139
+ "torch_dtype": "bfloat16",
140
+ "torchscript": false,
141
+ "typical_p": 1.0,
142
+ "use_bfloat16": false,
143
+ "use_cache": true,
144
+ "use_embedding_sharing": true,
145
+ "use_gated_activation": true,
146
+ "use_parallel_block": true,
147
+ "use_parallel_embedding": false,
148
+ "use_qk_norm": false,
149
+ "vocab_size": 277395
150
  }
configuration_rumik_oss.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Configuration for rumik-oss 1."""
2
+
3
+ from transformers import Cohere2Config
4
+
5
+
6
+ class RumikOSSConfig(Cohere2Config):
7
+ """rumik-oss 1: tiny aya fire extended with flattened Mimi codec tokens.
8
+
9
+ Beyond the base Cohere2 fields, this records where the audio vocabulary
10
+ lives so that callers can map token ids to codec (code, quantizer) pairs
11
+ arithmetically, without loading the tokenizer or scanning its vocabulary.
12
+
13
+ The unit tokens are laid out code-major, quantizer-minor::
14
+
15
+ <0_0> <0_1> ... <0_7> <1_0> ... <2047_7>
16
+
17
+ so for any id in ``[first_unit_id, last_unit_id]``::
18
+
19
+ code = (token_id - first_unit_id) // num_quantizers
20
+ quantizer = (token_id - first_unit_id) % num_quantizers
21
+ """
22
+
23
+ model_type = "rumik_oss"
24
+
25
+ def __init__(
26
+ self,
27
+ num_quantizers=8,
28
+ codebook_size=2048,
29
+ audio_start_token_id=None,
30
+ audio_end_token_id=None,
31
+ text_start_token_id=None,
32
+ first_unit_id=None,
33
+ last_unit_id=None,
34
+ frame_rate_hz=12.5,
35
+ speakers=None,
36
+ **kwargs,
37
+ ):
38
+ super().__init__(**kwargs)
39
+ self.num_quantizers = int(num_quantizers)
40
+ self.codebook_size = int(codebook_size)
41
+ self.audio_start_token_id = audio_start_token_id
42
+ self.audio_end_token_id = audio_end_token_id
43
+ self.text_start_token_id = text_start_token_id
44
+ self.first_unit_id = first_unit_id
45
+ # Derivable, but stored so a hand-edited config stays self-consistent.
46
+ if last_unit_id is None and first_unit_id is not None:
47
+ last_unit_id = int(first_unit_id) + self.codebook_size * self.num_quantizers - 1
48
+ self.last_unit_id = last_unit_id
49
+ self.frame_rate_hz = float(frame_rate_hz)
50
+ self.speakers = speakers or ["Ira", "Aisha", "Siya", "Zoya"]
51
+
52
+ @property
53
+ def audio_tokens_per_second(self) -> float:
54
+ """Tokens the model emits per second of audio (8 x 12.5 = 100)."""
55
+ return self.frame_rate_hz * self.num_quantizers
configuration_tinyaya.py DELETED
@@ -1,21 +0,0 @@
1
- from transformers import Cohere2Config
2
-
3
-
4
- class TinyAyaConfig(Cohere2Config):
5
- model_type = "tinyaya_mimi"
6
-
7
- def __init__(
8
- self,
9
- num_quantizers=8,
10
- codebook_size=2048,
11
- audio_start_token_id=None,
12
- audio_end_token_id=None,
13
- speakers=None,
14
- **kwargs,
15
- ):
16
- super().__init__(**kwargs)
17
- self.num_quantizers = int(num_quantizers)
18
- self.codebook_size = int(codebook_size)
19
- self.audio_start_token_id = audio_start_token_id
20
- self.audio_end_token_id = audio_end_token_id
21
- self.speakers = speakers or ["Ira", "Aisha", "Siya", "Zoya"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
modeling_tinyaya.py → modeling_rumik_oss.py RENAMED
@@ -1,14 +1,23 @@
 
 
 
 
 
 
 
 
 
1
  from __future__ import annotations
2
 
3
  import torch
4
  from torch import nn
5
  from transformers.models.cohere2.modeling_cohere2 import Cohere2ForCausalLM
6
 
7
- from .configuration_tinyaya import TinyAyaConfig
8
 
9
 
10
- class TinyAyaForCausalLM(Cohere2ForCausalLM):
11
- config_class = TinyAyaConfig
12
 
13
  def __init__(self, config):
14
  super().__init__(config)
@@ -20,8 +29,55 @@ class TinyAyaForCausalLM(Cohere2ForCausalLM):
20
  nn.Linear(max(64, hidden // 4), 1),
21
  )
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  @staticmethod
24
- def _sample(scores, do_sample, temperature, top_k):
 
 
 
25
  if not do_sample:
26
  return scores.argmax(dim=-1, keepdim=True)
27
  scores = scores / max(float(temperature), 1e-5)
@@ -36,13 +92,19 @@ class TinyAyaForCausalLM(Cohere2ForCausalLM):
36
  self,
37
  input_ids,
38
  attention_mask,
39
- allowed_ids,
40
  max_new_tokens=2048,
41
  min_new_tokens=8,
42
  temperature=0.8,
43
  top_k=30,
44
  do_sample=True,
45
  ):
 
 
 
 
 
 
46
  out = self(
47
  input_ids=input_ids,
48
  attention_mask=attention_mask,
@@ -67,7 +129,7 @@ class TinyAyaForCausalLM(Cohere2ForCausalLM):
67
  restricted.index_copy_(1, allowed_ids, scores.index_select(1, allowed_ids))
68
  if step < int(min_new_tokens):
69
  restricted[:, audio_end_id] = torch.finfo(scores.dtype).min
70
- token = self._sample(restricted, do_sample, temperature, top_k)
71
  emitted = torch.cat((emitted, token), dim=1)
72
  if bool((token == audio_end_id).all()):
73
  break
 
1
+ """rumik-oss 1: text -> flattened Mimi codec tokens.
2
+
3
+ This model produces *audio tokens*, not waveforms. Decoding them to audio is
4
+ the caller's job and needs the Mimi codec (shipped in ``codec/``)::
5
+
6
+ ids = model.generate_audio(**inputs) # token ids
7
+ codes = model.audio_tokens_to_codes(ids) # [1, 8, T], ready for Mimi
8
+ wav = mimi.decode(codes).audio_values # your call, your decoder
9
+ """
10
  from __future__ import annotations
11
 
12
  import torch
13
  from torch import nn
14
  from transformers.models.cohere2.modeling_cohere2 import Cohere2ForCausalLM
15
 
16
+ from .configuration_rumik_oss import RumikOSSConfig
17
 
18
 
19
+ class RumikOSSForCausalLM(Cohere2ForCausalLM):
20
+ config_class = RumikOSSConfig
21
 
22
  def __init__(self, config):
23
  super().__init__(config)
 
29
  nn.Linear(max(64, hidden // 4), 1),
30
  )
31
 
32
+ # ---- audio vocabulary -------------------------------------------------
33
+ def audio_token_ids(self, device=None) -> torch.Tensor:
34
+ """Every id the model may legally emit inside an <audio> span.
35
+
36
+ Derived from the config, so callers no longer have to scan the
37
+ tokenizer vocabulary for ``<code>_<quantizer>`` tokens.
38
+ """
39
+ c = self.config
40
+ if c.first_unit_id is None:
41
+ raise ValueError(
42
+ "config.first_unit_id is unset; pass allowed_ids explicitly or "
43
+ "add first_unit_id/last_unit_id to config.json.")
44
+ ids = torch.arange(int(c.first_unit_id), int(c.last_unit_id) + 1, dtype=torch.long)
45
+ end = torch.tensor([int(c.audio_end_token_id)], dtype=torch.long)
46
+ return torch.cat((ids, end)).to(device if device is not None else self.device)
47
+
48
+ def audio_tokens_to_codes(self, token_ids, *, drop_partial_frame=True) -> torch.Tensor:
49
+ """Generated ids -> ``[1, num_quantizers, num_frames]`` codec codes.
50
+
51
+ Shaped for ``MimiModel.decode``. Purely arithmetic -- no tokenizer, no
52
+ codec, no audio. Anything outside the unit range (delimiters, stray
53
+ text tokens) ends the audio span.
54
+ """
55
+ c = self.config
56
+ first, last, Q = int(c.first_unit_id), int(c.last_unit_id), int(c.num_quantizers)
57
+ if torch.is_tensor(token_ids):
58
+ token_ids = token_ids.flatten().tolist()
59
+
60
+ codes: list[int] = []
61
+ for pos, tid in enumerate(int(t) for t in token_ids):
62
+ if not first <= tid <= last:
63
+ break # </audio> or anything else: done
64
+ if (tid - first) % Q != pos % Q:
65
+ break # off the round robin: stop cleanly
66
+ codes.append((tid - first) // Q)
67
+
68
+ usable = len(codes) - (len(codes) % Q) if drop_partial_frame else len(codes)
69
+ if usable < Q:
70
+ raise ValueError(
71
+ f"no complete codec frame in {len(token_ids)} tokens "
72
+ f"(need at least {Q}, got {usable})")
73
+ return torch.tensor(codes[:usable], dtype=torch.long).reshape(-1, Q).T.unsqueeze(0)
74
+
75
+ # ---- generation -------------------------------------------------------
76
  @staticmethod
77
+ def _constrained_sample(scores, do_sample, temperature, top_k):
78
+ # NB: deliberately not named `_sample`. `GenerationMixin._sample` is the
79
+ # method `generate()` dispatches to, and shadowing it makes every call
80
+ # to `generate()` fail with a TypeError on `logits_processor`.
81
  if not do_sample:
82
  return scores.argmax(dim=-1, keepdim=True)
83
  scores = scores / max(float(temperature), 1e-5)
 
92
  self,
93
  input_ids,
94
  attention_mask,
95
+ allowed_ids=None,
96
  max_new_tokens=2048,
97
  min_new_tokens=8,
98
  temperature=0.8,
99
  top_k=30,
100
  do_sample=True,
101
  ):
102
+ """Autoregressively emit audio tokens, ending at ``</audio>``.
103
+
104
+ ``allowed_ids`` defaults to the audio vocabulary from the config.
105
+ """
106
+ if allowed_ids is None:
107
+ allowed_ids = self.audio_token_ids(device=input_ids.device)
108
  out = self(
109
  input_ids=input_ids,
110
  attention_mask=attention_mask,
 
129
  restricted.index_copy_(1, allowed_ids, scores.index_select(1, allowed_ids))
130
  if step < int(min_new_tokens):
131
  restricted[:, audio_end_id] = torch.finfo(scores.dtype).min
132
+ token = self._constrained_sample(restricted, do_sample, temperature, top_k)
133
  emitted = torch.cat((emitted, token), dim=1)
134
  if bool((token == audio_end_id).all()):
135
  break