<0x00> 前言
项目要有TTS,然后就找到KokoroSharp这个包
项目中的模型对英文支持很好,但中文总有种好想成为人类的感觉
然后接着寻找,发现有新的v1.1-zh的模型
(KokoroSharp默认使用的是v1.0的模型)
但由于对中文的针对训练导致其他语言输出有一定劣化,所以还不是正式版
然后就开始折腾能不能让这个项目跑起v1.1-zh的模型
前排提示:流水账文章,而且问题也没解决
结果是成功让v1.1-zh跑起来了,但中文输出还是很差
所以只是做一个过程记录
我认为可能是原本模型从torch转换到onnx导致的问题
<0x01> 过程
尝试直接运行
如果直接加载v1.1-zh-onnx的模型,那确实是能加载的
但如果这样运行,会有下面的错误
using KokoroSharp;
using KokoroSharp.Core;
KokoroTTS tts = KokoroTTS.LoadModel("kokoro-v1.1-zh.onnx");
KokoroVoice heartVoice = KokoroVoiceManager.GetVoice("af_heart");
while (true)
{
tts.Speak(Console.ReadLine(), heartVoice);
}
输入任何文字回车后
Unhandled exception. Microsoft.ML.OnnxRuntime.OnnxRuntimeException: [ErrorCode:InvalidArgument] Input name: 'tokens' is not in the metadata
at Microsoft.ML.OnnxRuntime.InferenceSession.LookupInputMetadata(String nodeName)
at Microsoft.ML.OnnxRuntime.InferenceSession.LookupUtf8Names[T](IReadOnlyCollection`1 values, NameExtractor`1 nameExtractor, MetadataLookup metaLookup)
at Microsoft.ML.OnnxRuntime.InferenceSession.Run(IReadOnlyCollection`1 inputs, IReadOnlyCollection`1 outputNames, RunOptions options)
at Microsoft.ML.OnnxRuntime.InferenceSession.Run(IReadOnlyCollection`1 inputs, IReadOnlyCollection`1 outputNames)
at Microsoft.ML.OnnxRuntime.InferenceSession.Run(IReadOnlyCollection`1 inputs)
at KokoroSharp.Core.KokoroModel.Infer(Int32[] tokens, Single[,,] voiceStyle, Single speed) in E:\GitHub\KokoroSharp\KokoroSharp\Core\KokoroModel.cs:line 58
at KokoroSharp.Core.KokoroJob.Progress(KokoroModel model) in E:\GitHub\KokoroSharp\KokoroSharp\Core\KokoroJob.cs:line 28
at KokoroSharp.Core.KokoroEngine.<.ctor>b__2_0() in E:\GitHub\KokoroSharp\KokoroSharp\Core\KokoroEngine.cs:line 20
会说在元数据中找不到tokens
修改入口名
运行一个模型其实跟调用一个函数差不多
首先要确定的是这个模型的输入参数有什么
知道了区别后,就可以尝试修改入口名了
这里使用python修改这个入口名
import onnx
def ModEntryName(onnxModel: onnx.ModelProto, oldName: str, newName: str) -> onnx.ModelProto:
# 查找输入节点并修改
for input_tensor in onnxModel.graph.input:
if input_tensor.name == oldName:
input_tensor.name = newName
# 遍历修改后面的每个节点名
for node in onnxModel.graph.node:
for i, name in enumerate(node.input):
if name == oldName:
node.input[i] = newName
return onnxModel
model_path = "kokoro-v1.1-zh"
model = onnx.load(model_path + ".onnx")
ModEntryName(model, "input_ids", "tokens")
onnx.save(model, model_path + "-mod.onnx")
修改入口参数类型
像上面改好后,再次运行
还是有报错,如下
Unhandled exception. Microsoft.ML.OnnxRuntime.OnnxRuntimeException: [ErrorCode:InvalidArgument] Tensor element data type discovered: Float metadata expected: Int32
at Microsoft.ML.OnnxRuntime.ManagedTypeProjection.CreateTensorProjection(NamedOnnxValue node, NodeMetadata elementMeta)
at Microsoft.ML.OnnxRuntime.ManagedTypeProjection.CreateProjection(NamedOnnxValue namedOnnxValue, NodeMetadata metadata)
at Microsoft.ML.OnnxRuntime.NamedOnnxValue.InputToOrtValueHandle(NodeMetadata metadata, IDisposable& memoryOwner)
at Microsoft.ML.OnnxRuntime.InferenceSession.ExtractOrtValueHandleForInput(NamedOnnxValue input, NodeMetadata metadata, IDisposable& memOwner)
at Microsoft.ML.OnnxRuntime.InferenceSession.GetOrtValuesHandles(IReadOnlyCollection`1 values, MetadataLookup metaLookup, OrtValueHandleExtractor ortValueExtractor, DisposableArray`1& disposer)
at Microsoft.ML.OnnxRuntime.InferenceSession.Run(IReadOnlyCollection`1 inputs, IReadOnlyCollection`1 outputNames, RunOptions options)
at Microsoft.ML.OnnxRuntime.InferenceSession.Run(IReadOnlyCollection`1 inputs, IReadOnlyCollection`1 outputNames)
at Microsoft.ML.OnnxRuntime.InferenceSession.Run(IReadOnlyCollection`1 inputs)
at KokoroSharp.Core.KokoroModel.Infer(Int32[] tokens, Single[,,] voiceStyle, Single speed) in E:\GitHub\KokoroSharp\KokoroSharp\Core\KokoroModel.cs:line 58
at KokoroSharp.Core.KokoroJob.Progress(KokoroModel model) in E:\GitHub\KokoroSharp\KokoroSharp\Core\KokoroJob.cs:line 28
at KokoroSharp.Core.KokoroEngine.<.ctor>b__2_0() in E:\GitHub\KokoroSharp\KokoroSharp\Core\KokoroEngine.cs:line 20
参数错误说是
这时候需要通过下面的代码来检查模型的参数类型
using Microsoft.ML.OnnxRuntime;
// 原本使用的v1.0模型
using var kokoroV1 = new InferenceSession("kokoro.onnx");
// 现在打算使用的v1.1-zh模型
using var kokoroV1_1 = new InferenceSession("kokoro-v1.1-zh-mod.onnx");
Console.WriteLine("Kokoro v1.0");
foreach (var kvp in kokoroV1.InputMetadata)
{
var name = kvp.Key;
var meta = kvp.Value;
Console.WriteLine($"name: {name}");
Console.WriteLine($"type: {meta.ElementType}");
}
Console.WriteLine();
Console.WriteLine("Kokoro v1.1-zh");
foreach (var kvp in kokoroV1_1.InputMetadata)
{
var name = kvp.Key;
var meta = kvp.Value;
Console.WriteLine($"name: {name}");
Console.WriteLine($"type: {meta.ElementType}");
}
输出如下
Kokoro v1.0
name: tokens
type: System.Int64
name: style
type: System.Single
name: speed
type: System.Single
Kokoro v1.1-zh
name: tokens
type: System.Int64
name: style
type: System.Single
name: speed
type: System.Int32
可以看到speed参数类型不同,需要修改下
这里用python改入口参数的类型
import onnx
from onnx import helper, TensorProto
def ModEntryType(onnxModel: onnx.ModelProto, entryName: str, targetType) -> onnx.ModelProto:
graph = onnxModel.graph
for i, input_tensor in enumerate(graph.input):
if input_tensor.name == entryName:
# 获取原始维度
shape = [
dim.dim_value if dim.HasField("dim_value") else -1
for dim in input_tensor.type.tensor_type.shape.dim
]
new_input = helper.make_tensor_value_info(
name=input_tensor.name, elem_type=targetType, shape=shape
)
# 替换旧输入
graph.input.remove(input_tensor)
graph.input.insert(i, new_input)
onnx.checker.check_model(onnxModel)
return onnxModel
model_path = "kokoro-v1.1-zh"
model = onnx.load(model_path + ".onnx")
ModEntryType(model, "tokens", TensorProto.FLOAT)
onnx.save(model, model_path + "-mod.onnx")
然后替换模型文件
获取新voice
现在模型是可以跑了,但输出的语音还是英文
参考v1.0的文档,可以知道示例代码中的af_heart表示一种语音
所以我们要找到中文的语音叫什么
而又参考v1.1-zh的python示例,可以知道示例中传入的是zf_001
但直接这样传入会报这样的错误
Unhandled exception. System.InvalidOperationException: Sequence contains no matching element
at System.Linq.ThrowHelper.ThrowNoMatchException()
at System.Linq.Enumerable.First[TSource](IEnumerable`1 source, Func`2 predicate)
at KokoroSharp.KokoroVoiceManager.GetVoice(String name) in E:\GitHub\KokoroSharp\KokoroSharp\HighLevel\KokoroVoiceManager.cs:line 33
at Program.<Main>$(String[] args) in E:\Code\CSharp\RiderTest\Program.cs:line 34
就是说找不到zf_001这个元素
通过反编译,可以知道KokoroSharp是通过检索./voices文件夹来确定的
这个文件夹下面都是*.npy类型的文件
比如说之前的af_heart,就有af_heart.npy文件
那zf_001的哪里找呢
在v1.1-zh-onnx的huggingface上,还有voice文件夹
里面就是这些voice文件了,但都是bin格式的,还需要做一个转换
代码如下
import numpy as np
bin_path = "zf_001.bin"
data = np.fromfile(bin_path, dtype=np.float32)
data = data.reshape((510, 1, 256))
np.save("zf_001.npy", data)
然后把输出文件添加到运行目录中的./voices中就可以了
(也就是在项目中添加一个构建时复制的文件)
现在再运行下面的代码
using KokoroSharp;
using KokoroSharp.Core;
// 经过参数名称与类型修改的模型文件
KokoroTTS tts = KokoroTTS.LoadModel("kokoro-v1.1-zh-mod.onnx");
KokoroVoice testVoice = KokoroVoiceManager.GetVoice("zf_001");
while (true)
{
tts.Speak(Console.ReadLine(), testVoice);
}
总算是能跑了,但效果还是不大能满意
<0x02> 其他尝试
修改Tokenizer
我后面发现新的模型的tokenizer.json有变动
(v1.0版本的config.json,v1.1-zh版本的config.json)
于是我就想把这些应用上
但KokoroSharp的Tokenizer将这些硬编码到代码中
所以我只能把代码clone下来修改
修改后只能说差别不大
尝试pull request中的修改
参考这个pull request修改了代码
也是差别不大
<0x03> 没办法
最后实在没办法,找到用onnx跑这个模型的python代码
跑了一下,一听,您猜怎么着
跑出来效果跟我用C#跑的差不多烂
所以我觉得是torch转换到onnx导致效果没那么好
比起之前的话更像是不会中文的人说中文,也算有点效果