File size: 5,906 Bytes
79c6440
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# -*- coding: utf-8 -*-
"""run_inference.py — Vibrato 开箱即用推理脚本

支持两种运行模式:
  1. e5s 满血模式(默认,端到端 ~15ms):结合 e5-small int8 语义底座 + Vibrato e5s 判头
  2. fast 纯文字极速模式(~3ms,零模型底座):仅加载 Vibrato v6 int8 判头

依赖:
  pip install onnxruntime tokenizers numpy

用法:
  python run_inference.py              # 运行内置测试用例
  python run_inference.py --mode fast   # 纯文字极速模式
"""
from __future__ import annotations

import argparse
import json
import os
import sys

import numpy as np
import onnxruntime as ort
from tokenizers import Tokenizer

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)

import vib_messages
from battery import FAMILY_IDS, NOUL_IDS

DEMOS = [
    [{"role": "user", "message": "今天累死啦,快抱抱"}],
    [{"role": "user", "message": "终于等到你啦~ 今天累死本宝宝了,快抱抱!"}],
    [
        {"role": "user", "message": "你刚才为什么要那样敷衍我?给我说清楚!"},
        {"role": "assistant", "message": "抱歉,我没有想敷衍你的意思。"},
        {"role": "user", "message": "算了,说了也没用。"}
    ],
    [
        {"role": "assistant", "message": "今天过得怎么样呀?", "pad": [0.4, -0.2, 0.1]},
        {"role": "user", "message": "还行吧,就那样,一堆破事"}
    ]
]


def load_e5s():
    vocab = json.load(open(os.path.join(HERE, "vocab.json"), encoding="utf-8"))
    tok = Tokenizer.from_file(os.path.join(HERE, "tokenizer", "tokenizer.json"))
    so = ort.SessionOptions()
    so.intra_op_num_threads = 1
    head = ort.InferenceSession(os.path.join(HERE, "models", "vibrato_e5s_int8.onnx"), so, providers=["CPUExecutionProvider"])
    backbone = ort.InferenceSession(os.path.join(HERE, "models", "e5s_int8.onnx"), so, providers=["CPUExecutionProvider"])
    return vocab, tok, head, backbone


def load_fast():
    vocab = json.load(open(os.path.join(HERE, "vocab.json"), encoding="utf-8"))
    so = ort.SessionOptions()
    so.intra_op_num_threads = 1
    head = ort.InferenceSession(os.path.join(HERE, "models", "vibrato_v6_int8.onnx"), so, providers=["CPUExecutionProvider"])
    return vocab, head


def get_e5s_feats(backbone, tok, text, max_chars):
    n = min(len(text), max_chars)
    out = np.zeros((max_chars, 384), dtype=np.float32)
    enc = tok.encode(text, add_special_tokens=False)
    ids = np.array([enc.ids], dtype=np.int64)
    mask = np.ones_like(ids)
    hidden = backbone.run(None, {"input_ids": ids, "attention_mask": mask})[0][0]
    for (s, e), vec in zip(enc.offsets, hidden):
        lo, hi = s, min(e, n)
        if hi > lo:
            out[lo:hi] = vec
    return out


def score_messages(messages, vocab, head, backbone=None, tok=None, max_len=512):
    vib_messages.validate_messages(messages)
    echo = next((m["pad"] for m in reversed(messages[:-1]) if m["role"] == "user" and "pad" in m), [0.0] * 3)
    units = vib_messages.sequence_units(messages, max_window=max_len)
    
    ids, spos, svals, smask = [], [], [], []
    unk = len(vocab)
    for u in units:
        if u["kind"] == "text":
            for ch in u["text"]:
                ids.append(vocab.get(ch, unk))
                spos.append(False)
            svals.extend([None] * len(u["text"]))
            smask.extend([None] * len(u["text"]))
        else:
            ids.append(0)
            spos.append(True)
            svals.append(u["vals"])
            smask.append(u["mask"])
            
    L = len(ids)
    z = [0.0] * vib_messages.STATE_DIM
    
    if backbone is not None and tok is not None:
        feats = np.zeros((L, 384), dtype=np.float32)
        pos = 0
        for u in units:
            if u["kind"] == "text":
                if u["text"].strip():
                    f = get_e5s_feats(backbone, tok, u["text"], L)
                    n = min(len(u["text"]), L - pos)
                    feats[pos:pos + n] = f[:n]
                pos += len(u["text"])
            else:
                pos += 1
    else:
        feats = np.zeros((L, 1024), dtype=np.float32)

    feed = {
        "ids": np.array([ids], dtype=np.int64),
        "state_pos": np.array([spos], dtype=np.float32),
        "state_vals": np.array([[v if v is not None else z for v in svals]], dtype=np.float32),
        "state_mask": np.array([[v if v is not None else z for v in smask]], dtype=np.float32),
        "echo_prev": np.array([echo], dtype=np.float32),
        "feats": feats[None]
    }
    o = head.run(None, feed)
    pad = o[2][0].tolist()
    fam = FAMILY_IDS[int(o[3][0].argmax())]
    noul = {q: bool(o[4][0, j, 1] > 0.5) for j, q in enumerate(NOUL_IDS)}
    conf = float(o[5][0])
    return pad, fam, noul, conf


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--mode", choices=["e5s", "fast"], default="e5s", help="e5s: 离线端到端满血 (384-dim) | fast: 纯文字极速 (零底座)")
    args = parser.parse_args()

    print(f"=== Vibrato 推理引擎启动 (模式: {args.mode}) ===")
    if args.mode == "e5s":
        vocab, tok, head, backbone = load_e5s()
    else:
        vocab, head = load_fast()
        backbone, tok = None, None

    for i, msgs in enumerate(DEMOS):
        target = msgs[-1]["message"]
        pad, fam, noul, conf = score_messages(msgs, vocab, head, backbone=backbone, tok=tok)
        print(f"\n[测试例 {i+1}] 输入: 「{target}」")
        print(f"  -> PAD 情绪分布: Pleasure={pad[0]:+.2f}, Arousal={pad[1]:+.2f}, Dominance={pad[2]:+.2f}")
        print(f"  -> 情绪族 (Family): {fam}")
        print(f"  -> 置信度 (Conf): {conf:.2f}")
        active_nouls = [k for k, v in noul.items() if v]
        print(f"  -> 语用标记 (Nouls): {active_nouls if active_nouls else '无特殊标记'}")


if __name__ == "__main__":
    main()