# TinySilero VAD 性能优化报告
本文档记录了将 Rust 实现的 `TinySilero` VAD (Voice Activity Detection) 引擎性能从 **6,250 FPS** 提升至 **24,500+ FPS** 的优化过程。
## 1. 背景与基准 (Baseline)
`TinySilero` 是 Silero VAD 模型的纯 Rust 实现,旨在摆脱对 ONNX Runtime 的依赖,提供更轻量、更易部署的解决方案。
初始版本的实现直接翻译了 PyTorch/ONNX 的计算图:
- **STFT**: 使用 `Conv1d` (Kernel=256, Stride=128) 实现。
- **Encoder**: 4 层 `Conv1d`。
- **Decoder**: LSTM + 全连接层。
- **内存**: 每次 `process` 调用都会分配新的 `Vec<f32>`。
**基准性能 (Baseline):**
- **单帧耗时**: ~160.0 µs
- **吞吐量**: ~6,250 FPS
- **实时倍率 (RTF)**: ~125x
---
## 2. 优化过程
### 阶段 1: 零拷贝内存管理 (Zero-copy Memory Management)
**问题**:
在 `process` 函数中,将输入的 `i16` 音频采样转换为 `f32` 时,每次都会分配一个新的 `Vec<f32>`。这导致了频繁的堆内存分配和释放,增加了 GC 压力(虽然 Rust 没有 GC,但分配器有开销)和内存碎片。
**优化方案**:
引入 `buf_chunk_f32` 成员变量作为复用缓冲区。使用 `std::mem::take` 技巧在不违反借用规则的情况下复用缓冲区。
```rust
// Before
// After
let mut chunk_f32 = std::mem::take(&mut self.buf_chunk_f32);
// ... fill chunk_f32 ...
self.predict(&chunk_f32);
self.buf_chunk_f32 = chunk_f32; // Restore
```
**结果**:
性能提升微小,但消除了运行时的内存抖动,为后续优化打下基础。
### 阶段 2: 算法替换 (FFT 替代卷积 STFT)
**问题**:
原始模型使用 `Conv1d` 层来实现 STFT (短时傅里叶变换)。
- 卷积复杂度: $O(N^2)$ (对于每个输出点,都要进行 N 次乘加)。
- STFT 窗口大小为 256,计算量较大。
**优化方案**:
通过分析权重文件,确认 `stft_weight` 实际上是 Hann Window 加权的 DFT 基。我们将 `Conv1d` 替换为 `realfft` 库提供的 FFT 实现。
- FFT 复杂度: $O(N \log N)$。
**结果**:
- **单帧耗时**: ~143.0 µs
- **吞吐量**: ~6,990 FPS
- **提升**: +12%
### 阶段 3: 核心计算层特化 (Enc0 Specialization)
**问题**:
在引入 FFT 后,性能分析显示第一层编码器 (`enc0`) 成为新的瓶颈。
- `enc0` 参数: Input Channels=129, Output Channels=128, Kernel=3, Stride=1, Padding=1。
- 输入长度 (Time) 仅为 3。
- 通用的 `Conv1d` 实现包含大量的边界检查、循环开销和条件判断。
**优化方案**:
针对 `enc0` 的特定形状进行手动循环展开 (Loop Unrolling) 和特化。
- 移除边界检查 (使用 `unsafe { *ptr.get_unchecked(i) }`)。
- 硬编码循环次数。
- 显式加载变量以利用寄存器。
```rust
// 伪代码示例
let x0 = unsafe { *input.get_unchecked(in_offset) };
let x1 = unsafe { *input.get_unchecked(in_offset + 1) };
let x2 = unsafe { *input.get_unchecked(in_offset + 2) };
// ... 直接计算 sum0, sum1, sum2 ...
```
**结果**:
- **单帧耗时**: ~101.5 µs
- **吞吐量**: ~9,852 FPS
- **提升**: +58% (相比 Baseline)
### 阶段 4: 全面特化 (Full Specialization)
**问题**:
虽然 `enc0` 已优化,但后续的 `enc1`, `enc2`, `enc3` 仍然使用通用的 `Conv1d` 逻辑。这些层的输入长度更短 (Time=3, 2, 1),通用逻辑的开销占比极高。
**优化方案**:
对剩余的所有卷积层进行类似的特化处理:
- **Enc1**: Input Len=3 -> Output Len=2 (Stride=2)
- **Enc2**: Input Len=2 -> Output Len=1 (Stride=2)
- **Enc3**: Input Len=1 -> Output Len=1 (Stride=1)
**结果**:
- **单帧耗时**: ~76.4 µs
- **吞吐量**: ~24,528 FPS
- **提升**: +292% (相比 Baseline)
---
## 3. 最终性能对比
测试环境: macOS, Apple Silicon (单核性能)
测试数据: 60秒 16kHz 音频
| **WebRTC VAD** | C/C++ (Bind) | ~3.1 ms | ~960,000 | ~20,000x | 传统算法,极快但准确率较低 |
| **TinyTen** | Rust (Hand-opt) | ~44.9 ms | ~41,700 | ~850x | 极小模型 |
| **ONNX Ten** | ONNX Runtime | ~99.6 ms | ~37,600 | ~760x | |
| **TinySilero (F32)** | **Rust (Optimized)** | **~76.4 ms** | **~24,528** | **~785x** | **本项目** |
| **TinySilero (INT8)** | Rust (Quantized) | ~129.9 ms | ~14,429 | ~460x | 量化版 (未做同等程度的特化) |
| **ONNX Silero** | ONNX Runtime | ~158.3 ms | ~11,842 | ~380x | 标准基准 |
## 4. 结论
通过算法改进(FFT)和针对特定计算图形状的极致代码特化(Loop Unrolling, Unsafe Pointer Arithmetic),我们将纯 Rust 实现的 `TinySilero` 性能提升了近 **4 倍**。
目前,该实现:
1. **速度超越 ONNX Runtime**: 比高度优化的 ONNX Runtime 快 **2 倍以上**。
2. **速度超越 INT8 版本**: 在特定场景下,极致优化的 F32 代码甚至击败了普通的 INT8 实现。
3. **生产就绪**: 单核即可处理 200+ 路实时音频流,且无外部重型依赖。
---
# TinyTen VAD 性能优化报告
在完成 TinySilero 的优化后,我们将相同的优化策略应用于更轻量级的 `TinyTen` 模型。
## 1. 背景与基准 (Baseline)
`TinyTen` 是一个基于深度可分离卷积 (Depthwise Separable Convolution) 的极轻量级 VAD 模型。
- **结构**: 包含 3 个卷积块 (Conv Block),每个块由 Depthwise Conv (3x3 或 1x3) + Pointwise Conv (1x1) + ReLU 组成。
- **特点**: 输入张量极小 (3x41),通用卷积实现的循环和分支开销占比极大。
**基准性能 (Baseline):**
- **处理 60s 音频耗时**: ~44.7 ms
- **吞吐量**: ~41,900 FPS
- **实时倍率 (RTF)**: ~838x
## 2. 优化过程
### 策略: 针对性特化 (Specialization)
由于 `TinyTen` 的每一层卷积核形状和步长都是固定的,我们采用了与 TinySilero 相同的策略:**硬编码卷积核逻辑,手动展开循环**。
### 阶段 1: Conv1 与 MaxPool 优化
**目标**:
- **Conv1_DW**: Kernel=3x3, Stride=1, Padding=1 (Depthwise)
- **Conv1_PW**: Kernel=1x1, Stride=1 (Pointwise)
- **MaxPool**: Kernel=1x3, Stride=1x2
**优化**:
- 针对 3x3 Depthwise 卷积,直接展开 9 次乘加运算。
- 针对 1x1 Pointwise 卷积,直接展开输入通道循环。
- 针对 MaxPool,移除通用逻辑,直接比较 3 个元素。
**结果**:
- **耗时**: ~41.88 ms
- **吞吐量**: ~44,700 FPS
- **提升**: +6.7%
### 阶段 2: Conv2 优化
**目标**:
- **Conv2_DW**: Kernel=1x3, Stride=1x2 (Depthwise)
- **Conv2_PW**: Kernel=1x1, Stride=1 (Pointwise)
**优化**:
- 针对 1x3 (实际上是 1D 卷积) 且 Stride=2 的情况进行特化。
- 减少了一半的计算量和内存访问。
**结果**:
- **耗时**: ~32.08 ms
- **吞吐量**: ~58,400 FPS
- **提升**: +30.6% (相比上一阶段)
### 阶段 3: Conv3 优化
**目标**:
- **Conv3_DW**: Kernel=1x3, Stride=1x2 (Depthwise)
- **Conv3_PW**: Kernel=1x1, Stride=1 (Pointwise)
**优化**:
- 类似于 Conv2,对最后一层卷积进行特化。
**结果**:
- **耗时**: ~31.75 ms
- **吞吐量**: ~59,060 FPS
- **提升**: +1.1% (相比上一阶段)
## 3. 最终总结
通过对所有卷积层和池化层的完全特化,`TinyTen` 的性能得到了显著提升。
| **Baseline** | 44.70 ms | 41,900 | 838x | - |
| **Optimized** | 31.75 ms | 59,060 | 1181x | **+40.9%** |
**结论**:
对于小模型和小输入尺寸,通用算子的开销是巨大的。通过手动展开循环和特化算子,我们消除了绝大部分的运行时开销,使得 Rust 实现的推理速度达到了极高的水平。