diff --git a/README.md b/README.md
index 502aca3..fea358f 100644
--- a/README.md
+++ b/README.md
@@ -70,6 +70,8 @@ We compared the performance of multilingual speech recognition between SenseVoic
Due to the current lack of widely-used benchmarks and methods for speech emotion recognition, we conducted evaluations across various metrics on multiple test sets and performed a comprehensive comparison with numerous results from recent benchmarks. The selected test sets encompass data in both Chinese and English, and include multiple styles such as performances, films, and natural conversations. Without finetuning on the target data, SenseVoice was able to achieve and exceed the performance of the current best speech emotion recognition models.
+For a reproducible zero-shot CASIA or RAVDESS rerun, use the [SER evaluation contract](./benchmarks/ser/README.md). It reads the raw SenseVoice emotion tag and reports both UA and WA instead of deriving labels from formatted transcription text.
+
diff --git a/benchmarks/ser/README.md b/benchmarks/ser/README.md
new file mode 100644
index 0000000..d9b4a95
--- /dev/null
+++ b/benchmarks/ser/README.md
@@ -0,0 +1,36 @@
+# Reproducing SenseVoice SER Measurements
+
+The SER table in the project README reports zero-shot results. It is not a
+promise that every dataset mirror, parser, or aggregate metric produces the
+same number. In particular, the CASIA SenseVoiceSmall row is **70.0 UA / 70.0
+WA** for the six-label benchmark protocol shown in the table.
+
+Use `evaluate.py` to make a local evaluation reproducible. It accepts a JSONL
+manifest; every non-empty line must contain an audio path or URL and one of the
+following labels:
+
+```json
+{"audio": "/data/CASIA/angry/example.wav", "label": "angry"}
+```
+
+The accepted canonical labels are `angry`, `fearful`, `happy`, `neutral`,
+`sad`, and `surprised`. Dataset spellings `fear` and `surprise` are normalized
+to `fearful` and `surprised`.
+
+```bash
+python benchmarks/ser/evaluate.py casia.jsonl \
+ --model iic/SenseVoiceSmall --device cuda:0 --output casia-results.json
+```
+
+The evaluator reads the raw `<|EMOTION|>` tag returned by SenseVoice before
+calling rich-text post-processing. Do not infer the label by splitting the
+formatted transcription: tags and display text have different contracts.
+
+`wa` is accuracy over all records. `ua` is the mean recall over labels present
+in the manifest. The JSON result includes per-label recall and a confusion map;
+unknown labels, missing emotion tags, and malformed manifest records fail the
+run instead of being skipped.
+
+CASIA and RAVDESS distributions are controlled by their respective providers.
+Keep the dataset version, manifest, model revision, package versions, and this
+JSON result together when comparing a rerun with the README table.
diff --git a/benchmarks/ser/evaluate.py b/benchmarks/ser/evaluate.py
new file mode 100644
index 0000000..cc29b98
--- /dev/null
+++ b/benchmarks/ser/evaluate.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python3
+"""Evaluate zero-shot SenseVoice emotion predictions from a JSONL manifest."""
+
+import argparse
+import json
+import re
+from collections import Counter, defaultdict
+from pathlib import Path
+
+
+CANONICAL_LABELS = (
+ "angry",
+ "fearful",
+ "happy",
+ "neutral",
+ "sad",
+ "surprised",
+)
+LABEL_ALIASES = {
+ "angry": "angry",
+ "fear": "fearful",
+ "fearful": "fearful",
+ "happy": "happy",
+ "neutral": "neutral",
+ "sad": "sad",
+ "surprise": "surprised",
+ "surprised": "surprised",
+}
+EMOTION_TAG = re.compile(r"<\|(?P