You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift用Accelerate复现Torch STFT遇结果偏差,求技术解惑

问题:Swift中用Accelerate/vDSP重实现PyTorch STFT生成Log Mel频谱图的差异问题

背景

我正在Swift环境下使用Accelerate/vDSP库重实现PyTorch的STFT代码,目标是生成Log Mel频谱图作为OpenAI Whisper模型CoreML移植版本的输入。

结果差异

PyTorch原生STFT/Mel代码生成的频谱图(注:原始float32数据导入Photoshop导致被截断):
PyTorch生成的频谱图

我生成的频谱图:
自己生成的频谱图

两者在数值和频率分量偏移上存在明显差异。

参考依据

PyTorch的STFT官方文档:torch.stft

STFT公式:

$$X[\omega,m]= \sum_{k=0}^{win_length-1} window[k] \cdot input[m×hop_length+k] \cdot exp\left(−j \cdot \frac{2π⋅ωk}{win_length}\right)$$

困惑点

我认为自己已正确处理window[k] * input[m×hop_length+k]部分,但对以下内容存在疑问:

  • 公式中的指数计算逻辑
  • -j的含义(复数虚部单位)
  • 如何在vDSP中对应实现该指数运算
  • 如何通过求和得到所需的200个元素结果

我的实现代码

func processData(audio: [Int16]) -> [Float]
{
    assert(self.sampleCount == audio.count)
        
    var audioFloat:[Float] = [Float](repeating: 0, count: audio.count)
            
    vDSP.convertElements(of: audio, to: &audioFloat)
    
    vDSP.divide(audioFloat, 32768.0, result: &audioFloat)
    
    // 到这一步,Python和Swift的数值完全一致
         
    // 在前后各插入numFFT/2个0样本,凑出额外的numFFT长度用于处理
    // TODO: 这一步是必须的吗?
    audioFloat.insert(contentsOf: [Float](repeating: 0, count: self.numFFT/2), at: 0)
    audioFloat.append(contentsOf: [Float](repeating: 0, count: self.numFFT/2))

    // 存储FFT结果的拆分复数数组
    var allSampleReal = [[Float]](repeating: [Float](repeating: 0, count: self.numFFT/2), count: self.melSampleCount)
    var allSampleImaginary = [[Float]](repeating: [Float](repeating: 0, count: self.numFFT/2), count: self.melSampleCount)

    
    // 步骤2 - 需要创建200×3000的STFT矩阵 - 注意输出应该是复数?
    for (m) in 0 ..< self.melSampleCount
    {
        // 每隔hopCount取一段numFFT长度的样本,用于生成梅尔频谱
        // audioFrame最终存放拆分后的复数
        var audioFrame = Array<Float>( audioFloat[ (m * self.hopCount) ..< ( (m * self.hopCount) + self.numFFT) ] )
        
        // audioFrame原始样本的副本
        let audioFrameOriginal = audioFrame
        
        assert(audioFrame.count == self.numFFT)
        
        // 存储单帧音频FFT结果的拆分复数数组,之后会追加到allSample系列数组中
        var sampleReal:[Float] = [Float](repeating: 0, count: self.numFFT/2)
        var sampleImaginary:[Float] = [Float](repeating: 0, count: self.numFFT/2)

        sampleReal.withUnsafeMutableBytes { unsafeReal in
            sampleImaginary.withUnsafeMutableBytes { unsafeImaginary in
                
                vDSP.multiply(audioFrame,
                              hanningWindow,
                              result: &audioFrame)

                var complexSignal = DSPSplitComplex(realp: unsafeReal.bindMemory(to: Float.self).baseAddress!,
                                                    imagp: unsafeImaginary.bindMemory(to: Float.self).baseAddress!)
                       
                audioFrame.withUnsafeBytes { unsafeAudioBytes in
                    vDSP.convert(interleavedComplexVector: [DSPComplex](unsafeAudioBytes.bindMemory(to: DSPComplex.self)),
                                 toSplitComplexVector: &complexSignal)
                }
                
                // 步骤3 - 执行FFT
                self.fft.forward(input: complexSignal, output: &complexSignal)                    
            }
        }

        // 需要对齐PyTorch的torch.stft实现
        // 到这里我不知道该怎么继续了?
        
//        let twoπ = Float.pi * 2
//        let freqstep:Float = Float(16000 / (self.numFFT/2))
//
//        var w:Float = 0.0
//        for (k) in 0 ..< self.numFFT/2
//        {
//            let j:Float = sampleImaginary[k]
//            let sample = audioFrame[k]
//
//            let exponent = -j * ( (twoπ * freqstep * Float(k) ) / Float((self.numFFT/2)))
//
//            w += powf(sample, exponent)
//        }
        
        
        allSampleReal[m] = sampleReal
        allSampleImaginary[m] = sampleImaginary
    }
    
    // 现在allSample系列拆分复数数组中存储了3000个200维的实部和虚部FFT结果
    
    // 将其展平为3000×200的DSPSplitComplex值数组
    var flattnedReal:[Float] = allSampleReal.flatMap { $0 }
    var flattnedImaginary:[Float] = allSampleImaginary.flatMap { $0 }

}

内容的提问来源于stack exchange,提问作者vade

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 12:30:41