From f3b531a6ab1237af1a4135ec6f1c3b2b175b9c84 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 10 Jul 2026 12:02:20 +0800 Subject: [PATCH 1/6] Add audeering wav2vec2 emotion support Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- examples/recipes/README.md | 3 +- .../audio-classification_fp32_config.json | 43 +++++++ src/winml/modelkit/models/hf/__init__.py | 7 ++ src/winml/modelkit/models/hf/wav2vec2.py | 113 ++++++++++++++++++ 4 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 examples/recipes/audeering_wav2vec2-large-robust-12-ft-emotion-msp-dim/audio-classification_fp32_config.json create mode 100644 src/winml/modelkit/models/hf/wav2vec2.py diff --git a/examples/recipes/README.md b/examples/recipes/README.md index 1077e4f74..2e2699ada 100644 --- a/examples/recipes/README.md +++ b/examples/recipes/README.md @@ -14,7 +14,7 @@ Each *(model, task)* includes: ## Models -Total: **75** (model, task) tuples that pass fp16 eval on all 10 (EP, device) buckets. +Total: **76** (model, task) tuples with curated model recipes. | Model | Task | |---|---| @@ -37,6 +37,7 @@ Total: **75** (model, task) tuples that pass fp16 eval on all 10 (EP, device) bu | StanfordAIMI/dinov2-base-xray-224 | image-feature-extraction | | ahotrod/electra_large_discriminator_squad2_512 | question-answering | | apple/mobilevit-small | image-classification | +| audeering/wav2vec2-large-robust-12-ft-emotion-msp-dim | audio-classification | | cardiffnlp/twitter-roberta-base-sentiment-latest | text-classification | | dbmdz/bert-large-cased-finetuned-conll03-english | token-classification | | deepset/bert-large-uncased-whole-word-masking-squad2 | question-answering | diff --git a/examples/recipes/audeering_wav2vec2-large-robust-12-ft-emotion-msp-dim/audio-classification_fp32_config.json b/examples/recipes/audeering_wav2vec2-large-robust-12-ft-emotion-msp-dim/audio-classification_fp32_config.json new file mode 100644 index 000000000..07673bafa --- /dev/null +++ b/examples/recipes/audeering_wav2vec2-large-robust-12-ft-emotion-msp-dim/audio-classification_fp32_config.json @@ -0,0 +1,43 @@ +{ + "export": { + "opset_version": 17, + "batch_size": 1, + "export_params": true, + "do_constant_folding": true, + "verbose": false, + "dynamo": false, + "enable_hierarchy_tags": false, + "clean_onnx": false, + "hierarchy_tag_format": "full", + "input_tensors": [ + { + "name": "input_values", + "dtype": "float32", + "shape": [ + 1, + 16000 + ], + "value_range": [ + -1, + 1 + ] + } + ], + "output_tensors": [ + { + "name": "hidden_states" + }, + { + "name": "logits" + } + ] + }, + "optim": {}, + "quant": null, + "compile": null, + "loader": { + "task": "audio-classification", + "model_class": "EmotionModel", + "model_type": "wav2vec2_emotion_regression" + } +} diff --git a/src/winml/modelkit/models/hf/__init__.py b/src/winml/modelkit/models/hf/__init__.py index 094714494..70d7ddf26 100644 --- a/src/winml/modelkit/models/hf/__init__.py +++ b/src/winml/modelkit/models/hf/__init__.py @@ -95,6 +95,12 @@ ) from .vision_encoder_decoder import VisionEncoderIOConfig as _VisionEncoderIOConfig from .vitpose import MODEL_CLASS_MAPPING as _VITPOSE_CLASS_MAPPING +from .wav2vec2 import ( + MODEL_CLASS_MAPPING as _WAV2VEC2_CLASS_MAPPING, +) +from .wav2vec2 import ( + Wav2Vec2EmotionRegressionIOConfig as _Wav2Vec2EmotionRegressionIOConfig, +) from .zoedepth import ZoeDepthIOConfig as _ZoeDepthIOConfig # triggers registration @@ -128,6 +134,7 @@ _T5_CLASS_MAPPING, _VED_CLASS_MAPPING, _VITPOSE_CLASS_MAPPING, + _WAV2VEC2_CLASS_MAPPING, ) for _key, _model_cls in _sub_mapping.items() } diff --git a/src/winml/modelkit/models/hf/wav2vec2.py b/src/winml/modelkit/models/hf/wav2vec2.py new file mode 100644 index 000000000..1576fc1f6 --- /dev/null +++ b/src/winml/modelkit/models/hf/wav2vec2.py @@ -0,0 +1,113 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# -------------------------------------------------------------------------- +"""Audeering wav2vec2 emotion-regression export variant.""" + +from __future__ import annotations + +from typing import Any + +import torch +import torch.nn as nn +from optimum.exporters.onnx import OnnxConfig +from optimum.utils import NormalizedConfig +from optimum.utils.input_generators import DummyAudioInputGenerator +from transformers.models.wav2vec2.modeling_wav2vec2 import ( + Wav2Vec2Model, + Wav2Vec2PreTrainedModel, +) + +from ...export import register_onnx_overwrite +from ..winml import register_specialization + + +EMOTION_REGRESSION_MODEL_TYPE = "wav2vec2_emotion_regression" + + +class RegressionHead(nn.Module): + """Audeering dimensional-emotion regression head.""" + + def __init__(self, config: Any) -> None: + super().__init__() + self.dense = nn.Linear(config.hidden_size, config.hidden_size) + self.dropout = nn.Dropout(config.final_dropout) + self.out_proj = nn.Linear(config.hidden_size, config.num_labels) + + def forward(self, features: torch.Tensor, **kwargs: Any) -> torch.Tensor: # noqa: D102 + x = features + x = self.dropout(x) + x = self.dense(x) + x = torch.tanh(x) + x = self.dropout(x) + x = self.out_proj(x) + return x # noqa: RET504 + + +class EmotionModel(Wav2Vec2PreTrainedModel): + """Audeering wav2vec2 mean-pooling regression model.""" + + def __init__(self, config: Any) -> None: + super().__init__(config) + self.config = config + self.wav2vec2 = Wav2Vec2Model(config) + self.classifier = RegressionHead(config) + self.config.model_type = EMOTION_REGRESSION_MODEL_TYPE + self.init_weights() + + def forward(self, input_values: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: # noqa: D102 + outputs = self.wav2vec2(input_values) + hidden_states = outputs[0] + hidden_states = torch.mean(hidden_states, dim=1) + logits = self.classifier(hidden_states) + return hidden_states, logits + + +_EMOTION_NORMALIZED_CONFIG = NormalizedConfig.with_args( + hidden_size="hidden_size", + num_labels="num_labels", + allow_new=True, +) + + +@register_onnx_overwrite( + EMOTION_REGRESSION_MODEL_TYPE, + "audio-classification", + library_name="transformers", +) +class Wav2Vec2EmotionRegressionIOConfig(OnnxConfig): # type: ignore[misc] # optimum base is untyped + """ONNX config for raw waveform -> hidden_states, logits.""" + + NORMALIZED_CONFIG_CLASS = _EMOTION_NORMALIZED_CONFIG + DUMMY_INPUT_GENERATOR_CLASSES = (DummyAudioInputGenerator,) + + @property + def inputs(self) -> dict[str, dict[int, str]]: # noqa: D102 + return {"input_values": {0: "batch_size", 1: "audio_sequence_length"}} + + @property + def outputs(self) -> dict[str, dict[int, str]]: # noqa: D102 + return { + "hidden_states": {0: "batch_size"}, + "logits": {0: "batch_size"}, + } + + +MODEL_CLASS_MAPPING: dict[tuple[str, str], type] = { + ("wav2vec2-emotion-regression", "audio-classification"): EmotionModel, +} + +register_specialization( + EMOTION_REGRESSION_MODEL_TYPE, + "audio-classification", + "WinMLModelForGenericTask", +) + + +__all__ = [ + "EMOTION_REGRESSION_MODEL_TYPE", + "MODEL_CLASS_MAPPING", + "EmotionModel", + "RegressionHead", + "Wav2Vec2EmotionRegressionIOConfig", +] From 6efc3a7a7dd12874fd81845c6a6e0a8772cdd728 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 10 Jul 2026 14:03:44 +0800 Subject: [PATCH 2/6] fix(wav2vec2): satisfy mypy no-any-return in RegressionHead.forward nn.Linear.__call__ is typed to return Any, so eturn x tripped the strict mypy [no-any-return] gate (CI lint). Return the projection via cast("torch.Tensor", ...) to match the repo house style (blip.py/mu2.py) and drop the now-unnecessary RET504 assign-then-return. Runtime behavior unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/winml/modelkit/models/hf/wav2vec2.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/winml/modelkit/models/hf/wav2vec2.py b/src/winml/modelkit/models/hf/wav2vec2.py index 1576fc1f6..970a42fe4 100644 --- a/src/winml/modelkit/models/hf/wav2vec2.py +++ b/src/winml/modelkit/models/hf/wav2vec2.py @@ -6,7 +6,7 @@ from __future__ import annotations -from typing import Any +from typing import Any, cast import torch import torch.nn as nn @@ -40,8 +40,7 @@ def forward(self, features: torch.Tensor, **kwargs: Any) -> torch.Tensor: # noq x = self.dense(x) x = torch.tanh(x) x = self.dropout(x) - x = self.out_proj(x) - return x # noqa: RET504 + return cast("torch.Tensor", self.out_proj(x)) class EmotionModel(Wav2Vec2PreTrainedModel): From 71101358f756396bb65f24c08d661ba5de07bcf6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 13 Jul 2026 14:45:58 +0800 Subject: [PATCH 3/6] fix(wav2vec2): address PR review feedback - Add tests/unit/models/wav2vec2/test_onnx_config.py covering the MODEL_CLASS_MAPPING entry -> EmotionModel, the resolve_task underscore (_ -> -) normalization contract, and the IOConfig registration plus input/output axes. - Add the '# triggers registration' comment to the IOConfig side-effect import and collapse the mapping import to a single statement. - Restore the fp16/all-buckets guarantee wording in the recipes README and note the new fp32/CPU-only exception instead of weakening every row. - Drop the unused **kwargs from RegressionHead.forward. --- examples/recipes/README.md | 2 +- src/winml/modelkit/models/hf/__init__.py | 5 +- src/winml/modelkit/models/hf/wav2vec2.py | 2 +- .../unit/models/wav2vec2/test_onnx_config.py | 142 ++++++++++++++++++ 4 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 tests/unit/models/wav2vec2/test_onnx_config.py diff --git a/examples/recipes/README.md b/examples/recipes/README.md index 2e2699ada..7299805aa 100644 --- a/examples/recipes/README.md +++ b/examples/recipes/README.md @@ -14,7 +14,7 @@ Each *(model, task)* includes: ## Models -Total: **76** (model, task) tuples with curated model recipes. +Total: **76** (model, task) tuples with curated recipes. 75 pass fp16 eval on all 10 (EP, device) buckets; audeering/wav2vec2-large-robust-12-ft-emotion-msp-dim (audio-classification) is fp32/CPU-only. | Model | Task | |---|---| diff --git a/src/winml/modelkit/models/hf/__init__.py b/src/winml/modelkit/models/hf/__init__.py index 70d7ddf26..7298b2341 100644 --- a/src/winml/modelkit/models/hf/__init__.py +++ b/src/winml/modelkit/models/hf/__init__.py @@ -95,10 +95,9 @@ ) from .vision_encoder_decoder import VisionEncoderIOConfig as _VisionEncoderIOConfig from .vitpose import MODEL_CLASS_MAPPING as _VITPOSE_CLASS_MAPPING +from .wav2vec2 import MODEL_CLASS_MAPPING as _WAV2VEC2_CLASS_MAPPING from .wav2vec2 import ( - MODEL_CLASS_MAPPING as _WAV2VEC2_CLASS_MAPPING, -) -from .wav2vec2 import ( + # triggers registration Wav2Vec2EmotionRegressionIOConfig as _Wav2Vec2EmotionRegressionIOConfig, ) from .zoedepth import ZoeDepthIOConfig as _ZoeDepthIOConfig # triggers registration diff --git a/src/winml/modelkit/models/hf/wav2vec2.py b/src/winml/modelkit/models/hf/wav2vec2.py index 970a42fe4..869734272 100644 --- a/src/winml/modelkit/models/hf/wav2vec2.py +++ b/src/winml/modelkit/models/hf/wav2vec2.py @@ -34,7 +34,7 @@ def __init__(self, config: Any) -> None: self.dropout = nn.Dropout(config.final_dropout) self.out_proj = nn.Linear(config.hidden_size, config.num_labels) - def forward(self, features: torch.Tensor, **kwargs: Any) -> torch.Tensor: # noqa: D102 + def forward(self, features: torch.Tensor) -> torch.Tensor: # noqa: D102 x = features x = self.dropout(x) x = self.dense(x) diff --git a/tests/unit/models/wav2vec2/test_onnx_config.py b/tests/unit/models/wav2vec2/test_onnx_config.py new file mode 100644 index 000000000..dab7a16a0 --- /dev/null +++ b/tests/unit/models/wav2vec2/test_onnx_config.py @@ -0,0 +1,142 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# -------------------------------------------------------------------------- +"""Tests for the audeering wav2vec2 dimensional-emotion export variant. + +The audeering ``wav2vec2-large-robust-12-ft-emotion-msp-dim`` checkpoint ships a +custom mean-pooling ``RegressionHead`` that transformers/Optimum cannot route on +their own. Support hinges on three contracts these tests lock in: + +- the ``("wav2vec2-emotion-regression", "audio-classification")`` entry in + ``MODEL_CLASS_MAPPING`` resolving to ``EmotionModel``; +- ``resolve_task`` honouring the build variant's underscore ``model_type`` + (``wav2vec2_emotion_regression``) via the ``_`` -> ``-`` normalization the + mapping key depends on; +- ``Wav2Vec2EmotionRegressionIOConfig`` registering for the + ``audio-classification`` ONNX export with the expected input/output axes. + +A rename of ``EMOTION_REGRESSION_MODEL_TYPE`` or a resolver normalization change +would otherwise break routing with nothing failing in CI. +""" + +from __future__ import annotations + +import pytest +from optimum.exporters.tasks import TasksManager +from optimum.utils.input_generators import DummyAudioInputGenerator +from transformers import Wav2Vec2Config + +from winml.modelkit.loader import resolve_task +from winml.modelkit.models.hf import MODEL_CLASS_MAPPING +from winml.modelkit.models.hf.wav2vec2 import ( + EMOTION_REGRESSION_MODEL_TYPE, + EmotionModel, + Wav2Vec2EmotionRegressionIOConfig, +) +from winml.modelkit.models.hf.wav2vec2 import MODEL_CLASS_MAPPING as WAV2VEC2_MAPPING + + +# ============================================================================= +# Test Constants +# ============================================================================= + +TASK = "audio-classification" +MAPPING_KEY = ("wav2vec2-emotion-regression", TASK) + + +# ============================================================================= +# Fixtures +# ============================================================================= + + +@pytest.fixture(scope="module") +def emotion_config(): + """Minimal Wav2Vec2Config exercising the emotion-regression head dimensions.""" + return Wav2Vec2Config(hidden_size=16, num_labels=3) + + +# ============================================================================= +# MODEL_CLASS_MAPPING — routing to the custom regression wrapper +# ============================================================================= + + +class TestWav2Vec2EmotionModelClassMapping: + """The emotion-regression variant routes to EmotionModel.""" + + def test_mapping_entry_registered(self): + """The aggregated mapping exposes the emotion-regression entry.""" + assert MAPPING_KEY in MODEL_CLASS_MAPPING + assert MODEL_CLASS_MAPPING[MAPPING_KEY] is EmotionModel + + def test_module_mapping_merged_into_aggregate(self): + """The module-level mapping is included in the aggregated mapping.""" + assert WAV2VEC2_MAPPING.items() <= MODEL_CLASS_MAPPING.items() + + +# ============================================================================= +# resolve_task — underscore model_type normalization contract +# ============================================================================= + + +class TestWav2Vec2EmotionTaskResolution: + """resolve_task honours the underscore build-variant model_type.""" + + def test_model_type_override_resolves_emotion_model(self, emotion_config): + """An underscore model_type override normalizes and resolves EmotionModel.""" + resolution = resolve_task( + emotion_config, + task=TASK, + model_class="EmotionModel", + model_type_override=EMOTION_REGRESSION_MODEL_TYPE, + ) + + assert resolution.task == TASK + assert resolution.model_class is EmotionModel + + def test_registered_model_type_uses_underscores(self): + """The registered model_type uses underscores; the mapping key uses dashes.""" + assert EMOTION_REGRESSION_MODEL_TYPE == "wav2vec2_emotion_regression" + assert EMOTION_REGRESSION_MODEL_TYPE.replace("_", "-") == MAPPING_KEY[0] + + +# ============================================================================= +# Wav2Vec2EmotionRegressionIOConfig — registration and I/O axes +# ============================================================================= + + +class TestWav2Vec2EmotionRegressionIOConfig: + """The ONNX export config is registered with the expected axes.""" + + def test_onnx_config_registered(self): + """Config is registered with TasksManager for audio-classification.""" + config_cls = TasksManager.get_exporter_config_constructor( + model_type=EMOTION_REGRESSION_MODEL_TYPE, + exporter="onnx", + task=TASK, + library_name="transformers", + ) + assert config_cls.func.__name__ == Wav2Vec2EmotionRegressionIOConfig.__name__ + + def test_inputs_axes(self, emotion_config): + """Inputs expose input_values with batch and audio-length dynamic axes.""" + io_config = Wav2Vec2EmotionRegressionIOConfig(emotion_config, task=TASK) + + assert io_config.inputs == { + "input_values": {0: "batch_size", 1: "audio_sequence_length"}, + } + + def test_outputs_axes(self, emotion_config): + """Outputs expose hidden_states and logits with a batch dynamic axis.""" + io_config = Wav2Vec2EmotionRegressionIOConfig(emotion_config, task=TASK) + + assert io_config.outputs == { + "hidden_states": {0: "batch_size"}, + "logits": {0: "batch_size"}, + } + + def test_dummy_input_generator_class(self): + """Uses DummyAudioInputGenerator for raw-waveform dummy inputs.""" + assert ( + DummyAudioInputGenerator, + ) == Wav2Vec2EmotionRegressionIOConfig.DUMMY_INPUT_GENERATOR_CLASSES From 410546fc23e7e3472d0c68a785c1f539d400694e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Jul 2026 17:08:52 +0800 Subject: [PATCH 4/6] Move audeering emotion recipe under cpu/cpu EP/device folder and add validation test Relocate the fp32 CPU-tested recipe to examples/recipes/audeering_wav2vec2-large-robust-12-ft-emotion-msp-dim/cpu/cpu/, matching the // layout convention. Add tests/unit/recipes/test_cpu_recipes.py to validate the recipe loads and routes to the emotion-regression head. --- .../audio-classification_fp32_config.json | 0 tests/unit/recipes/test_cpu_recipes.py | 57 +++++++++++++++++++ 2 files changed, 57 insertions(+) rename examples/recipes/audeering_wav2vec2-large-robust-12-ft-emotion-msp-dim/{ => cpu/cpu}/audio-classification_fp32_config.json (100%) create mode 100644 tests/unit/recipes/test_cpu_recipes.py diff --git a/examples/recipes/audeering_wav2vec2-large-robust-12-ft-emotion-msp-dim/audio-classification_fp32_config.json b/examples/recipes/audeering_wav2vec2-large-robust-12-ft-emotion-msp-dim/cpu/cpu/audio-classification_fp32_config.json similarity index 100% rename from examples/recipes/audeering_wav2vec2-large-robust-12-ft-emotion-msp-dim/audio-classification_fp32_config.json rename to examples/recipes/audeering_wav2vec2-large-robust-12-ft-emotion-msp-dim/cpu/cpu/audio-classification_fp32_config.json diff --git a/tests/unit/recipes/test_cpu_recipes.py b/tests/unit/recipes/test_cpu_recipes.py new file mode 100644 index 000000000..deba60f07 --- /dev/null +++ b/tests/unit/recipes/test_cpu_recipes.py @@ -0,0 +1,57 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# -------------------------------------------------------------------------- + +import json +from pathlib import Path + +import pytest + +from winml.modelkit.config import WinMLBuildConfig + + +REPO_ROOT = Path(__file__).resolve().parents[3] + +recipes = [ + { + "path": REPO_ROOT + / "examples" + / "recipes" + / "audeering_wav2vec2-large-robust-12-ft-emotion-msp-dim" + / "cpu" + / "cpu" + / "audio-classification_fp32_config.json", + "loader_task": "audio-classification", + "loader_model_class": "EmotionModel", + "loader_model_type": "wav2vec2_emotion_regression", + "opset_version": 17, + }, +] + + +@pytest.mark.parametrize("rec", recipes, ids=["audeering-wav2vec2-emotion"]) +def test_cpu_recipes(rec): + path: Path = rec["path"] + assert path.exists(), f"Recipe file missing: {path}" + + # EP/device is encoded by folder layout: ///.json + assert path.parent.name == "cpu" # device + assert path.parent.parent.name == "cpu" # ep + + data = json.loads(path.read_text(encoding="utf-8")) + + # Construct the validated config from the recipe dict + config = WinMLBuildConfig.from_dict(data) + + # export.opset_version exact + assert config.export is not None + assert config.export.opset_version == rec["opset_version"] + + # loader routes to the emotion-regression head + assert config.loader.task == rec["loader_task"] + assert config.loader.model_class == rec["loader_model_class"] + assert config.loader.model_type == rec["loader_model_type"] + + # fp32 CPU recipe: no quantization + assert config.quant is None From 540af6fe7bf50f68133b7908990313f943f53db1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Jul 2026 17:19:41 +0800 Subject: [PATCH 5/6] Revert examples/recipes/README.md to main Keep the README unchanged from main: audeering/wav2vec2-large-robust-12-ft-emotion-msp-dim is fp32/CPU-only and does not satisfy the table's fp16-on-all-buckets invariant, so it is not listed there. --- examples/recipes/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/recipes/README.md b/examples/recipes/README.md index 7299805aa..1077e4f74 100644 --- a/examples/recipes/README.md +++ b/examples/recipes/README.md @@ -14,7 +14,7 @@ Each *(model, task)* includes: ## Models -Total: **76** (model, task) tuples with curated recipes. 75 pass fp16 eval on all 10 (EP, device) buckets; audeering/wav2vec2-large-robust-12-ft-emotion-msp-dim (audio-classification) is fp32/CPU-only. +Total: **75** (model, task) tuples that pass fp16 eval on all 10 (EP, device) buckets. | Model | Task | |---|---| @@ -37,7 +37,6 @@ Total: **76** (model, task) tuples with curated recipes. 75 pass fp16 eval on al | StanfordAIMI/dinov2-base-xray-224 | image-feature-extraction | | ahotrod/electra_large_discriminator_squad2_512 | question-answering | | apple/mobilevit-small | image-classification | -| audeering/wav2vec2-large-robust-12-ft-emotion-msp-dim | audio-classification | | cardiffnlp/twitter-roberta-base-sentiment-latest | text-classification | | dbmdz/bert-large-cased-finetuned-conll03-english | token-classification | | deepset/bert-large-uncased-whole-word-masking-squad2 | question-answering | From a9a1af5441dcf7ecd8eed409a9c2a116b830547d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Jul 2026 18:55:12 +0800 Subject: [PATCH 6/6] recipe(wav2vec2): add cpu/cpu fp16 alongside fp32 for audeering emotion Per CPU precision policy: the cpu/cpu bucket ships both float precisions (fp32 + fp16, quant:null). Adds audio-classification_fp16_config.json (byte-identical to the existing fp32 recipe) and a matching parametrized entry in tests/unit/recipes/test_cpu_recipes.py. Verified: test_cpu_recipes.py 2 passed (fp32 + fp16); fp16 recipe SHA256 identical to the already-verified fp32 recipe. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../cpu/audio-classification_fp16_config.json | 43 +++++++++++++++++++ tests/unit/recipes/test_cpu_recipes.py | 21 ++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 examples/recipes/audeering_wav2vec2-large-robust-12-ft-emotion-msp-dim/cpu/cpu/audio-classification_fp16_config.json diff --git a/examples/recipes/audeering_wav2vec2-large-robust-12-ft-emotion-msp-dim/cpu/cpu/audio-classification_fp16_config.json b/examples/recipes/audeering_wav2vec2-large-robust-12-ft-emotion-msp-dim/cpu/cpu/audio-classification_fp16_config.json new file mode 100644 index 000000000..07673bafa --- /dev/null +++ b/examples/recipes/audeering_wav2vec2-large-robust-12-ft-emotion-msp-dim/cpu/cpu/audio-classification_fp16_config.json @@ -0,0 +1,43 @@ +{ + "export": { + "opset_version": 17, + "batch_size": 1, + "export_params": true, + "do_constant_folding": true, + "verbose": false, + "dynamo": false, + "enable_hierarchy_tags": false, + "clean_onnx": false, + "hierarchy_tag_format": "full", + "input_tensors": [ + { + "name": "input_values", + "dtype": "float32", + "shape": [ + 1, + 16000 + ], + "value_range": [ + -1, + 1 + ] + } + ], + "output_tensors": [ + { + "name": "hidden_states" + }, + { + "name": "logits" + } + ] + }, + "optim": {}, + "quant": null, + "compile": null, + "loader": { + "task": "audio-classification", + "model_class": "EmotionModel", + "model_type": "wav2vec2_emotion_regression" + } +} diff --git a/tests/unit/recipes/test_cpu_recipes.py b/tests/unit/recipes/test_cpu_recipes.py index deba60f07..9884dde5b 100644 --- a/tests/unit/recipes/test_cpu_recipes.py +++ b/tests/unit/recipes/test_cpu_recipes.py @@ -27,10 +27,27 @@ "loader_model_type": "wav2vec2_emotion_regression", "opset_version": 17, }, + { + "path": REPO_ROOT + / "examples" + / "recipes" + / "audeering_wav2vec2-large-robust-12-ft-emotion-msp-dim" + / "cpu" + / "cpu" + / "audio-classification_fp16_config.json", + "loader_task": "audio-classification", + "loader_model_class": "EmotionModel", + "loader_model_type": "wav2vec2_emotion_regression", + "opset_version": 17, + }, ] -@pytest.mark.parametrize("rec", recipes, ids=["audeering-wav2vec2-emotion"]) +@pytest.mark.parametrize( + "rec", + recipes, + ids=["audeering-wav2vec2-emotion-fp32", "audeering-wav2vec2-emotion-fp16"], +) def test_cpu_recipes(rec): path: Path = rec["path"] assert path.exists(), f"Recipe file missing: {path}" @@ -53,5 +70,5 @@ def test_cpu_recipes(rec): assert config.loader.model_class == rec["loader_model_class"] assert config.loader.model_type == rec["loader_model_type"] - # fp32 CPU recipe: no quantization + # float CPU recipe (fp32/fp16): no quantization assert config.quant is None