Views
No views yet
1import torch
2from torch import nn
3from transformers import VoxtralForConditionalGeneration
4from transformers.cache_utils import DynamicCache
5import os
6import onnx
7
8model_id = "mistralai/Voxtral-Mini-3B-2507"
9device = "cuda" if torch.cuda.is_available() else "cpu"
10torch_dtype = torch.float16 if torch.cuda.is_available() else torch.float32
11
12model = VoxtralForConditionalGeneration.from_pretrained(
13 model_id,
14 torch_dtype=torch_dtype,
15 low_cpu_mem_usage=True,
16 use_safetensors=True,
17 attn_implementation="eager",
18)
19model.to(device)
20model.eval()
21
22class DecoderONNXWrapper(nn.Module):
23 def __init__(self, language_model):
24 super().__init__()
25 self.language_model = language_model
26
27 def forward(self, inputs_embeds, attention_mask, *past_key_value_tensors):
28 num_layers = self.language_model.config.num_hidden_layers
29 legacy_past = tuple(
30 (past_key_value_tensors[i*2], past_key_value_tensors[i*2+1]) for i in range(num_layers)
31 )
32 past_key_values_cache = DynamicCache.from_legacy_cache(past_key_values=legacy_past)
33
34 outputs = self.language_model(
35 input_ids=None,
36 inputs_embeds=inputs_embeds,
37 attention_mask=attention_mask,
38 past_key_values=past_key_values_cache,
39 output_attentions=True,
40 use_cache=True,
41 )
42
43 flat_outputs = [outputs.logits]
44 for k, v in zip(outputs.past_key_values.key_cache, outputs.past_key_values.value_cache):
45 flat_outputs.extend([k, v])
46 for attn in outputs.attentions:
47 flat_outputs.append(attn)
48 return tuple(flat_outputs)
49
50batch_size = 1
51seq_len = 128
52past_seq_len = 100
53text_config = model.config.text_config
54num_layers = text_config.num_hidden_layers
55hidden_size = text_config.hidden_size
56head_dim = text_config.head_dim
57num_kv_heads = text_config.num_key_value_heads
58
59inputs_embeds = torch.randn((batch_size, seq_len, hidden_size), dtype=torch_dtype, device=device)
60attention_mask_4d = torch.ones((batch_size, 1, seq_len, past_seq_len + seq_len), dtype=torch_dtype, device=device)
61past_key_value_flat_tuple = tuple(
62 torch.randn((batch_size, num_kv_heads, past_seq_len, head_dim), dtype=torch_dtype, device=device)
63 for _ in range(num_layers * 2)
64)
65dummy_inputs = (inputs_embeds, attention_mask_4d) + past_key_value_flat_tuple
66
67output_path = "decoder_model_attentive_unpacked.onnx"
68input_names = ["inputs_embeds", "attention_mask"] + [f"past_key_values.{i}.{kv}" for i in range(num_layers) for kv in ["key", "value"]]
69output_names = ["logits"] + [f"present.{i}.{kv}" for i in range(num_layers) for kv in ["key", "value"]] + [f"attention.{i}" for i in range(num_layers)]
70
71dynamic_axes = {
72 "inputs_embeds": {1: "sequence_length"},
73 "attention_mask": {2: "sequence_length", 3: "total_sequence_length"},
74}
75for name in input_names + output_names:
76 if "key" in name or "value" in name:
77 dynamic_axes[name] = {2: "past_sequence_length"} if "past" in name else {2: "total_sequence_length"}
78 elif "attention" in name:
79 dynamic_axes[name] = {2: "sequence_length", 3: "total_sequence_length"}
80
81wrapped_model = DecoderONNXWrapper(model.language_model)
82wrapped_model.eval()
83
84with torch.no_grad():
85 torch.onnx.export(
86 wrapped_model,
87 dummy_inputs,
88 output_path,
89 input_names=input_names,
90 output_names=output_names,
91 dynamic_axes=dynamic_axes,
92 opset_version=17,
93 )
94
95import onnx
96
97onnx_model = onnx.load(output_path, load_external_data=True)
98
99data_file_location = "decoder_model_attentive.onnx_data"
100
101onnx.save_model(
102 onnx_model,
103 "decoder_model_attentive.onnx",
104 save_as_external_data=True,
105 all_tensors_to_one_file=True,
106 location=data_file_location,
107)1for fname in os.listdir("."):
2 if fname.startswith("language_") or fname.startswith("onnx_"):
3 os.remove(os.path.join(my_dir, fname))
41import os
2from neural_compressor import PostTrainingQuantConfig, quantization
3from neural_compressor.utils.constant import FP32
4import onnx
5import logging
6logging.basicConfig(level=logging.INFO)
7
8model_dir = "."
9model_fp32 = 'decoder_model_attentive.onnx'
10model_quantized = 'decoder_model_attentive_q4_weight_only_inc.onnx'
11
12input_model_path = os.path.join(model_dir, model_fp32)
13output_model_path = os.path.join(model_dir, model_quantized)
14
15try:
16 if not onnx.checker.check_model(input_model_path):
17 print(f"Error: Original model '{input_model_path}' is not a valid ONNX model.")
18 exit()
19 print(f"Original model '{input_model_path}' is valid.")
20except Exception as e:
21 print(f"Failed to load or check original model '{input_model_path}': {e}")
22 print("Please ensure the original model file exists and is not corrupted.")
23 exit()
24
25config = PostTrainingQuantConfig(
26 approach="weight_only",
27 op_type_dict={
28 ".*": {
29 "weight": {
30 "bits": 4,
31 "algorithm": ["RTN"],
32 "scheme": ["asym"],
33 "group_size": 32,
34 }
35 }
36 },
37)
38
39print(f"\nAttempting to quantize '{model_fp32}' to 4-bit weight-only using Neural Compressor...")
40
41try:
42 q_model = quantization.fit(
43 input_model_path,
44 config,
45 )
46
47 q_model.save(output_model_path)
48 print(f"Model successfully quantized and saved to {output_model_path}")
49
50except Exception as e:
51 print(f"Error during Neural Compressor weight-only quantization: {e}")
52 print("Please ensure Neural Compressor is installed (`pip install neural_compressor`)")
53 print("and that your ONNX Runtime version is compatible.")*.onnx AND *.onnx_data)ipythonaudio.wav (any audio file you wish to transcribe)1import os
2import numpy as np
3import onnxruntime as ort
4from huggingface_hub import snapshot_download
5from tokenizers import Tokenizer
6import soundfile as sf
7import librosa
8import logging
9import matplotlib.pyplot as plt
10from IPython.display import display, Audio
11import torch
12
13def _create_4d_causal_attention_mask(input_shape, past_sequence_length, dtype=np.float32):
14 batch_size, sequence_length = input_shape
15 total_sequence_length = past_sequence_length + sequence_length
16
17 mask = np.tril(np.ones((total_sequence_length, total_sequence_length), dtype=np.bool_))
18 mask = mask[past_sequence_length:, :]
19
20 causal_mask = np.zeros((batch_size, 1, sequence_length, total_sequence_length), dtype=dtype)
21 causal_mask[:, :, :, :] = np.where(
22 mask[None, None, :, :], 0.0, np.finfo(dtype).min
23 )
24 return causal_mask
25
26repo_id = "onnx-community/Voxtral-Mini-3B-2507-ONNX"
27audio_file_path = "audio.wav"
28custom_decoder_path = "decoder_model_attentive_q4_weight_only_inc.onnx"
29max_generation_tokens = 999
30eos_token_id = 2
31
32print(f"Downloading base model files from {repo_id}...")
33local_dir = snapshot_download(
34 repo_id=repo_id,
35 repo_type="model",
36 allow_patterns=["onnx/audio_encoder_q4.*", "onnx/embed_tokens_q4.*", "onnx/decoder_model_merged_q4.*", "tokenizer.json"],
37)
38onnx_dir = os.path.join(local_dir, "onnx")
39tok = Tokenizer.from_file(os.path.join(local_dir, "tokenizer.json"))
40bos_id, inst_id, baud_id, aud_id, einst_id = 1, 3, 25, 24, 4
41ae_path = os.path.join(onnx_dir, "audio_encoder_q4.onnx")
42embed_path = os.path.join(onnx_dir, "embed_tokens_q4.onnx")
43if not os.path.exists(custom_decoder_path):
44 raise FileNotFoundError(f"Custom ONNX decoder not found at '{custom_decoder_path}'.")
45sess_opts = ort.SessionOptions()
46session_providers = ["CPUExecutionProvider"]
47ae_sess = ort.InferenceSession(ae_path, sess_options=sess_opts, providers=session_providers)
48embed_sess = ort.InferenceSession(embed_path, sess_options=sess_opts, providers=session_providers)
49dec_sess = ort.InferenceSession(custom_decoder_path, sess_options=sess_opts, providers=session_providers)
50num_decoder_layers = sum(1 for i in dec_sess.get_inputs() if i.name.endswith(".key"))
51print(f"Detected {num_decoder_layers} decoder layers.")
52
53def extract_mel_features_for_chunk(audio_chunk, sampling_rate=16000, n_fft=400, hop_length=160, n_mels=128, target_length=3000):
54 target_samples = sampling_rate * 30
55 audio_chunk = librosa.util.fix_length(audio_chunk, size=target_samples)
56 mel_spec = librosa.feature.melspectrogram(y=audio_chunk, sr=sampling_rate, n_fft=n_fft, hop_length=hop_length, n_mels=n_mels)
57 log_spec = np.log10(np.maximum(mel_spec, 1e-10))
58 log_spec = np.maximum(log_spec, log_spec.max() - 8.0)
59 log_spec = (log_spec + 4.0) / 4.0
60 return log_spec[:, :target_length].astype(np.float32)
61
62def process_long_audio(audio_path, session, sampling_rate=16000):
63 y, sr = sf.read(audio_path)
64 if y.ndim > 1: y = y.mean(axis=1)
65 if sr != sampling_rate: y = librosa.resample(y, orig_sr=sr, target_sr=sampling_rate)
66 chunk_samples = chunk_duration = 30 * sampling_rate
67 num_chunks = int(np.ceil(len(y) / chunk_samples))
68 all_audio_embeds = []
69 print(f"Processing in {num_chunks} chunk(s)...")
70 for i in range(num_chunks):
71 chunk = y[i * chunk_samples:(i + 1) * chunk_samples]
72 mel_features = extract_mel_features_for_chunk(chunk, sampling_rate)
73 all_audio_embeds.append(session.run(None, {session.get_inputs()[0].name: mel_features[None, :]})[0])
74 return np.concatenate(all_audio_embeds, axis=0)
75
76if not os.path.exists(audio_file_path): raise FileNotFoundError(f"Audio file '{audio_file_path}' not found.")
77
78audio_embeds_raw = process_long_audio(audio_file_path, ae_sess)
79batch_size = 1
80audio_output_frames = audio_embeds_raw.shape[0] // batch_size
81audio_embeds = audio_embeds_raw.reshape(batch_size, audio_output_frames, -1)
82text_instruction_ids = tok.encode("Transcribe.", add_special_tokens=False).ids
83prompt_tokens = ([bos_id, inst_id, baud_id] + [aud_id] * audio_output_frames + text_instruction_ids + [einst_id])
84initial_sequence_length = len(prompt_tokens)
85
86prompt_ids = np.array([prompt_tokens], dtype=np.int64)
87inputs_embeds = embed_sess.run(None, {"input_ids": prompt_ids})[0]
88inputs_embeds[0, 3:3 + audio_output_frames, :] = audio_embeds[0]
89inputs_embeds = inputs_embeds.astype(np.float32)
90
91generated_ids = []
92past_key_values = None
93current_past_len = 0
94for i in range(max_generation_tokens):
95 dec_inputs = {}
96 if i == 0:
97 dec_inputs["inputs_embeds"] = inputs_embeds
98 attention_mask = _create_4d_causal_attention_mask((batch_size, initial_sequence_length), 0)
99 else:
100 last_token_id = np.array([[generated_ids[-1]]], dtype=np.int64)
101 dec_inputs["inputs_embeds"] = embed_sess.run(None, {"input_ids": last_token_id})[0].astype(np.float32)
102 attention_mask = _create_4d_causal_attention_mask((batch_size, 1), current_past_len)
103
104 dec_inputs["attention_mask"] = attention_mask
105 if past_key_values:
106 for l in range(num_decoder_layers):
107 dec_inputs[f"past_key_values.{l}.key"] = past_key_values[l*2].astype(np.float32)
108 dec_inputs[f"past_key_values.{l}.value"] = past_key_values[l*2+1].astype(np.float32)
109 else:
110 for l in range(num_decoder_layers):
111 dec_inputs[f"past_key_values.{l}.key"] = np.zeros((batch_size, 8, 0, 128), dtype=np.float32)
112 dec_inputs[f"past_key_values.{l}.value"] = np.zeros((batch_size, 8, 0, 128), dtype=np.float32)
113
114 outputs = dec_sess.run(None, dec_inputs)
115 logits, past_key_values = outputs[0], outputs[1:1+num_decoder_layers*2]
116
117 next_token_id = np.argmax(logits[0, -1, :])
118 if next_token_id == eos_token_id: break
119 generated_ids.append(next_token_id)
120 print(tok.decode([next_token_id]), end="", flush=True)
121
122full_sequence_ids = np.array([prompt_tokens + generated_ids], dtype=np.int64)
123full_embeds = embed_sess.run(None, {"input_ids": full_sequence_ids})[0]
124full_embeds[0, 3:3 + audio_output_frames, :] = audio_embeds[0]
125full_embeds = full_embeds.astype(np.float32)
126
127alignment_inputs = {
128 "inputs_embeds": full_embeds,
129 "attention_mask": _create_4d_causal_attention_mask(full_embeds.shape[:2], 0)
130}
131for l in range(num_decoder_layers):
132 alignment_inputs[f"past_key_values.{l}.key"] = np.zeros((batch_size, 8, 0, 128), dtype=np.float32)
133 alignment_inputs[f"past_key_values.{l}.value"] = np.zeros((batch_size, 8, 0, 128), dtype=np.float32)
134
135alignment_outputs = dec_sess.run(None, alignment_inputs)
136attentions = [torch.from_numpy(attn) for attn in alignment_outputs[1+num_decoder_layers*2:]]
137
138text_start_idx = len(prompt_tokens)
139audio_end_idx = 3 + audio_output_frames
140start_layer, end_layer = 10, 20
141layer_attentions = []
142for i in range(start_layer, end_layer):
143 layer_attn = attentions[i][0]
144 layer_attn_avg_heads = layer_attn.mean(dim=0)
145 relevant_attns = layer_attn_avg_heads[text_start_idx:, 3:audio_end_idx]
146 if relevant_attns.numel() > 0:
147 layer_attentions.append(relevant_attns)
148
149if not layer_attentions:
150 raise ValueError("Could not extract any valid attention weights. The generated text might be empty.")
151
152avg_attentions = torch.stack(layer_attentions).mean(dim=0)
153temperature = 0.1
154weights = torch.nn.functional.softmax(avg_attentions / temperature, dim=1).cpu().numpy()
155
156plt.figure(figsize=(10, 10))
157plt.imshow(weights, aspect="auto", origin="lower", cmap="viridis")
158plt.xlabel("Audio Frames")
159plt.ylabel("Generated Text Tokens")
160plt.title("Audio-to-Text Alignment Matrix (Sharpened)")
161plt.colorbar()
162plt.savefig("alignment_matrix.png")
163
164cost_matrix = -weights.T
165D, wp = librosa.sequence.dtw(C=cost_matrix.astype(np.float32), backtrack=True)
166wp = np.flip(wp, axis=0)
167
168token_to_frame_map = {}
169for frame_idx, token_idx in wp:
170 if token_idx not in token_to_frame_map:
171 token_to_frame_map[token_idx] = frame_idx
172
173word_groups = []
174current_word_tokens = []
175if generated_ids:
176 for token_id in generated_ids:
177 if tok.decode([token_id]).startswith(" ") and current_word_tokens:
178 word_groups.append(current_word_tokens)
179 current_word_tokens = []
180 current_word_tokens.append(token_id)
181 if current_word_tokens: word_groups.append(current_word_tokens)
182
183EFFECTIVE_AUDIO_DURATION = 30.0
184AUDIO_TIME_PER_FRAME = EFFECTIVE_AUDIO_DURATION / audio_output_frames
185results = []
186token_idx_counter = 0
187previous_word_end_time = token_to_frame_map.get(0, 0) * AUDIO_TIME_PER_FRAME
188
189for word_group in word_groups:
190 word_text = tok.decode(word_group).strip()
191 if not word_text: continue
192
193 start_time = previous_word_end_time
194 last_token_in_word_idx = token_idx_counter + len(word_group) - 1
195 end_frame = token_to_frame_map.get(last_token_in_word_idx, 0)
196 end_time = max(start_time, end_frame * AUDIO_TIME_PER_FRAME)
197 results.append({"word": word_text, "start": start_time, "end": end_time})
198 previous_word_end_time = end_time
199 token_idx_counter += len(word_group)
200
201for res in results: print(f"[{res['start']: >6.2f}s -> {res['end']: >6.2f}s] {res['word']}")
202
203SAMPLING_RATE = 16000
204y, sr = librosa.load(audio_file_path, sr=SAMPLING_RATE)
205if not results: print("No words were transcribed to verify.")
206else:
207 for res in results:
208 start_sample = int(res['start'] * SAMPLING_RATE)
209 end_sample = int(res['end'] * SAMPLING_RATE)
210 audio_snippet = y[start_sample:end_sample]
211 print(f"\n[{res['start']: >6.2f}s -> {res['end']: >6.2f}s] {res['word']}")
212 if len(audio_snippet) > 0: display(Audio(audio_snippet, rate=SAMPLING_RATE))
213 else: print(" (No audio for this segment)")