chantier B STT #1 : S1 unification engine (mel partagé + skeleton stt + VAD C++)
S1.1 mel extractor unifié (kazeia_mel.{h,cpp}) :
- Paramétrable : window (sym/periodic), compression (log/log10), post-norm
(none/whisper), FFT auto radix-2 (zero-pad N_FFT pas puissance de 2)
- Configs pré-faites : config_qwen3_tts_speaker() + config_whisper()
- speaker_encoder.cpp migré dessus : régression damien cos=0.9997 et bit-exact
(max abs diff = 0) vs avant. E2E clonage in-process intact (RTF inchangé).
S1.2 audit ORT C++ :
- libonnxruntime.so + headers C++ trouvés dans cache Gradle (AAR Maven
onnxruntime-android-qnn:1.24.3). API_VERSION=24, QNN EP intégré.
- headers/ copiés dans dist/include/onnxruntime/ (versionné, ~1 MB)
- libonnxruntime.so copiée dans dist/lib/ (gitignore, 20 MB, README pour
reproduire l'extraction).
S1.3 skeleton stt_engine :
- stt_engine.{h,cpp} : API parallèle à tts_engine.h
(SttEngineLoadCfg / SttTranscribeCfg / SttTranscribeResult)
- VAD RMS C++ complet (port de VadStage.kt prod : frame=1600, seuil=150,
3 frames speech, 8 frames silence)
- stt_engine_load + stt_engine_transcribe : stubs err=-99 jusqu'au port ORT
QNN en S2 (encoder NPU + decoder loop KV-cache + tokenizer BPE)
- Binaire kazeia_stt_test (1.2 MB statique) valide mel Whisper [80,3000] +
VAD sur audio FR 5s, plage de valeurs cohérente.
Reste S2 : port encoder/decoder NPU via Ort::Session + QNN EP, port tokenizer
BPE byte-level, override transcribe. Quand S2 done : S3 = JNI + façade Kotlin
+ suppression libmel_extractor.so / VadStage.kt / WhisperHybridEngine.kt.
This commit is contained in:
parent
82d206137f
commit
4b43d9e3d7
|
|
@ -9,3 +9,4 @@ eval/out/
|
||||||
dist/b-vulkan/
|
dist/b-vulkan/
|
||||||
dist/lib-chraac/
|
dist/lib-chraac/
|
||||||
models/
|
models/
|
||||||
|
dist/lib/libonnxruntime.so
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ CFLAGS=(
|
||||||
INCS=( -I"$INC" )
|
INCS=( -I"$INC" )
|
||||||
LIBS=(
|
LIBS=(
|
||||||
"$JNI/speaker_encoder.cpp"
|
"$JNI/speaker_encoder.cpp"
|
||||||
|
"$JNI/kazeia_mel.cpp"
|
||||||
-L"$LIB" -lggml -lggml-base -lggml-cpu
|
-L"$LIB" -lggml -lggml-base -lggml-cpu
|
||||||
-Wl,-rpath,'$ORIGIN'
|
-Wl,-rpath,'$ORIGIN'
|
||||||
-llog -ldl -lm
|
-llog -ldl -lm
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build kazeia_stt_test : binaire smoke-test S1.3 (mel Whisper + VAD RMS).
|
||||||
|
# Pas encore de dépendance ORT/QNN (ça vient en S2).
|
||||||
|
set -euo pipefail
|
||||||
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
NDK="${ANDROID_NDK_ROOT:-/opt/Kazeia/android-ndk-r27d}"
|
||||||
|
CXX="$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android31-clang++"
|
||||||
|
|
||||||
|
JNI="$HERE/jni"
|
||||||
|
OUT="$HERE/b-jni/kazeia_stt_test"
|
||||||
|
mkdir -p "$HERE/b-jni"
|
||||||
|
|
||||||
|
CFLAGS=(
|
||||||
|
-std=c++17 -O3 -fPIE
|
||||||
|
-march=armv8.6-a+i8mm+bf16+dotprod+fp16
|
||||||
|
-Wno-unused-parameter -Wno-unused-variable -Wno-sign-compare
|
||||||
|
-static-libstdc++
|
||||||
|
)
|
||||||
|
LIBS=(
|
||||||
|
"$JNI/stt_test.cpp"
|
||||||
|
"$JNI/stt_engine.cpp"
|
||||||
|
"$JNI/kazeia_mel.cpp"
|
||||||
|
-llog -ldl -lm
|
||||||
|
)
|
||||||
|
|
||||||
|
echo "== building $OUT =="
|
||||||
|
"$CXX" "${CFLAGS[@]}" "${LIBS[@]}" -o "$OUT"
|
||||||
|
ls -la "$OUT"
|
||||||
|
|
@ -37,6 +37,7 @@ LIBS=(
|
||||||
"$JNI/sampler.cpp"
|
"$JNI/sampler.cpp"
|
||||||
"$JNI/kazeia_text_tokenizer.cpp"
|
"$JNI/kazeia_text_tokenizer.cpp"
|
||||||
"$JNI/speaker_encoder.cpp"
|
"$JNI/speaker_encoder.cpp"
|
||||||
|
"$JNI/kazeia_mel.cpp"
|
||||||
"$DECODER_A"
|
"$DECODER_A"
|
||||||
-L"$LIB" -lllama -lggml -lggml-base -lggml-cpu
|
-L"$LIB" -lllama -lggml -lggml-base -lggml-cpu
|
||||||
-Wl,-rpath,'$ORIGIN'
|
-Wl,-rpath,'$ORIGIN'
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
#include "onnxruntime_c_api.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \param use_arena zero: false. non-zero: true.
|
||||||
|
*/
|
||||||
|
ORT_EXPORT
|
||||||
|
ORT_API_STATUS(OrtSessionOptionsAppendExecutionProvider_CPU, _In_ OrtSessionOptions* options, int use_arena)
|
||||||
|
ORT_ALL_ARGS_NONNULL;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// This file contains well-known keys for OrtEnv configuration entries, which may be used to configure EPs or
|
||||||
|
// other global settings.
|
||||||
|
// Refer to OrtEnvCreationOptions::config_entries and OrtApi::CreateEnvWithOptions.
|
||||||
|
// This file does NOT specify all available keys as EPs may accept custom entries with the prefix "ep.<ep_name>.".
|
||||||
|
|
||||||
|
// Key for a boolean option that, when enabled, allows EP factories to create virtual OrtHardwareDevice
|
||||||
|
// instances via OrtEpApi::CreateHardwareDevice().
|
||||||
|
//
|
||||||
|
// This config entry is automatically set to "1" by ORT if an application registers an EP library with a registration
|
||||||
|
// name that ends in the suffix ".virtual". See OrtApi::RegisterExecutionProviderLibrary().
|
||||||
|
//
|
||||||
|
// Note: A virtual OrtHardwareDevice does not represent actual hardware on the device, and is identified via the
|
||||||
|
// metadata entry "is_virtual" with a value of "1".
|
||||||
|
//
|
||||||
|
// Allowed values:
|
||||||
|
// - "0": Default. Creation of virtual devices is not allowed.
|
||||||
|
// This is the assumed default value if this key is not present in the environment's configuration entries.
|
||||||
|
// - "1": Creation of virtual devices is allowed.
|
||||||
|
static const char* const kOrtEnvAllowVirtualDevices = "allow_virtual_devices";
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// This file contains well-known keys for OrtEpDevice and OrtHardwareDevice metadata entries.
|
||||||
|
// It does NOT specify all available metadata keys.
|
||||||
|
|
||||||
|
// Key for the execution provider version string. This should be available for all plugin EPs.
|
||||||
|
static const char* const kOrtEpDevice_EpMetadataKey_Version = "version";
|
||||||
|
|
||||||
|
// Key for the execution provider OS driver version.
|
||||||
|
static const char* const kOrtEpDevice_EpMetadataKey_OSDriverVersion = "os_driver_version";
|
||||||
|
|
||||||
|
// Prefix for execution provider compatibility information stored in model metadata.
|
||||||
|
// Used when generating EP context models to store compatibility strings for each EP.
|
||||||
|
// Full key format: "ep_compatibility_info.<EP_TYPE>"
|
||||||
|
static const char* const kOrtModelMetadata_EpCompatibilityInfoPrefix = "ep_compatibility_info.";
|
||||||
|
|
||||||
|
// Key for the execution provider library path (for dynamically loaded EPs)
|
||||||
|
static const char* const kOrtEpDevice_EpMetadataKey_LibraryPath = "library_path";
|
||||||
|
|
||||||
|
// Optional metadata key to determine if a OrtHardwareDevice represents a virtual (non-hardware) device.
|
||||||
|
// Possible values:
|
||||||
|
// - "0": OrtHardwareDevice is not virtual (i.e., actual hardware device). This is the assumed default value
|
||||||
|
// if this metadata key is not present.
|
||||||
|
// - "1": OrtHardwareDevice is virtual.
|
||||||
|
static const char* const kOrtHardwareDevice_MetadataKey_IsVirtual = "is_virtual";
|
||||||
|
|
@ -0,0 +1,537 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace onnxruntime_float16 {
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
enum class endian {
|
||||||
|
#if defined(_WIN32)
|
||||||
|
little = 0,
|
||||||
|
big = 1,
|
||||||
|
native = little,
|
||||||
|
#elif defined(__GNUC__) || defined(__clang__)
|
||||||
|
little = __ORDER_LITTLE_ENDIAN__,
|
||||||
|
big = __ORDER_BIG_ENDIAN__,
|
||||||
|
native = __BYTE_ORDER__,
|
||||||
|
#else
|
||||||
|
#error onnxruntime_float16::detail::endian is not implemented in this environment.
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
endian::native == endian::little || endian::native == endian::big,
|
||||||
|
"Only little-endian or big-endian native byte orders are supported.");
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shared implementation between public and internal classes. CRTP pattern.
|
||||||
|
/// </summary>
|
||||||
|
template <class Derived>
|
||||||
|
struct Float16Impl {
|
||||||
|
protected:
|
||||||
|
/// <summary>
|
||||||
|
/// Converts from float to uint16_t float16 representation
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="v"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
constexpr static uint16_t ToUint16Impl(float v) noexcept;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts float16 to float
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>float representation of float16 value</returns>
|
||||||
|
float ToFloatImpl() const noexcept;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an instance that represents absolute value.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Absolute value</returns>
|
||||||
|
uint16_t AbsImpl() const noexcept {
|
||||||
|
return static_cast<uint16_t>(val & ~kSignMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance with the sign flipped.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Flipped sign instance</returns>
|
||||||
|
uint16_t NegateImpl() const noexcept {
|
||||||
|
return IsNaN() ? val : static_cast<uint16_t>(val ^ kSignMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// uint16_t special values
|
||||||
|
static constexpr uint16_t kSignMask = 0x8000U;
|
||||||
|
static constexpr uint16_t kBiasedExponentMask = 0x7C00U;
|
||||||
|
static constexpr uint16_t kPositiveInfinityBits = 0x7C00U;
|
||||||
|
static constexpr uint16_t kNegativeInfinityBits = 0xFC00U;
|
||||||
|
static constexpr uint16_t kPositiveQNaNBits = 0x7E00U;
|
||||||
|
static constexpr uint16_t kNegativeQNaNBits = 0xFE00U;
|
||||||
|
static constexpr uint16_t kMaxValueBits = 0x7BFFU; // Largest normal number
|
||||||
|
static constexpr uint16_t kMinValueBits = 0xFBFFU; // Lowest normal number
|
||||||
|
static constexpr uint16_t kOneBits = 0x3C00U;
|
||||||
|
static constexpr uint16_t kMinusOneBits = 0xBC00U;
|
||||||
|
|
||||||
|
uint16_t val{0};
|
||||||
|
|
||||||
|
Float16Impl() = default;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the value is negative
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if negative</returns>
|
||||||
|
bool IsNegative() const noexcept {
|
||||||
|
return static_cast<int16_t>(val) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests if the value is NaN
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if NaN</returns>
|
||||||
|
bool IsNaN() const noexcept {
|
||||||
|
return AbsImpl() > kPositiveInfinityBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests if the value is finite
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if finite</returns>
|
||||||
|
bool IsFinite() const noexcept {
|
||||||
|
return AbsImpl() < kPositiveInfinityBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests if the value represents positive infinity.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if positive infinity</returns>
|
||||||
|
bool IsPositiveInfinity() const noexcept {
|
||||||
|
return val == kPositiveInfinityBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests if the value represents negative infinity
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if negative infinity</returns>
|
||||||
|
bool IsNegativeInfinity() const noexcept {
|
||||||
|
return val == kNegativeInfinityBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests if the value is either positive or negative infinity.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if absolute value is infinity</returns>
|
||||||
|
bool IsInfinity() const noexcept {
|
||||||
|
return AbsImpl() == kPositiveInfinityBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests if the value is NaN or zero. Useful for comparisons.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if NaN or zero.</returns>
|
||||||
|
bool IsNaNOrZero() const noexcept {
|
||||||
|
auto abs = AbsImpl();
|
||||||
|
return (abs == 0 || abs > kPositiveInfinityBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests if the value is normal (not zero, subnormal, infinite, or NaN).
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if so</returns>
|
||||||
|
bool IsNormal() const noexcept {
|
||||||
|
auto abs = AbsImpl();
|
||||||
|
return (abs < kPositiveInfinityBits) // is finite
|
||||||
|
&& (abs != 0) // is not zero
|
||||||
|
&& ((abs & kBiasedExponentMask) != 0); // is not subnormal (has a non-zero exponent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests if the value is subnormal (denormal).
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if so</returns>
|
||||||
|
bool IsSubnormal() const noexcept {
|
||||||
|
auto abs = AbsImpl();
|
||||||
|
return (abs < kPositiveInfinityBits) // is finite
|
||||||
|
&& (abs != 0) // is not zero
|
||||||
|
&& ((abs & kBiasedExponentMask) == 0); // is subnormal (has a zero exponent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an instance that represents absolute value.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Absolute value</returns>
|
||||||
|
Derived Abs() const noexcept { return Derived::FromBits(AbsImpl()); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance with the sign flipped.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Flipped sign instance</returns>
|
||||||
|
Derived Negate() const noexcept { return Derived::FromBits(NegateImpl()); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// IEEE defines that positive and negative zero are equal, this gives us a quick equality check
|
||||||
|
/// for two values by or'ing the private bits together and stripping the sign. They are both zero,
|
||||||
|
/// and therefore equivalent, if the resulting value is still zero.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lhs">first value</param>
|
||||||
|
/// <param name="rhs">second value</param>
|
||||||
|
/// <returns>True if both arguments represent zero</returns>
|
||||||
|
static bool AreZero(const Float16Impl& lhs, const Float16Impl& rhs) noexcept {
|
||||||
|
return static_cast<uint16_t>((lhs.val | rhs.val) & ~kSignMask) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const Float16Impl& rhs) const noexcept {
|
||||||
|
if (IsNaN() || rhs.IsNaN()) {
|
||||||
|
// IEEE defines that NaN is not equal to anything, including itself.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return val == rhs.val;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const Float16Impl& rhs) const noexcept { return !(*this == rhs); }
|
||||||
|
|
||||||
|
bool operator<(const Float16Impl& rhs) const noexcept {
|
||||||
|
if (IsNaN() || rhs.IsNaN()) {
|
||||||
|
// IEEE defines that NaN is unordered with respect to everything, including itself.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool left_is_negative = IsNegative();
|
||||||
|
if (left_is_negative != rhs.IsNegative()) {
|
||||||
|
// When the signs of left and right differ, we know that left is less than right if it is
|
||||||
|
// the negative value. The exception to this is if both values are zero, in which case IEEE
|
||||||
|
// says they should be equal, even if the signs differ.
|
||||||
|
return left_is_negative && !AreZero(*this, rhs);
|
||||||
|
}
|
||||||
|
return (val != rhs.val) && ((val < rhs.val) ^ left_is_negative);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// The following Float16_t conversions are based on the code from
|
||||||
|
// Eigen library.
|
||||||
|
|
||||||
|
// The conversion routines are Copyright (c) Fabian Giesen, 2016.
|
||||||
|
// The original license follows:
|
||||||
|
//
|
||||||
|
// Copyright (c) Fabian Giesen, 2016
|
||||||
|
// All rights reserved.
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted.
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
union float32_bits {
|
||||||
|
unsigned int u;
|
||||||
|
float f;
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <class Derived>
|
||||||
|
inline constexpr uint16_t Float16Impl<Derived>::ToUint16Impl(float v) noexcept {
|
||||||
|
detail::float32_bits f{};
|
||||||
|
f.f = v;
|
||||||
|
|
||||||
|
constexpr detail::float32_bits f32infty = {255 << 23};
|
||||||
|
constexpr detail::float32_bits f16max = {(127 + 16) << 23};
|
||||||
|
constexpr detail::float32_bits denorm_magic = {((127 - 15) + (23 - 10) + 1) << 23};
|
||||||
|
constexpr unsigned int sign_mask = 0x80000000u;
|
||||||
|
uint16_t val = static_cast<uint16_t>(0x0u);
|
||||||
|
|
||||||
|
unsigned int sign = f.u & sign_mask;
|
||||||
|
f.u ^= sign;
|
||||||
|
|
||||||
|
// NOTE all the integer compares in this function can be safely
|
||||||
|
// compiled into signed compares since all operands are below
|
||||||
|
// 0x80000000. Important if you want fast straight SSE2 code
|
||||||
|
// (since there's no unsigned PCMPGTD).
|
||||||
|
|
||||||
|
if (f.u >= f16max.u) { // result is Inf or NaN (all exponent bits set)
|
||||||
|
val = (f.u > f32infty.u) ? 0x7e00 : 0x7c00; // NaN->qNaN and Inf->Inf
|
||||||
|
} else { // (De)normalized number or zero
|
||||||
|
if (f.u < (113 << 23)) { // resulting FP16 is subnormal or zero
|
||||||
|
// use a magic value to align our 10 mantissa bits at the bottom of
|
||||||
|
// the float. as long as FP addition is round-to-nearest-even this
|
||||||
|
// just works.
|
||||||
|
f.f += denorm_magic.f;
|
||||||
|
|
||||||
|
// and one integer subtract of the bias later, we have our final float!
|
||||||
|
val = static_cast<uint16_t>(f.u - denorm_magic.u);
|
||||||
|
} else {
|
||||||
|
unsigned int mant_odd = (f.u >> 13) & 1; // resulting mantissa is odd
|
||||||
|
|
||||||
|
// update exponent, rounding bias part 1
|
||||||
|
// Equivalent to `f.u += ((unsigned int)(15 - 127) << 23) + 0xfff`, but
|
||||||
|
// without arithmetic overflow.
|
||||||
|
f.u += 0xc8000fffU;
|
||||||
|
// rounding bias part 2
|
||||||
|
f.u += mant_odd;
|
||||||
|
// take the bits!
|
||||||
|
val = static_cast<uint16_t>(f.u >> 13);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val |= static_cast<uint16_t>(sign >> 16);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Derived>
|
||||||
|
inline float Float16Impl<Derived>::ToFloatImpl() const noexcept {
|
||||||
|
constexpr detail::float32_bits magic = {113 << 23};
|
||||||
|
constexpr unsigned int shifted_exp = 0x7c00 << 13; // exponent mask after shift
|
||||||
|
detail::float32_bits o{};
|
||||||
|
|
||||||
|
o.u = (val & 0x7fff) << 13; // exponent/mantissa bits
|
||||||
|
unsigned int exp = shifted_exp & o.u; // just the exponent
|
||||||
|
o.u += (127 - 15) << 23; // exponent adjust
|
||||||
|
|
||||||
|
// handle exponent special cases
|
||||||
|
if (exp == shifted_exp) { // Inf/NaN?
|
||||||
|
o.u += (128 - 16) << 23; // extra exp adjust
|
||||||
|
} else if (exp == 0) { // Zero/Denormal?
|
||||||
|
o.u += 1 << 23; // extra exp adjust
|
||||||
|
o.f -= magic.f; // re-normalize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to workaround the Internal Compiler Error on ARM64
|
||||||
|
// for bitwise | operator, including std::bitset
|
||||||
|
#if (defined _MSC_VER) && (defined _M_ARM || defined _M_ARM64 || defined _M_ARM64EC)
|
||||||
|
if (IsNegative()) {
|
||||||
|
return -o.f;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// original code:
|
||||||
|
o.u |= (val & 0x8000U) << 16U; // sign bit
|
||||||
|
#endif
|
||||||
|
return o.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shared implementation between public and internal classes. CRTP pattern.
|
||||||
|
template <class Derived>
|
||||||
|
struct BFloat16Impl {
|
||||||
|
protected:
|
||||||
|
/// <summary>
|
||||||
|
/// Converts from float to uint16_t float16 representation
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="v"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
static uint16_t ToUint16Impl(float v) noexcept;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts bfloat16 to float
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>float representation of bfloat16 value</returns>
|
||||||
|
float ToFloatImpl() const noexcept;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an instance that represents absolute value.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Absolute value</returns>
|
||||||
|
uint16_t AbsImpl() const noexcept {
|
||||||
|
return static_cast<uint16_t>(val & ~kSignMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance with the sign flipped.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Flipped sign instance</returns>
|
||||||
|
uint16_t NegateImpl() const noexcept {
|
||||||
|
return IsNaN() ? val : static_cast<uint16_t>(val ^ kSignMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// uint16_t special values
|
||||||
|
static constexpr uint16_t kSignMask = 0x8000U;
|
||||||
|
static constexpr uint16_t kBiasedExponentMask = 0x7F80U;
|
||||||
|
static constexpr uint16_t kPositiveInfinityBits = 0x7F80U;
|
||||||
|
static constexpr uint16_t kNegativeInfinityBits = 0xFF80U;
|
||||||
|
static constexpr uint16_t kPositiveQNaNBits = 0x7FC1U;
|
||||||
|
static constexpr uint16_t kNegativeQNaNBits = 0xFFC1U;
|
||||||
|
static constexpr uint16_t kMaxValueBits = 0x7F7FU;
|
||||||
|
static constexpr uint16_t kMinValueBits = 0xFF7FU;
|
||||||
|
static constexpr uint16_t kRoundToNearest = 0x7FFFU;
|
||||||
|
static constexpr uint16_t kOneBits = 0x3F80U;
|
||||||
|
static constexpr uint16_t kMinusOneBits = 0xBF80U;
|
||||||
|
|
||||||
|
uint16_t val{0};
|
||||||
|
|
||||||
|
BFloat16Impl() = default;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the value is negative
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if negative</returns>
|
||||||
|
bool IsNegative() const noexcept {
|
||||||
|
return static_cast<int16_t>(val) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests if the value is NaN
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if NaN</returns>
|
||||||
|
bool IsNaN() const noexcept {
|
||||||
|
return AbsImpl() > kPositiveInfinityBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests if the value is finite
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if finite</returns>
|
||||||
|
bool IsFinite() const noexcept {
|
||||||
|
return AbsImpl() < kPositiveInfinityBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests if the value represents positive infinity.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if positive infinity</returns>
|
||||||
|
bool IsPositiveInfinity() const noexcept {
|
||||||
|
return val == kPositiveInfinityBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests if the value represents negative infinity
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if negative infinity</returns>
|
||||||
|
bool IsNegativeInfinity() const noexcept {
|
||||||
|
return val == kNegativeInfinityBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests if the value is either positive or negative infinity.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if absolute value is infinity</returns>
|
||||||
|
bool IsInfinity() const noexcept {
|
||||||
|
return AbsImpl() == kPositiveInfinityBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests if the value is NaN or zero. Useful for comparisons.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if NaN or zero.</returns>
|
||||||
|
bool IsNaNOrZero() const noexcept {
|
||||||
|
auto abs = AbsImpl();
|
||||||
|
return (abs == 0 || abs > kPositiveInfinityBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests if the value is normal (not zero, subnormal, infinite, or NaN).
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if so</returns>
|
||||||
|
bool IsNormal() const noexcept {
|
||||||
|
auto abs = AbsImpl();
|
||||||
|
return (abs < kPositiveInfinityBits) // is finite
|
||||||
|
&& (abs != 0) // is not zero
|
||||||
|
&& ((abs & kBiasedExponentMask) != 0); // is not subnormal (has a non-zero exponent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests if the value is subnormal (denormal).
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if so</returns>
|
||||||
|
bool IsSubnormal() const noexcept {
|
||||||
|
auto abs = AbsImpl();
|
||||||
|
return (abs < kPositiveInfinityBits) // is finite
|
||||||
|
&& (abs != 0) // is not zero
|
||||||
|
&& ((abs & kBiasedExponentMask) == 0); // is subnormal (has a zero exponent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an instance that represents absolute value.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Absolute value</returns>
|
||||||
|
Derived Abs() const noexcept { return Derived::FromBits(AbsImpl()); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance with the sign flipped.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Flipped sign instance</returns>
|
||||||
|
Derived Negate() const noexcept { return Derived::FromBits(NegateImpl()); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// IEEE defines that positive and negative zero are equal, this gives us a quick equality check
|
||||||
|
/// for two values by or'ing the private bits together and stripping the sign. They are both zero,
|
||||||
|
/// and therefore equivalent, if the resulting value is still zero.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lhs">first value</param>
|
||||||
|
/// <param name="rhs">second value</param>
|
||||||
|
/// <returns>True if both arguments represent zero</returns>
|
||||||
|
static bool AreZero(const BFloat16Impl& lhs, const BFloat16Impl& rhs) noexcept {
|
||||||
|
// IEEE defines that positive and negative zero are equal, this gives us a quick equality check
|
||||||
|
// for two values by or'ing the private bits together and stripping the sign. They are both zero,
|
||||||
|
// and therefore equivalent, if the resulting value is still zero.
|
||||||
|
return static_cast<uint16_t>((lhs.val | rhs.val) & ~kSignMask) == 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Derived>
|
||||||
|
inline uint16_t BFloat16Impl<Derived>::ToUint16Impl(float v) noexcept {
|
||||||
|
uint16_t result;
|
||||||
|
if (std::isnan(v)) {
|
||||||
|
result = kPositiveQNaNBits;
|
||||||
|
} else {
|
||||||
|
auto get_msb_half = [](float fl) {
|
||||||
|
uint16_t result;
|
||||||
|
#ifdef __cpp_if_constexpr
|
||||||
|
if constexpr (detail::endian::native == detail::endian::little) {
|
||||||
|
#else
|
||||||
|
if (detail::endian::native == detail::endian::little) {
|
||||||
|
#endif
|
||||||
|
std::memcpy(&result, reinterpret_cast<char*>(&fl) + sizeof(uint16_t), sizeof(uint16_t));
|
||||||
|
} else {
|
||||||
|
std::memcpy(&result, &fl, sizeof(uint16_t));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
uint16_t upper_bits = get_msb_half(v);
|
||||||
|
union {
|
||||||
|
uint32_t U32;
|
||||||
|
float F32;
|
||||||
|
};
|
||||||
|
F32 = v;
|
||||||
|
U32 += (upper_bits & 1) + kRoundToNearest;
|
||||||
|
result = get_msb_half(F32);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Derived>
|
||||||
|
inline float BFloat16Impl<Derived>::ToFloatImpl() const noexcept {
|
||||||
|
if (IsNaN()) {
|
||||||
|
return std::numeric_limits<float>::quiet_NaN();
|
||||||
|
}
|
||||||
|
float result;
|
||||||
|
char* const first = reinterpret_cast<char*>(&result);
|
||||||
|
char* const second = first + sizeof(uint16_t);
|
||||||
|
#ifdef __cpp_if_constexpr
|
||||||
|
if constexpr (detail::endian::native == detail::endian::little) {
|
||||||
|
#else
|
||||||
|
if (detail::endian::native == detail::endian::little) {
|
||||||
|
#endif
|
||||||
|
std::memset(first, 0, sizeof(uint16_t));
|
||||||
|
std::memcpy(second, &val, sizeof(uint16_t));
|
||||||
|
} else {
|
||||||
|
std::memcpy(first, &val, sizeof(uint16_t));
|
||||||
|
std::memset(second, 0, sizeof(uint16_t));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace onnxruntime_float16
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,54 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file defines RunOptions Config Keys and format of the Config Values.
|
||||||
|
*
|
||||||
|
* The Naming Convention for a RunOptions Config Key,
|
||||||
|
* "[Area][.[SubArea1].[SubArea2]...].[Keyname]"
|
||||||
|
* Such as "ep.cuda.use_arena"
|
||||||
|
* The Config Key cannot be empty
|
||||||
|
* The maximum length of the Config Key is 128
|
||||||
|
*
|
||||||
|
* The string format of a RunOptions Config Value is defined individually for each Config.
|
||||||
|
* The maximum length of the Config Value is 1024
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Key for enabling shrinkages of user listed device memory arenas.
|
||||||
|
// Expects a list of semi-colon separated key value pairs separated by colon in the following format:
|
||||||
|
// "device_0:device_id_0;device_1:device_id_1"
|
||||||
|
// No white-spaces allowed in the provided list string.
|
||||||
|
// Currently, the only supported devices are : "cpu", "gpu" (case sensitive).
|
||||||
|
// If "cpu" is included in the list, DisableCpuMemArena() API must not be called (i.e.) arena for cpu should be enabled.
|
||||||
|
// Example usage: "cpu:0;gpu:0" (or) "gpu:0"
|
||||||
|
// By default, the value for this key is empty (i.e.) no memory arenas are shrunk
|
||||||
|
static const char* const kOrtRunOptionsConfigEnableMemoryArenaShrinkage = "memory.enable_memory_arena_shrinkage";
|
||||||
|
|
||||||
|
// Set to '1' to not synchronize execution providers with CPU at the end of session run.
|
||||||
|
// Per default it will be set to '0'
|
||||||
|
// Taking CUDA EP as an example, it omit triggering cudaStreamSynchronize on the compute stream.
|
||||||
|
static const char* const kOrtRunOptionsConfigDisableSynchronizeExecutionProviders = "disable_synchronize_execution_providers";
|
||||||
|
|
||||||
|
// Set HTP performance mode for QNN HTP backend before session run.
|
||||||
|
// options for HTP performance mode: "burst", "balanced", "default", "high_performance",
|
||||||
|
// "high_power_saver", "low_balanced", "extreme_power_saver", "low_power_saver", "power_saver",
|
||||||
|
// "sustained_high_performance". Default to "default".
|
||||||
|
static const char* const kOrtRunOptionsConfigQnnPerfMode = "qnn.htp_perf_mode";
|
||||||
|
|
||||||
|
// Set HTP performance mode for QNN HTP backend post session run.
|
||||||
|
static const char* const kOrtRunOptionsConfigQnnPerfModePostRun = "qnn.htp_perf_mode_post_run";
|
||||||
|
|
||||||
|
// Set RPC control latency for QNN HTP backend
|
||||||
|
static const char* const kOrtRunOptionsConfigQnnRpcControlLatency = "qnn.rpc_control_latency";
|
||||||
|
|
||||||
|
// Set QNN Lora Config File for apply Lora in QNN context binary
|
||||||
|
static const char* const kOrtRunOptionsConfigQnnLoraConfig = "qnn.lora_config";
|
||||||
|
|
||||||
|
// Set graph annotation id for CUDA EP. Use with enable_cuda_graph=true.
|
||||||
|
// The value should be an integer. If the value is not set, the default value is 0 and
|
||||||
|
// ORT session only captures one cuda graph before another capture is requested.
|
||||||
|
// If the value is set to -1, cuda graph capture/replay is disabled in that run.
|
||||||
|
// User are not expected to set the value to 0 as it is reserved for internal use.
|
||||||
|
static const char* const kOrtRunOptionsConfigCudaGraphAnnotation = "gpu_graph_id";
|
||||||
|
|
@ -0,0 +1,436 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file defines SessionOptions Config Keys and format of the Config Values.
|
||||||
|
*
|
||||||
|
* The Naming Convention for a SessionOptions Config Key,
|
||||||
|
* "[Area][.[SubArea1].[SubArea2]...].[Keyname]"
|
||||||
|
* Such as "ep.cuda.use_arena"
|
||||||
|
* The Config Key cannot be empty
|
||||||
|
* The maximum length of the Config Key is 1024
|
||||||
|
*
|
||||||
|
* The string format of a SessionOptions Config Value is defined individually for each Config.
|
||||||
|
* The maximum length of the Config Value is 8192
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Key for disable PrePacking,
|
||||||
|
// If the config value is set to "1" then the prepacking is disabled, otherwise prepacking is enabled (default value)
|
||||||
|
static const char* const kOrtSessionOptionsConfigDisablePrepacking = "session.disable_prepacking";
|
||||||
|
|
||||||
|
// A value of "1" means allocators registered in the env will be used. "0" means the allocators created in the session
|
||||||
|
// will be used. Use this to override the usage of env allocators on a per session level.
|
||||||
|
static const char* const kOrtSessionOptionsConfigUseEnvAllocators = "session.use_env_allocators";
|
||||||
|
|
||||||
|
// Set to 'ORT' (case sensitive) to load an ORT format model.
|
||||||
|
// If unset, model type will default to ONNX unless inferred from filename ('.ort' == ORT format) or bytes to be ORT
|
||||||
|
static const char* const kOrtSessionOptionsConfigLoadModelFormat = "session.load_model_format";
|
||||||
|
|
||||||
|
// Set to 'ORT' (case sensitive) to save optimized model in ORT format when SessionOptions.optimized_model_path is set.
|
||||||
|
// If unset, format will default to ONNX unless optimized_model_filepath ends in '.ort'.
|
||||||
|
static const char* const kOrtSessionOptionsConfigSaveModelFormat = "session.save_model_format";
|
||||||
|
|
||||||
|
// If a value is "1", flush-to-zero and denormal-as-zero are applied. The default is "0".
|
||||||
|
// When multiple sessions are created, a main thread doesn't override changes from succeeding session options,
|
||||||
|
// but threads in session thread pools follow option changes.
|
||||||
|
// When ORT runs with OpenMP, the same rule is applied, i.e. the first session option to flush-to-zero and
|
||||||
|
// denormal-as-zero is only applied to global OpenMP thread pool, which doesn't support per-session thread pool.
|
||||||
|
// Note that an alternative way not using this option at runtime is to train and export a model without denormals
|
||||||
|
// and that's recommended because turning this option on may hurt model accuracy.
|
||||||
|
static const char* const kOrtSessionOptionsConfigSetDenormalAsZero = "session.set_denormal_as_zero";
|
||||||
|
|
||||||
|
// It controls to run quantization model in QDQ (QuantizelinearDeQuantizelinear) format or not.
|
||||||
|
// "0": enable. ORT does fusion logic for QDQ format.
|
||||||
|
// "1": disable. ORT doesn't do fusion logic for QDQ format.
|
||||||
|
// Its default value is "0" unless the DirectML execution provider is registered, in which case it defaults to "1".
|
||||||
|
static const char* const kOrtSessionOptionsDisableQuantQDQ = "session.disable_quant_qdq";
|
||||||
|
|
||||||
|
// It controls whether to enable Double QDQ remover and Identical Children Consolidation
|
||||||
|
// "0": not to disable. ORT does remove the middle 2 Nodes from a Q->(QD->Q)->QD pairs
|
||||||
|
// "1": disable. ORT doesn't remove the middle 2 Nodes from a Q->(QD->Q)->QD pairs
|
||||||
|
// Its default value is "0"
|
||||||
|
static const char* const kOrtSessionOptionsDisableDoubleQDQRemover = "session.disable_double_qdq_remover";
|
||||||
|
|
||||||
|
// If set to "1", enables the removal of QuantizeLinear/DequantizeLinear node pairs once all QDQ handling has been
|
||||||
|
// completed. e.g. If after all QDQ handling has completed and we have -> FloatOp -> Q -> DQ -> FloatOp -> the
|
||||||
|
// Q -> DQ could potentially be removed. This will provide a performance benefit by avoiding going from float to
|
||||||
|
// 8-bit and back to float, but could impact accuracy. The impact on accuracy will be model specific and depend on
|
||||||
|
// other factors like whether the model was created using Quantization Aware Training or Post Training Quantization.
|
||||||
|
// As such, it's best to test to determine if enabling this works well for your scenario.
|
||||||
|
// The default value is "0"
|
||||||
|
// Available since version 1.11.
|
||||||
|
static const char* const kOrtSessionOptionsEnableQuantQDQCleanup = "session.enable_quant_qdq_cleanup";
|
||||||
|
|
||||||
|
// Enable or disable gelu approximation in graph optimization. "0": disable; "1": enable. The default is "0".
|
||||||
|
// GeluApproximation has side effects which may change the inference results. It is disabled by default due to this.
|
||||||
|
static const char* const kOrtSessionOptionsEnableGeluApproximation = "optimization.enable_gelu_approximation";
|
||||||
|
|
||||||
|
// Enable or disable Cast chain elimination in graph optimization. "0": disable; "1": enable. The default is "0".
|
||||||
|
// CastElimination with chain elimination has side effects which may change the inference results. It is disabled by default due to this.
|
||||||
|
static const char* const kOrtSessionOptionsEnableCastChainElimination = "optimization.enable_cast_chain_elimination";
|
||||||
|
|
||||||
|
// This setting controls whether to enable AheadOfTime function inlining.
|
||||||
|
// AOT function inlining examines the graph and attempts to inline as many locally defined functions in the model
|
||||||
|
// as possible with the help of enabled execution providers.
|
||||||
|
// This can reduce the number of function calls and improve performance because it is done before
|
||||||
|
// Level1 optimizers and constant folding. However, under some circumstances, when the EPs are not available,
|
||||||
|
// one can disable the AOT inlining, produce an optimized model and postpone AOT until run time.
|
||||||
|
// "0": enable; "1": disable.
|
||||||
|
// Its default value is "0".
|
||||||
|
static const char* const kOrtSessionOptionsDisableAheadOfTimeFunctionInlining = "session.disable_aot_function_inlining";
|
||||||
|
|
||||||
|
#ifdef ENABLE_TRAINING
|
||||||
|
// Specifies a path of the file containing a list of memory optimization configurations.
|
||||||
|
// The value should be a string indicating the file path of the config file.
|
||||||
|
// The content of the config file is a JSON struct like this:
|
||||||
|
// [
|
||||||
|
// "Gelu+Cast+:1:0",
|
||||||
|
// "Dropout+:1:1"
|
||||||
|
// ]
|
||||||
|
// Taking the example of "Gelu+Cast+:1:0",
|
||||||
|
// > "Gelu+Cast+" is the subgraph string, a valid "subgraph string" should be one subgraph representation
|
||||||
|
// output by ORT graph transformations.
|
||||||
|
// > "1" is "optimization strategy", valid values: 0 - disabled, 1 - recompute.
|
||||||
|
// > "0" is "number of subgraph to apply" which is used to control how many subgraphs to apply optimization,
|
||||||
|
// to avoid "oversaving" the memory.
|
||||||
|
static const char* const kOrtSessionOptionsMemoryOptimizerApplyConfig = "optimization.memory_optimizer_config";
|
||||||
|
|
||||||
|
// Specifies the config for detecting subgraphs for memory footprint reduction.
|
||||||
|
// The value should be a string contains int separated using commas. The default value is "0:0".
|
||||||
|
static const char* const kOrtSessionOptionsMemoryOptimizerProbeConfig = "optimization.enable_memory_probe_recompute_config";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// This setting if set should contain a comma separated list of optimizers names that should be disabled.
|
||||||
|
// Optimizers may take time to execute and affect model loading time. If you feel that a specific optimizer
|
||||||
|
// does not provider runtime benefits, but affects your model loading time you may disable it using this config
|
||||||
|
// entry. This option is not enabled in ORT_MINIMAL_BUILD build.
|
||||||
|
// A list of optimizes is available in onnxruntime/core/optimizer/graph_transformer_utils.cc
|
||||||
|
//
|
||||||
|
// Default is an empty string which means no optimizers are disabled.
|
||||||
|
static const char* const kOrtSessionOptionsDisableSpecifiedOptimizers = "optimization.disable_specified_optimizers";
|
||||||
|
|
||||||
|
// It controls whether to run graph optimizations in loop or not.
|
||||||
|
//
|
||||||
|
// "0": disable. Graph Optimization Loop is disabled.
|
||||||
|
// ```
|
||||||
|
// Level 2 --> Level 3 --> InsertCastTransforms --> Level 4
|
||||||
|
// ^ |
|
||||||
|
// | "No Loop" |
|
||||||
|
// | |
|
||||||
|
// X xxxxxxxxxxx X
|
||||||
|
// ```
|
||||||
|
// "1": enable. Graph Optimization Loop is enabled, such that, if optimizations at Level 4 are applied then
|
||||||
|
// the loop will check for any other valid optimization that can happen.
|
||||||
|
// ```
|
||||||
|
// Level 2 --> Level 3 --> InsertCastTransforms --> Level 4
|
||||||
|
// ^ |
|
||||||
|
// | "Loop only depending on Level 4" |
|
||||||
|
// | |
|
||||||
|
// ---------------------------------------------------
|
||||||
|
// ```
|
||||||
|
// "2": enable. Graph Optimization Loop is enabled, such that, if optimizations at Level 2 or above are applied then
|
||||||
|
// The loop will check for any other valid optimization that can happen.
|
||||||
|
// ```
|
||||||
|
// Level 2 --> Level 3 --> InsertCastTransforms --> Level 4
|
||||||
|
// ^ |
|
||||||
|
// | "Loop" |
|
||||||
|
// | |
|
||||||
|
// ---------------------------------------------------
|
||||||
|
// ```
|
||||||
|
// Default value is set to "1".
|
||||||
|
static const char* const kOrtSessionOptionsGraphOptimizationsLoopLevel = "session.graph_optimizations_loop_level";
|
||||||
|
|
||||||
|
// Enable or disable using device allocator for allocating initialized tensor memory. "1": enable; "0": disable. The default is "0".
|
||||||
|
// Using device allocators means the memory allocation is made using malloc/new.
|
||||||
|
static const char* const kOrtSessionOptionsUseDeviceAllocatorForInitializers = "session.use_device_allocator_for_initializers";
|
||||||
|
|
||||||
|
// Configure whether to allow the inter_op/intra_op threads spinning a number of times before blocking
|
||||||
|
// "0": thread will block if found no job to run
|
||||||
|
// "1": thread will spin a number of times before blocking
|
||||||
|
// The default is "0" when ORT is built with "ORT_CLIENT_PACKAGE_BUILD" and "1" otherwise.
|
||||||
|
// Thread spinning is disabled by default for client/on-device workloads to reduce cpu utilization and improve power efficiency.
|
||||||
|
static const char* const kOrtSessionOptionsConfigAllowInterOpSpinning = "session.inter_op.allow_spinning";
|
||||||
|
static const char* const kOrtSessionOptionsConfigAllowIntraOpSpinning = "session.intra_op.allow_spinning";
|
||||||
|
|
||||||
|
// Key for using model bytes directly for ORT format
|
||||||
|
// If a session is created using an input byte array contains the ORT format model data,
|
||||||
|
// By default we will copy the model bytes at the time of session creation to ensure the model bytes
|
||||||
|
// buffer is valid.
|
||||||
|
// Setting this option to "1" will disable copy the model bytes, and use the model bytes directly. The caller
|
||||||
|
// has to guarantee that the model bytes are valid until the ORT session using the model bytes is destroyed.
|
||||||
|
static const char* const kOrtSessionOptionsConfigUseORTModelBytesDirectly = "session.use_ort_model_bytes_directly";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Key for using the ORT format model flatbuffer bytes directly for initializers.
|
||||||
|
/// This avoids copying the bytes and reduces peak memory usage during model loading and initialization.
|
||||||
|
/// Requires `session.use_ort_model_bytes_directly` to be true.
|
||||||
|
/// If set, the flatbuffer bytes provided when creating the InferenceSession MUST remain valid for the entire
|
||||||
|
/// duration of the InferenceSession.
|
||||||
|
/// </summary>
|
||||||
|
static const char* const kOrtSessionOptionsConfigUseORTModelBytesForInitializers =
|
||||||
|
"session.use_ort_model_bytes_for_initializers";
|
||||||
|
|
||||||
|
// This should only be specified when exporting an ORT format model for use on a different platform.
|
||||||
|
// If the ORT format model will be used on ARM platforms set to "1". For other platforms set to "0"
|
||||||
|
// Available since version 1.11.
|
||||||
|
static const char* const kOrtSessionOptionsQDQIsInt8Allowed = "session.qdqisint8allowed";
|
||||||
|
|
||||||
|
// x64 SSE4.1/AVX2/AVX512(with no VNNI) has overflow problem with quantizied matrix multiplication with U8S8.
|
||||||
|
// To avoid this we need to use slower U8U8 matrix multiplication instead. This option, if
|
||||||
|
// turned on, use slower U8U8 matrix multiplications. Only effective with AVX2 or AVX512
|
||||||
|
// platforms.
|
||||||
|
static const char* const kOrtSessionOptionsAvx2PrecisionMode = "session.x64quantprecision";
|
||||||
|
|
||||||
|
// Specifies how minimal build graph optimizations are handled in a full build.
|
||||||
|
// These optimizations are at the extended level or higher.
|
||||||
|
// Possible values and their effects are:
|
||||||
|
// "save": Save runtime optimizations when saving an ORT format model.
|
||||||
|
// "apply": Only apply optimizations available in a minimal build.
|
||||||
|
// ""/<unspecified>: Apply optimizations available in a full build.
|
||||||
|
// Available since version 1.11.
|
||||||
|
static const char* const kOrtSessionOptionsConfigMinimalBuildOptimizations =
|
||||||
|
"optimization.minimal_build_optimizations";
|
||||||
|
|
||||||
|
// Note: The options specific to an EP should be specified prior to appending that EP to the session options object in
|
||||||
|
// order for them to take effect.
|
||||||
|
|
||||||
|
// Specifies a list of stop op types. Nodes of a type in the stop op types and nodes downstream from them will not be
|
||||||
|
// run by the NNAPI EP.
|
||||||
|
// The value should be a ","-delimited list of op types. For example, "Add,Sub".
|
||||||
|
// If not specified, the default set of stop ops is used. To specify an empty stop ops types list and disable stop op
|
||||||
|
// exclusion, set the value to "".
|
||||||
|
static const char* const kOrtSessionOptionsConfigNnapiEpPartitioningStopOps = "ep.nnapi.partitioning_stop_ops";
|
||||||
|
|
||||||
|
// Enabling dynamic block-sizing for multithreading.
|
||||||
|
// With a positive value, thread pool will split a task of N iterations to blocks of size starting from:
|
||||||
|
// N / (num_of_threads * dynamic_block_base)
|
||||||
|
// As execution progresses, the size will decrease according to the diminishing residual of N,
|
||||||
|
// meaning the task will be distributed in smaller granularity for better parallelism.
|
||||||
|
// For some models, it helps to reduce the variance of E2E inference latency and boost performance.
|
||||||
|
// The feature will not function by default, specify any positive integer, e.g. "4", to enable it.
|
||||||
|
// Available since version 1.11.
|
||||||
|
static const char* const kOrtSessionOptionsConfigDynamicBlockBase = "session.dynamic_block_base";
|
||||||
|
|
||||||
|
// This option allows to decrease CPU usage between infrequent
|
||||||
|
// requests and forces any TP threads spinning stop immediately when the last of
|
||||||
|
// concurrent Run() call returns.
|
||||||
|
// Spinning is restarted on the next Run() call.
|
||||||
|
// Applies only to internal thread-pools
|
||||||
|
static const char* const kOrtSessionOptionsConfigForceSpinningStop = "session.force_spinning_stop";
|
||||||
|
|
||||||
|
// "1": all inconsistencies encountered during shape and type inference
|
||||||
|
// will result in failures.
|
||||||
|
// "0": in some cases warnings will be logged but processing will continue. The default.
|
||||||
|
// May be useful to expose bugs in models.
|
||||||
|
static const char* const kOrtSessionOptionsConfigStrictShapeTypeInference = "session.strict_shape_type_inference";
|
||||||
|
|
||||||
|
// "1": every model using a more recent opset than the latest released one will fail
|
||||||
|
// "0": the model may or may not work if onnxruntime cannot find an implementation, this option
|
||||||
|
// is used for development purpose.
|
||||||
|
static const char* const kOrtSessionOptionsConfigStrictAllowReleasedOpsetsOnly = "session.allow_released_opsets_only";
|
||||||
|
|
||||||
|
// The file saves configuration for partitioning node among logic streams
|
||||||
|
static const char* const kNodePartitionConfigFile = "session.node_partition_config_file";
|
||||||
|
|
||||||
|
// This Option allows setting affinities for intra op threads.
|
||||||
|
// Affinity string follows format:
|
||||||
|
// logical_processor_id,logical_processor_id;logical_processor_id,logical_processor_id
|
||||||
|
// Semicolon isolates configurations among threads, while comma split processors where ith thread expected to attach to.
|
||||||
|
// e.g.1,2,3;4,5
|
||||||
|
// specifies affinities for two threads, with the 1st thread attach to the 1st, 2nd, and 3rd processor, and 2nd thread to the 4th and 5th.
|
||||||
|
// To ease the configuration, an "interval" is also allowed:
|
||||||
|
// e.g. 1-8;8-16;17-24
|
||||||
|
// orders that the 1st thread runs on first eight processors, 2nd thread runs on next eight processors, and so forth.
|
||||||
|
// Note:
|
||||||
|
// 1. Once set, the number of thread affinities must equal to intra_op_num_threads - 1, since ort does not set affinity on the main thread which
|
||||||
|
// is started and managed by the calling app;
|
||||||
|
// 2. For windows, ort will infer the group id from a logical processor id, for example, assuming there are two groups with each has 64 logical processors,
|
||||||
|
// an id of 64 will be inferred as the last processor of the 1st group, while 65 will be interpreted as the 1st processor of the second group.
|
||||||
|
// Hence 64-65 is an invalid configuration, because a windows thread cannot be attached to processors across group boundary.
|
||||||
|
static const char* const kOrtSessionOptionsConfigIntraOpThreadAffinities = "session.intra_op_thread_affinities";
|
||||||
|
|
||||||
|
// This option will dump out the model to assist debugging any issues with layout transformation,
|
||||||
|
// and is primarily intended for developer usage. It is only relevant if an execution provider that requests
|
||||||
|
// NHWC layout is enabled such as NNAPI, XNNPACK or QNN.
|
||||||
|
//
|
||||||
|
// Default is off. Set to "1" to enable.
|
||||||
|
//
|
||||||
|
// If modified by layout transformation the model will be dumped after these steps:
|
||||||
|
// 1) insertion of the layout transformation Transpose nodes
|
||||||
|
// 2) after those are optimized using the transpose optimizer,
|
||||||
|
// 3) after the L1 transformers are applied to the updated graph.
|
||||||
|
// The model will be saved to filename post_layout_transform_step_<step_number>.onnx.
|
||||||
|
static const char* const kDebugLayoutTransformation = "session.debug_layout_transformation";
|
||||||
|
|
||||||
|
// Graph nodes that are not supported by the execution providers (EPs) explicitly added to the session are
|
||||||
|
// assigned (i.e., "fallback") to the CPU EP by default.
|
||||||
|
//
|
||||||
|
// This option allows the user to disable the fallback of unsupported graph nodes to the CPU EP.
|
||||||
|
// If this option is set to "1", session creation will fail if the execution providers other than the CPU EP cannot
|
||||||
|
// fully support all of the nodes in the graph.
|
||||||
|
//
|
||||||
|
// It is invalid to set this option and explicitly add the CPU EP to the session. In this case, session creation
|
||||||
|
// will also fail with an error.
|
||||||
|
//
|
||||||
|
// Option values:
|
||||||
|
// - "0": CPU EP fallback is not disabled. [DEFAULT]
|
||||||
|
// - "1": CPU EP fallback is disabled.
|
||||||
|
static const char* const kOrtSessionOptionsDisableCPUEPFallback = "session.disable_cpu_ep_fallback";
|
||||||
|
|
||||||
|
// Use this config when serializing a large model after optimization to specify an external initializers file
|
||||||
|
static const char* const kOrtSessionOptionsOptimizedModelExternalInitializersFileName =
|
||||||
|
"session.optimized_model_external_initializers_file_name";
|
||||||
|
|
||||||
|
// Use this config to control the minimum size of the initializer when externalizing it during serialization
|
||||||
|
static const char* const kOrtSessionOptionsOptimizedModelExternalInitializersMinSizeInBytes =
|
||||||
|
"session.optimized_model_external_initializers_min_size_in_bytes";
|
||||||
|
|
||||||
|
// When loading model from memory buffer and the model has external initializers
|
||||||
|
// Use this config to set the external data file folder path
|
||||||
|
// All external data files should be in the same folder
|
||||||
|
static const char* const kOrtSessionOptionsModelExternalInitializersFileFolderPath =
|
||||||
|
"session.model_external_initializers_file_folder_path";
|
||||||
|
|
||||||
|
// Use this config when saving pre-packed constant initializers to an external data file.
|
||||||
|
// This allows you to memory map pre-packed initializers on model load and leave it to
|
||||||
|
// to the OS the amount of memory consumed by the pre-packed initializers. Otherwise,
|
||||||
|
// pre-packed data resides on the heap.
|
||||||
|
//
|
||||||
|
// - "0": Default is not save pre-packed initializers to a data file.
|
||||||
|
// - "1": Save pre-packed constant initializers to an external data file.
|
||||||
|
// Sample usage: sess_options.add_session_config_entry(kOrtSessionOptionsSavePrePackedConstantInitializers, "1")
|
||||||
|
static const char* const kOrtSessionOptionsSavePrePackedConstantInitializers =
|
||||||
|
"session.save_external_prepacked_constant_initializers";
|
||||||
|
|
||||||
|
// Use this config when you want to collect memory stats for each node in the graph.
|
||||||
|
// The file format is a CSV file with the following columns:
|
||||||
|
// The file will be created if it does not exist, and will be overwritten if it does.
|
||||||
|
//
|
||||||
|
// The content of the file can be used to estimate memory requirements at run time including
|
||||||
|
// the temporary allocations. This operation is preferably done on a CPU device, as the model may exceed
|
||||||
|
// device memory limits in constrained environments. When enabling this option, it is important to disable
|
||||||
|
// memory patterns, as they tend to allocate large blocks to avoid fragmentation and accommodate needs of multiple
|
||||||
|
// kernels. Memory patterns may make it difficult to allocate on a device with limited memory.
|
||||||
|
//
|
||||||
|
// The collected stats then can be used to partition the graph among the devices in a way that only the
|
||||||
|
// required memory is allocated on each device.
|
||||||
|
//
|
||||||
|
// node_name, initializers_memory, dynamic_outputs_sizes, temp_allocations_size
|
||||||
|
//
|
||||||
|
// - "full path to file": there is not a default for this option. If the file can not be opened for writing, an error will be returned.
|
||||||
|
static const char* const kOrtSessionOptionsCollectNodeMemoryStatsToFile = "session.collect_node_memory_stats_to_file";
|
||||||
|
|
||||||
|
/// This is a composite CSV setting formatted as "memory limit in kb,file name for collected stats"
|
||||||
|
/// "limit > 0": enables Capacity Aware Partitioning for Cuda EP. `limit` is optional and when absent
|
||||||
|
/// the provider may attempt to figure out the memory available automatically.
|
||||||
|
/// The setting with no limit is expected to look like: ",file name for collected stats"
|
||||||
|
/// The EP will place nodes on device "file name" :
|
||||||
|
/// this file is expected to be found at the same folder with the model. The file contains
|
||||||
|
/// pre-recorded stats collected when running with kOrtSessionOptionsCollectNodeMemoryStatsToFile enforce (see above)
|
||||||
|
static const char* const kOrtSessionOptionsResourceCudaPartitioningSettings =
|
||||||
|
"session.resource_cuda_partitioning_settings";
|
||||||
|
|
||||||
|
// Enable EP context feature to dump the partitioned graph which includes the EP context into Onnx file.
|
||||||
|
// The dumped Onnx model with EP context can be used for future inference to avoid the EP graph partitioning/compile overhead.
|
||||||
|
// "0": disable. (default)
|
||||||
|
// "1": enable.
|
||||||
|
static const char* const kOrtSessionOptionEpContextEnable = "ep.context_enable";
|
||||||
|
|
||||||
|
// Specify the file path for the Onnx model which has EP context.
|
||||||
|
// Default to original_file_name_ctx.onnx if not specified
|
||||||
|
// Folder is not a valid option
|
||||||
|
static const char* const kOrtSessionOptionEpContextFilePath = "ep.context_file_path";
|
||||||
|
|
||||||
|
// Flag to specify whether to dump the EP context into the Onnx model.
|
||||||
|
// "0": dump the EP context into separate file, keep the file name in the Onnx model. (default).
|
||||||
|
// "1": dump the EP context into the Onnx model.
|
||||||
|
static const char* const kOrtSessionOptionEpContextEmbedMode = "ep.context_embed_mode";
|
||||||
|
|
||||||
|
// Specify the EPContext node name prefix to make it unique
|
||||||
|
// in case user need to merge/connect multiple EPContext nodes in one model
|
||||||
|
static const char* const kOrtSessionOptionEpContextNodeNamePrefix = "ep.context_node_name_prefix";
|
||||||
|
|
||||||
|
// Share EP related resources across sessions
|
||||||
|
static const char* const kOrtSessionOptionShareEpContexts = "ep.share_ep_contexts";
|
||||||
|
|
||||||
|
// Stop to share EP related resources across sessions from then on
|
||||||
|
static const char* const kOrtSessionOptionStopShareEpContexts = "ep.stop_share_ep_contexts";
|
||||||
|
|
||||||
|
// Used only for context model generation.
|
||||||
|
// This configuration is used when some nodes are partitioned on the CPU EP and those nodes have external initializers.
|
||||||
|
// When generating the EP context model, the new model should not rely on the old external data file used by the source ONNX model.
|
||||||
|
// Use this setting when dumping the EP context model with an external initializers file.
|
||||||
|
// If specified, all initializers will be placed inside the external data file.
|
||||||
|
// Otherwise, all initializers will be embedded inside the generated ONNX file.
|
||||||
|
// By default, this option is not set, meaning all initializers will be included within the ONNX file.
|
||||||
|
static const char* const kOrtSessionOptionsEpContextModelExternalInitializersFileName =
|
||||||
|
"ep.context_model_external_initializers_file_name";
|
||||||
|
|
||||||
|
// Gemm fastmath mode provides fp32 gemm acceleration with bfloat16 based matmul.
|
||||||
|
// Option values:
|
||||||
|
// - "0": Gemm FastMath mode is not enabled. [DEFAULT]
|
||||||
|
// - "1": Gemm FastMath mode is enabled.
|
||||||
|
static const char* const kOrtSessionOptionsMlasGemmFastMathArm64Bfloat16 = "mlas.enable_gemm_fastmath_arm64_bfloat16";
|
||||||
|
|
||||||
|
// Use LUT (Lookup Table) based GEMM for quantized models when available.
|
||||||
|
// Option values:
|
||||||
|
// - "0": Do not use LUT based GEMM. [DEFAULT]
|
||||||
|
// - "1": Use LUT based GEMM when available.
|
||||||
|
static const char* const kOrtSessionOptionsMlasLutGemm = "mlas.use_lut_gemm";
|
||||||
|
|
||||||
|
// When converting DQ + MatMul -> MatMulNBits, the accuracy level of the MatMulNBits is controlled by this option.
|
||||||
|
// Refer to MatMulNBits op schema for more details.
|
||||||
|
// If not provided, default is 4.
|
||||||
|
static const char* const kOrtSessionOptionsQDQMatMulNBitsAccuracyLevel = "session.qdq_matmulnbits_accuracy_level";
|
||||||
|
|
||||||
|
// Enable the DQ->MatMulNBits fusion graph transformer.
|
||||||
|
// "0": disabled (default). "1": enabled.
|
||||||
|
// This is typically set automatically by InferenceSession when the NvTensorRTRTX EP is registered.
|
||||||
|
static const char* const kOrtSessionOptionsEnableDQMatMulNBitsFusion = "session.enable_dq_matmulnbits_fusion";
|
||||||
|
|
||||||
|
// THIS OPTION IS NOT A REGULAR SESSION OPTION SINCE IT CAN BE MODIFIED AT ANY TIME
|
||||||
|
// Meant to be used with SetEpDynamicOptions
|
||||||
|
// Specify the type of workload for this session.
|
||||||
|
// "Default": OS determines the scheduling priority and processor performance to service this workload. [Default]
|
||||||
|
// "Efficient": OS treats this workload is efficiency oriented with low scheduling priority and efficient processor performance.
|
||||||
|
static const char* const kOrtEpDynamicOptionsWorkloadType = "ep.dynamic.workload_type";
|
||||||
|
|
||||||
|
// Disables model compilation during session initialization.
|
||||||
|
//
|
||||||
|
// If this option is set to "1", inference session creation will fail with error code ORT_MODEL_REQUIRES_COMPILATION
|
||||||
|
// if compilation is required to run the model on any Execution Provider added to the session.
|
||||||
|
// Only the following kinds of models are valid when this option is set to "1":
|
||||||
|
// - Pre-compiled models that have EPContext nodes for the compiling Execution Providers in the session.
|
||||||
|
// - Non-compiled models that run only on non-compiling Execution Providers, like CPU EP.
|
||||||
|
//
|
||||||
|
// See \href https://onnxruntime.ai/docs/execution-providers/EP-Context-Design.html for details about
|
||||||
|
// compiled models with EPContext nodes.
|
||||||
|
//
|
||||||
|
// Option values:
|
||||||
|
// - "0": EP compile is not disabled. [DEFAULT]
|
||||||
|
// - "1": EP compile is disabled.
|
||||||
|
static const char* const kOrtSessionOptionsDisableModelCompile = "session.disable_model_compile";
|
||||||
|
|
||||||
|
// Controls behavior when compiled model compatibility is SUPPORTED_PREFER_RECOMPILATION.
|
||||||
|
// "0": Allow execution with suboptimal performance. [DEFAULT]
|
||||||
|
// "1": Fail session creation to require recompilation for optimal performance.
|
||||||
|
// Note: UNSUPPORTED models always fail regardless of this setting.
|
||||||
|
static const char* const kOrtSessionOptionsFailOnSuboptimalCompiledModel =
|
||||||
|
"session.fail_on_suboptimal_compiled_model";
|
||||||
|
|
||||||
|
// THIS OPTION IS NOT A REGULAR SESSION OPTION SINCE IT CAN BE MODIFIED AT ANY TIME
|
||||||
|
// Meant to be used with SetEpDynamicOptions
|
||||||
|
// options for HTP performance mode: "burst", "balanced", "default", "high_performance",
|
||||||
|
// "high_power_saver", "low_balanced", "extreme_power_saver", "low_power_saver", "power_saver",
|
||||||
|
// "sustained_high_performance". Default to "default".
|
||||||
|
static const char* const kOrtEpDynamicOptionsQnnHtpPerformanceMode = "ep.dynamic.qnn_htp_performance_mode";
|
||||||
|
|
||||||
|
// Enables the session to record information about the subgraphs/nodes assigned to execution providers.
|
||||||
|
// When enabled, an application may call Session_GetEpGraphAssignmentInfo() to retrieve the information.
|
||||||
|
//
|
||||||
|
// Option values:
|
||||||
|
// - "0": Recording of EP graph assignment information is disabled. [DEFAULT]
|
||||||
|
// - "1": Recording of EP graph assignment information is enabled.
|
||||||
|
static const char* const kOrtSessionOptionsRecordEpGraphAssignmentInfo = "session.record_ep_graph_assignment_info";
|
||||||
|
|
@ -0,0 +1,236 @@
|
||||||
|
// Implementation kazeia_mel — voir kazeia_mel.h pour l'API publique.
|
||||||
|
#include "kazeia_mel.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
namespace kazeia_mel {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Configurations pré-faites
|
||||||
|
// ============================================================================
|
||||||
|
MelConfig config_qwen3_tts_speaker() {
|
||||||
|
MelConfig c;
|
||||||
|
c.sample_rate = 24000;
|
||||||
|
c.n_fft = 1024;
|
||||||
|
c.hop_size = 256;
|
||||||
|
c.win_size = 1024;
|
||||||
|
c.n_mels = 128;
|
||||||
|
c.pad_amount = (1024 - 256) / 2; // 384, reflect (TTS convention)
|
||||||
|
c.fixed_n_frames = 0;
|
||||||
|
c.clip_eps = 1e-5f;
|
||||||
|
c.window = WindowKind::HANN_SYMMETRIC;
|
||||||
|
c.comp = Compression::LOG_NATURAL;
|
||||||
|
c.post = PostNorm::NONE;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
MelConfig config_whisper() {
|
||||||
|
MelConfig c;
|
||||||
|
c.sample_rate = 16000;
|
||||||
|
c.n_fft = 400;
|
||||||
|
c.hop_size = 160;
|
||||||
|
c.win_size = 400;
|
||||||
|
c.n_mels = 80;
|
||||||
|
c.pad_amount = 200; // n_fft/2 (HF whisper)
|
||||||
|
c.fixed_n_frames = 3000; // 30s padding fixe
|
||||||
|
c.clip_eps = 1e-10f;
|
||||||
|
c.window = WindowKind::HANN_PERIODIC;
|
||||||
|
c.comp = Compression::LOG10;
|
||||||
|
c.post = PostNorm::WHISPER;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Helpers internes
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// FFT radix-2 in-place (n doit être puissance de 2). Si n_fft pas puissance de 2
|
||||||
|
// (cas Whisper N_FFT=400), on zero-pad jusqu'à la puissance de 2 supérieure.
|
||||||
|
static void fft_radix2(float* real, float* imag, int n) {
|
||||||
|
int j = 0;
|
||||||
|
for (int i = 1; i < n; i++) {
|
||||||
|
int bit = n >> 1;
|
||||||
|
while (j & bit) { j ^= bit; bit >>= 1; }
|
||||||
|
j ^= bit;
|
||||||
|
if (i < j) { std::swap(real[i], real[j]); std::swap(imag[i], imag[j]); }
|
||||||
|
}
|
||||||
|
for (int len = 2; len <= n; len <<= 1) {
|
||||||
|
int half = len / 2;
|
||||||
|
double angle = -2.0 * M_PI / len;
|
||||||
|
float wR = (float)std::cos(angle), wI = (float)std::sin(angle);
|
||||||
|
for (int i = 0; i < n; i += len) {
|
||||||
|
float cR = 1.f, cI = 0.f;
|
||||||
|
for (int k = 0; k < half; k++) {
|
||||||
|
float tR = cR * real[i+k+half] - cI * imag[i+k+half];
|
||||||
|
float tI = cR * imag[i+k+half] + cI * real[i+k+half];
|
||||||
|
real[i+k+half] = real[i+k] - tR;
|
||||||
|
imag[i+k+half] = imag[i+k] - tI;
|
||||||
|
real[i+k] += tR;
|
||||||
|
imag[i+k] += tI;
|
||||||
|
float ncR = cR * wR - cI * wI;
|
||||||
|
cI = cR * wI + cI * wR;
|
||||||
|
cR = ncR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int next_pow2(int n) {
|
||||||
|
int p = 1; while (p < n) p <<= 1; return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<float> make_window(int n, WindowKind kind) {
|
||||||
|
std::vector<float> w(n);
|
||||||
|
if (kind == WindowKind::HANN_SYMMETRIC) {
|
||||||
|
// torch.hann_window(N) default = symmetric : 0.5 * (1 - cos(2*pi*n/(N-1)))
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
w[i] = 0.5f * (1.0f - std::cos(2.0f * (float)M_PI * (float)i / (float)(n - 1)));
|
||||||
|
} else {
|
||||||
|
// Periodic (HF whisper, numpy default) : 0.5 * (1 - cos(2*pi*n/N))
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
w[i] = 0.5f * (1.0f - std::cos(2.0f * (float)M_PI * (float)i / (float)n));
|
||||||
|
}
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pad reflect : miroir interne (n'inclut PAS le sample bord). Équivalent torch reflect.
|
||||||
|
static std::vector<float> pad_reflect(const std::vector<float>& x, int padding) {
|
||||||
|
const int n = (int)x.size();
|
||||||
|
std::vector<float> out((size_t)n + 2 * padding);
|
||||||
|
for (int i = 0; i < padding; i++) out[i] = x[padding - i];
|
||||||
|
for (int i = 0; i < n; i++) out[padding + i] = x[i];
|
||||||
|
for (int i = 0; i < padding; i++) out[padding + n + i] = x[n - 2 - i];
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// load_mel_basis : fichier binaire f32 [n_mels, n_fft/2+1]
|
||||||
|
// ============================================================================
|
||||||
|
bool load_mel_basis(const char * path, const MelConfig & cfg, std::vector<float> & out) {
|
||||||
|
const int fft_bins = cfg.n_fft / 2 + 1;
|
||||||
|
const size_t expected = (size_t)cfg.n_mels * fft_bins;
|
||||||
|
std::ifstream f(path, std::ios::binary | std::ios::ate);
|
||||||
|
if (!f) { fprintf(stderr, "kazeia_mel::load_mel_basis: open %s FAIL\n", path); return false; }
|
||||||
|
size_t n = (size_t)f.tellg() / sizeof(float);
|
||||||
|
if (n != expected) {
|
||||||
|
fprintf(stderr, "kazeia_mel::load_mel_basis: %s contient %zu f32, attendu %zu (=%d mels x %d bins)\n",
|
||||||
|
path, n, expected, cfg.n_mels, fft_bins);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
f.seekg(0);
|
||||||
|
out.assign(expected, 0.0f);
|
||||||
|
f.read((char*)out.data(), expected * sizeof(float));
|
||||||
|
return (bool)f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// compute : STFT (radix-2 zero-pad) + projection mel + log + post-norm
|
||||||
|
// ============================================================================
|
||||||
|
std::vector<float> compute(const std::vector<float> & wav_in,
|
||||||
|
const std::vector<float> & mel_basis,
|
||||||
|
const MelConfig & cfg,
|
||||||
|
int & out_T) {
|
||||||
|
const int fft_bins = cfg.n_fft / 2 + 1;
|
||||||
|
const int pad_amount = (cfg.pad_amount >= 0) ? cfg.pad_amount : (cfg.n_fft - cfg.hop_size) / 2;
|
||||||
|
|
||||||
|
// Optionnel : padding audio à T_fixe (Whisper 30s)
|
||||||
|
std::vector<float> wav = wav_in;
|
||||||
|
if (cfg.fixed_n_frames > 0) {
|
||||||
|
int target_samples = cfg.fixed_n_frames * cfg.hop_size;
|
||||||
|
if ((int)wav.size() > target_samples) wav.resize(target_samples);
|
||||||
|
else if ((int)wav.size() < target_samples) wav.resize(target_samples, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pad = pad_reflect(wav, pad_amount);
|
||||||
|
auto hann = make_window(cfg.win_size, cfg.window);
|
||||||
|
const int len_pad = (int)pad.size();
|
||||||
|
if (len_pad < cfg.n_fft) {
|
||||||
|
fprintf(stderr, "kazeia_mel::compute: wav trop court (%d < %d)\n", len_pad, cfg.n_fft);
|
||||||
|
out_T = 0; return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// T_compute : sans fixed_n_frames -> calcule autant que possible (center=False).
|
||||||
|
// Avec fixed_n_frames -> exactement N_FRAMES (le padding ci-dessus garantit la place).
|
||||||
|
int T;
|
||||||
|
if (cfg.fixed_n_frames > 0) T = cfg.fixed_n_frames;
|
||||||
|
else T = (len_pad - cfg.n_fft) / cfg.hop_size + 1;
|
||||||
|
out_T = T;
|
||||||
|
|
||||||
|
// FFT en puissance de 2 >= n_fft (Whisper N_FFT=400 -> FFT 512).
|
||||||
|
const int fftN = next_pow2(cfg.n_fft);
|
||||||
|
|
||||||
|
std::vector<float> spec((size_t)fft_bins * T, 0.0f);
|
||||||
|
std::vector<float> re(fftN), im(fftN);
|
||||||
|
|
||||||
|
for (int t = 0; t < T; t++) {
|
||||||
|
const int off = t * cfg.hop_size;
|
||||||
|
// Window sur win_size samples, zero-pad jusqu'à fftN
|
||||||
|
for (int i = 0; i < cfg.win_size; i++) {
|
||||||
|
re[i] = pad[off + i] * hann[i];
|
||||||
|
im[i] = 0.0f;
|
||||||
|
}
|
||||||
|
for (int i = cfg.win_size; i < fftN; i++) { re[i] = 0.0f; im[i] = 0.0f; }
|
||||||
|
|
||||||
|
fft_radix2(re.data(), im.data(), fftN);
|
||||||
|
|
||||||
|
// Power spectrum [0..n_fft/2]
|
||||||
|
for (int k = 0; k < fft_bins; k++) {
|
||||||
|
spec[(size_t)k * T + t] = re[k]*re[k] + im[k]*im[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mel = basis [n_mels, fft_bins] @ spec [fft_bins, T] -> [n_mels, T]
|
||||||
|
std::vector<float> mel((size_t)cfg.n_mels * T, 0.0f);
|
||||||
|
for (int m = 0; m < cfg.n_mels; m++) {
|
||||||
|
for (int t = 0; t < T; t++) {
|
||||||
|
float s = 0.0f;
|
||||||
|
for (int k = 0; k < fft_bins; k++) {
|
||||||
|
s += mel_basis[m * fft_bins + k] * spec[(size_t)k * T + t];
|
||||||
|
}
|
||||||
|
mel[(size_t)m * T + t] = std::max(s, cfg.clip_eps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compression (log naturel ou log10) + sqrt si TTS (variante: TTS prend sqrt magnitude
|
||||||
|
// AVANT projection mel ; Whisper prend power direct projetée puis log10).
|
||||||
|
// Distinction empirique du speaker encoder Qwen3-TTS : il calcule magnitude = sqrt(re²+im²+1e-9)
|
||||||
|
// puis projette puis log. Pour rester strictement bit-correct avec le code spk d'origine,
|
||||||
|
// on adapte si compression == LOG_NATURAL : sqrt avant log.
|
||||||
|
if (cfg.comp == Compression::LOG_NATURAL) {
|
||||||
|
// TTS : sqrt(power+1e-9) puis mel_proj puis log(max(., eps))
|
||||||
|
// Refait correctement : revert la projection power, refais en magnitude.
|
||||||
|
// Implémenter ainsi pour bit-match :
|
||||||
|
std::vector<float> mag_spec((size_t)fft_bins * T);
|
||||||
|
for (int k = 0; k < fft_bins; k++)
|
||||||
|
for (int t = 0; t < T; t++)
|
||||||
|
mag_spec[(size_t)k * T + t] = std::sqrt(spec[(size_t)k * T + t] + 1e-9f);
|
||||||
|
for (int m = 0; m < cfg.n_mels; m++) {
|
||||||
|
for (int t = 0; t < T; t++) {
|
||||||
|
float s = 0.0f;
|
||||||
|
for (int k = 0; k < fft_bins; k++)
|
||||||
|
s += mel_basis[m * fft_bins + k] * mag_spec[(size_t)k * T + t];
|
||||||
|
mel[(size_t)m * T + t] = std::log(std::max(s, cfg.clip_eps));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // LOG10 (Whisper)
|
||||||
|
for (auto & v : mel) v = std::log10(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post-norm (Whisper : clamp à max-8, puis (x+4)/4)
|
||||||
|
if (cfg.post == PostNorm::WHISPER) {
|
||||||
|
float maxv = *std::max_element(mel.begin(), mel.end());
|
||||||
|
float floor_v = maxv - 8.0f;
|
||||||
|
for (auto & v : mel) {
|
||||||
|
if (v < floor_v) v = floor_v;
|
||||||
|
v = (v + 4.0f) * 0.25f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mel;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace kazeia_mel
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
// Mel spectrogram extractor unifié Kazeia-Engine.
|
||||||
|
// Couvre Whisper (16kHz, 80 mel, log10 + Whisper norm) et Qwen3-TTS speaker encoder
|
||||||
|
// (24kHz, 128 mel, log naturel + clip 1e-5).
|
||||||
|
//
|
||||||
|
// Bit-correct vs librosa/torch reference pour les deux profils.
|
||||||
|
//
|
||||||
|
// Layout sortie : [N_MELS, T] = data[mel * T + frame] (PyTorch [B, C, T] / ggml
|
||||||
|
// ne[0]=T fastest). Compatible directement avec speaker_encoder_encode_waveform()
|
||||||
|
// et Whisper encoder ONNX (qui veut [1, n_mels, n_frames] fp16).
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace kazeia_mel {
|
||||||
|
|
||||||
|
enum class WindowKind {
|
||||||
|
HANN_SYMMETRIC, // torch.hann_window(N) = 0.5*(1-cos(2*pi*n/(N-1))) (TTS)
|
||||||
|
HANN_PERIODIC, // numpy/HF whisper = 0.5*(1-cos(2*pi*n/N)) (STT)
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Compression {
|
||||||
|
LOG_NATURAL, // log(max(mel, eps)) (TTS Qwen3-TTS)
|
||||||
|
LOG10, // log10(max(mel, eps)) (STT Whisper)
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class PostNorm {
|
||||||
|
NONE, // pas de post-process (TTS)
|
||||||
|
WHISPER, // clamp à max-8, puis (x+4)/4 (STT Whisper)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MelConfig {
|
||||||
|
int sample_rate = 16000;
|
||||||
|
int n_fft = 400;
|
||||||
|
int hop_size = 160;
|
||||||
|
int win_size = 400;
|
||||||
|
int n_mels = 80;
|
||||||
|
int pad_amount = -1; // -1 -> auto : whisper n_fft/2, tts (n_fft-hop)/2
|
||||||
|
int fixed_n_frames = 0; // 0 -> variable ; >0 -> pad audio à T fixe (whisper=3000=30s)
|
||||||
|
float clip_eps = 1e-5f; // valeur min avant log
|
||||||
|
WindowKind window = WindowKind::HANN_SYMMETRIC;
|
||||||
|
Compression comp = Compression::LOG_NATURAL;
|
||||||
|
PostNorm post = PostNorm::NONE;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Configs pré-faites — utiliser comme défauts puis customiser au besoin.
|
||||||
|
MelConfig config_qwen3_tts_speaker(); // SR=24000, N_FFT=1024, hop=256, n_mels=128, log naturel
|
||||||
|
MelConfig config_whisper(); // SR=16000, N_FFT=400, hop=160, n_mels=80, log10 + WhisperNorm
|
||||||
|
|
||||||
|
// Charge mel_basis [n_mels, n_fft/2+1] depuis un fichier binaire f32 (librosa pré-calculé
|
||||||
|
// ou mel_filters.json converti). Le caller fournit le path et la lib vérifie la taille
|
||||||
|
// attendue n_mels * (n_fft/2+1).
|
||||||
|
bool load_mel_basis(const char * path, const MelConfig & cfg, std::vector<float> & out);
|
||||||
|
|
||||||
|
// Compute mel spectrogram. wav = mono f32 [-1..1] @ cfg.sample_rate. mel_basis attendu
|
||||||
|
// dans le layout librosa [n_mels, n_fft/2+1]. Retourne mel [n_mels, T] dans le layout
|
||||||
|
// data[mel * T + frame]. out_T renvoie le nombre de frames calculés.
|
||||||
|
std::vector<float> compute(const std::vector<float> & wav,
|
||||||
|
const std::vector<float> & mel_basis,
|
||||||
|
const MelConfig & cfg,
|
||||||
|
int & out_T);
|
||||||
|
|
||||||
|
} // namespace kazeia_mel
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
// Compile avec -DSPK_STANDALONE pour le binaire CLI :
|
// Compile avec -DSPK_STANDALONE pour le binaire CLI :
|
||||||
// usage : kazeia_speaker_encode <gguf> <mel_basis.bin> <ref.wav> <out.bin>
|
// usage : kazeia_speaker_encode <gguf> <mel_basis.bin> <ref.wav> <out.bin>
|
||||||
#include "speaker_encoder.h"
|
#include "speaker_encoder.h"
|
||||||
|
#include "kazeia_mel.h"
|
||||||
#include "ggml.h"
|
#include "ggml.h"
|
||||||
#include "gguf.h"
|
#include "gguf.h"
|
||||||
#include "ggml-cpu.h"
|
#include "ggml-cpu.h"
|
||||||
|
|
@ -29,111 +30,11 @@
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Constantes mel (cf /opt/Kazeia/qnn_venv/.../modeling_qwen3_tts.py mel_spectrogram)
|
// Constantes mel : maintenant centralisées dans kazeia_mel::config_qwen3_tts_speaker()
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
static const int SAMPLE_RATE = 24000;
|
static const int SAMPLE_RATE = 24000;
|
||||||
static const int N_FFT = 1024;
|
|
||||||
static const int HOP_SIZE = 256;
|
|
||||||
static const int WIN_SIZE = 1024;
|
|
||||||
static const int N_MELS = 128;
|
static const int N_MELS = 128;
|
||||||
static const int FFT_BINS = N_FFT / 2 + 1; // 513
|
static const int FFT_BINS = 513; // n_fft/2+1 pour n_fft=1024
|
||||||
static const float MEL_CLIP_VAL = 1e-5f;
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// FFT radix-2 (in-place, n doit être puissance de 2). Identique à mel_extractor.cpp.
|
|
||||||
// ============================================================================
|
|
||||||
static void fft_radix2(float* real, float* imag, int n) {
|
|
||||||
// Bit-reversal
|
|
||||||
int j = 0;
|
|
||||||
for (int i = 1; i < n; i++) {
|
|
||||||
int bit = n >> 1;
|
|
||||||
while (j & bit) { j ^= bit; bit >>= 1; }
|
|
||||||
j ^= bit;
|
|
||||||
if (i < j) { std::swap(real[i], real[j]); std::swap(imag[i], imag[j]); }
|
|
||||||
}
|
|
||||||
for (int len = 2; len <= n; len <<= 1) {
|
|
||||||
int half = len / 2;
|
|
||||||
double angle = -2.0 * M_PI / len;
|
|
||||||
float wR = (float)std::cos(angle), wI = (float)std::sin(angle);
|
|
||||||
for (int i = 0; i < n; i += len) {
|
|
||||||
float cR = 1.f, cI = 0.f;
|
|
||||||
for (int k = 0; k < half; k++) {
|
|
||||||
float tR = cR * real[i+k+half] - cI * imag[i+k+half];
|
|
||||||
float tI = cR * imag[i+k+half] + cI * real[i+k+half];
|
|
||||||
real[i+k+half] = real[i+k] - tR;
|
|
||||||
imag[i+k+half] = imag[i+k] - tI;
|
|
||||||
real[i+k] += tR;
|
|
||||||
imag[i+k] += tI;
|
|
||||||
float ncR = cR * wR - cI * wI;
|
|
||||||
cI = cR * wI + cI * wR;
|
|
||||||
cR = ncR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hann window (cosine), torch.hann_window(N) = 0.5*(1 - cos(2π * n / (N-1)))
|
|
||||||
static std::vector<float> make_hann(int n) {
|
|
||||||
std::vector<float> w(n);
|
|
||||||
for (int i = 0; i < n; i++)
|
|
||||||
w[i] = 0.5f * (1.0f - std::cos(2.0f * (float)M_PI * (float)i / (float)(n - 1)));
|
|
||||||
return w;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pad reflect : insère padding samples sur chaque côté, miroir interne (sans répéter
|
|
||||||
// la 1ère/dernière). Équivalent torch.nn.functional.pad(..., mode="reflect").
|
|
||||||
static std::vector<float> pad_reflect(const std::vector<float>& x, int padding) {
|
|
||||||
const int n = (int)x.size();
|
|
||||||
std::vector<float> out((size_t)n + 2 * padding);
|
|
||||||
for (int i = 0; i < padding; i++) out[i] = x[padding - i]; // miroir gauche
|
|
||||||
for (int i = 0; i < n; i++) out[padding + i] = x[i]; // milieu
|
|
||||||
for (int i = 0; i < padding; i++) out[padding + n + i] = x[n - 2 - i]; // miroir droite
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute mel-spectrogram [n_mels=128, T] depuis waveform mono 24kHz.
|
|
||||||
// Réplique exactement qwen_tts.core.models.modeling_qwen3_tts.mel_spectrogram.
|
|
||||||
static std::vector<float> mel_spectrogram(const std::vector<float>& wav,
|
|
||||||
const std::vector<float>& mel_basis, // [128, 513]
|
|
||||||
int& out_T) {
|
|
||||||
const int padding = (N_FFT - HOP_SIZE) / 2; // 384
|
|
||||||
auto pad = pad_reflect(wav, padding);
|
|
||||||
auto hann = make_hann(WIN_SIZE);
|
|
||||||
|
|
||||||
// T = floor((len_padded - n_fft) / hop) + 1 (center=False)
|
|
||||||
const int len_pad = (int)pad.size();
|
|
||||||
if (len_pad < N_FFT) { fprintf(stderr, "wav too short (%d < %d)\n", len_pad, N_FFT); return {}; }
|
|
||||||
const int T = (len_pad - N_FFT) / HOP_SIZE + 1;
|
|
||||||
out_T = T;
|
|
||||||
|
|
||||||
std::vector<float> spec(FFT_BINS * T, 0.0f); // [513, T] magnitude
|
|
||||||
std::vector<float> re(N_FFT), im(N_FFT);
|
|
||||||
for (int t = 0; t < T; t++) {
|
|
||||||
const int off = t * HOP_SIZE;
|
|
||||||
for (int i = 0; i < N_FFT; i++) {
|
|
||||||
re[i] = pad[off + i] * hann[i];
|
|
||||||
im[i] = 0.0f;
|
|
||||||
}
|
|
||||||
fft_radix2(re.data(), im.data(), N_FFT);
|
|
||||||
// Magnitude sqrt(re² + im² + 1e-9), onesided -> bins 0..N_FFT/2
|
|
||||||
for (int k = 0; k < FFT_BINS; k++) {
|
|
||||||
float m = re[k]*re[k] + im[k]*im[k] + 1e-9f;
|
|
||||||
spec[k * T + t] = std::sqrt(m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mel = mel_basis [128, 513] @ spec [513, T] -> [128, T]
|
|
||||||
std::vector<float> mel(N_MELS * T, 0.0f);
|
|
||||||
for (int m = 0; m < N_MELS; m++) {
|
|
||||||
for (int t = 0; t < T; t++) {
|
|
||||||
float s = 0.0f;
|
|
||||||
for (int k = 0; k < FFT_BINS; k++)
|
|
||||||
s += mel_basis[m * FFT_BINS + k] * spec[k * T + t];
|
|
||||||
mel[m * T + t] = std::log(std::max(s, MEL_CLIP_VAL)); // dynamic_range_compression
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mel;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Forward ECAPA-TDNN ggml C++ — bit-match qwen_tts.modeling_qwen3_tts
|
// Forward ECAPA-TDNN ggml C++ — bit-match qwen_tts.modeling_qwen3_tts
|
||||||
|
|
@ -540,7 +441,8 @@ SpeakerEncoder * speaker_encoder_load(const char * gguf_path, const char * mel_b
|
||||||
std::vector<float> speaker_encoder_encode_waveform(SpeakerEncoder * spk, const std::vector<float> & wav) {
|
std::vector<float> speaker_encoder_encode_waveform(SpeakerEncoder * spk, const std::vector<float> & wav) {
|
||||||
if (!spk) return {};
|
if (!spk) return {};
|
||||||
int T_mel = 0;
|
int T_mel = 0;
|
||||||
auto mel = mel_spectrogram(wav, spk->mel_basis, T_mel);
|
const auto cfg = kazeia_mel::config_qwen3_tts_speaker();
|
||||||
|
auto mel = kazeia_mel::compute(wav, spk->mel_basis, cfg, T_mel);
|
||||||
if (mel.empty()) return {};
|
if (mel.empty()) return {};
|
||||||
return spk_forward(*spk, mel, T_mel);
|
return spk_forward(*spk, mel, T_mel);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,167 @@
|
||||||
|
// Implementation stt_engine — voir stt_engine.h pour l'API publique.
|
||||||
|
//
|
||||||
|
// S1.3 (cette session) : squelette compilable, VAD complet, mel via kazeia_mel.
|
||||||
|
// stt_engine_load / transcribe / free : stubs renvoyant err=-99 jusqu'au port ORT
|
||||||
|
// QNN en S2.
|
||||||
|
#include "stt_engine.h"
|
||||||
|
#include "kazeia_mel.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// VAD RMS énergie (port C++ de VadStage.kt)
|
||||||
|
// ============================================================================
|
||||||
|
//
|
||||||
|
// État interne : compteur de frames parole / silence consécutifs, sliding sur
|
||||||
|
// chunks PCM. L'appelant push des chunks de taille frame_size; chaque push fait
|
||||||
|
// avancer l'état. is_speech / is_end_of_speech consultent l'état.
|
||||||
|
|
||||||
|
struct SttVadState {
|
||||||
|
int sample_rate;
|
||||||
|
int frame_size; // 1600 = 100 ms @ 16 kHz
|
||||||
|
int rms_threshold; // 150 sur PCM16 brut (prod KazeiaService)
|
||||||
|
int min_speech_frames; // 3 -> 300 ms de parole pour déclencher
|
||||||
|
int silence_end_frames; // 8 -> 800 ms de silence pour finir
|
||||||
|
|
||||||
|
int speech_count = 0;
|
||||||
|
int silence_count = 0;
|
||||||
|
bool in_speech = false;
|
||||||
|
bool end_of_speech = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
SttVadState * stt_vad_new(int sample_rate, int frame_size, int rms_threshold,
|
||||||
|
int min_speech_frames, int silence_end_frames) {
|
||||||
|
auto * s = new SttVadState();
|
||||||
|
s->sample_rate = sample_rate;
|
||||||
|
s->frame_size = frame_size;
|
||||||
|
s->rms_threshold = rms_threshold;
|
||||||
|
s->min_speech_frames = min_speech_frames;
|
||||||
|
s->silence_end_frames = silence_end_frames;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stt_vad_reset(SttVadState * s) {
|
||||||
|
if (!s) return;
|
||||||
|
s->speech_count = 0;
|
||||||
|
s->silence_count = 0;
|
||||||
|
s->in_speech = false;
|
||||||
|
s->end_of_speech = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stt_vad_free(SttVadState * s) { delete s; }
|
||||||
|
|
||||||
|
bool stt_vad_is_speech(const SttVadState * s) { return s && s->in_speech; }
|
||||||
|
bool stt_vad_is_end_of_speech(const SttVadState * s) { return s && s->end_of_speech; }
|
||||||
|
|
||||||
|
// Calcule RMS d'un buffer PCM16, renvoie l'énergie en unités PCM brutes
|
||||||
|
// (compatible seuil 150 prod).
|
||||||
|
static float rms_pcm16(const int16_t * pcm, int n) {
|
||||||
|
if (n <= 0) return 0.0f;
|
||||||
|
double acc = 0.0;
|
||||||
|
for (int i = 0; i < n; ++i) { double v = (double)pcm[i]; acc += v * v; }
|
||||||
|
return (float)std::sqrt(acc / (double)n);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool stt_vad_push(SttVadState * s, const int16_t * pcm, int n_samples) {
|
||||||
|
if (!s || !pcm || n_samples <= 0) return false;
|
||||||
|
s->end_of_speech = false;
|
||||||
|
|
||||||
|
// Traite par chunks de frame_size. Si n_samples < frame_size, on accumulera
|
||||||
|
// mentalement mais ici on calcule simplement la RMS sur ce qu'on a (l'app
|
||||||
|
// appelante pousse des chunks pleins de 100 ms en pratique).
|
||||||
|
int offset = 0;
|
||||||
|
while (offset + s->frame_size <= n_samples) {
|
||||||
|
float r = rms_pcm16(pcm + offset, s->frame_size);
|
||||||
|
if (r >= (float)s->rms_threshold) {
|
||||||
|
s->speech_count += 1;
|
||||||
|
s->silence_count = 0;
|
||||||
|
if (!s->in_speech && s->speech_count >= s->min_speech_frames) {
|
||||||
|
s->in_speech = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s->silence_count += 1;
|
||||||
|
// Pas de reset speech_count tant qu'on est in_speech, c'est l'enchainement
|
||||||
|
// silence consécutifs qui clôt.
|
||||||
|
if (s->in_speech && s->silence_count >= s->silence_end_frames) {
|
||||||
|
s->in_speech = false;
|
||||||
|
s->end_of_speech = true;
|
||||||
|
s->speech_count = 0;
|
||||||
|
} else if (!s->in_speech) {
|
||||||
|
s->speech_count = 0; // reset compteur si on n'a pas atteint le seuil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset += s->frame_size;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// SttEngine (stubs — port ORT QNN en S2)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
struct SttEngine {
|
||||||
|
SttEngineLoadCfg cfg;
|
||||||
|
std::vector<float> mel_basis; // [80, 201] librosa whisper
|
||||||
|
// S2 :
|
||||||
|
// void * encoder_session; // Ort::Session*
|
||||||
|
// void * decoder_session;
|
||||||
|
// std::vector<int> vocab_ids;
|
||||||
|
// std::unordered_map<std::string,int> token_to_id;
|
||||||
|
bool loaded = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
SttEngine * stt_engine_load(const SttEngineLoadCfg & cfg) {
|
||||||
|
if (!cfg.model_dir) {
|
||||||
|
fprintf(stderr, "stt_engine_load: model_dir requis\n"); return nullptr;
|
||||||
|
}
|
||||||
|
auto * eng = new SttEngine();
|
||||||
|
eng->cfg = cfg;
|
||||||
|
// Tente charger mel_filters.bin (préféré) ou mel_filters.json (sera porté en S2).
|
||||||
|
std::string mel_path = std::string(cfg.model_dir) + "/mel_filters.bin";
|
||||||
|
auto mel_cfg = kazeia_mel::config_whisper();
|
||||||
|
if (!kazeia_mel::load_mel_basis(mel_path.c_str(), mel_cfg, eng->mel_basis)) {
|
||||||
|
fprintf(stderr, "stt_engine_load: mel_filters.bin absent ou mauvaise taille à %s\n",
|
||||||
|
mel_path.c_str());
|
||||||
|
// Pas fatal : à S2 on chargera depuis mel_filters.json. Pour S1.3 on continue
|
||||||
|
// pour permettre les tests VAD.
|
||||||
|
}
|
||||||
|
eng->loaded = true;
|
||||||
|
return eng;
|
||||||
|
}
|
||||||
|
|
||||||
|
SttTranscribeResult stt_engine_transcribe(SttEngine * eng, const SttTranscribeCfg & cfg) {
|
||||||
|
SttTranscribeResult R{};
|
||||||
|
if (!eng || !eng->loaded || !cfg.pcm16 || cfg.n_samples <= 0) {
|
||||||
|
R.err = -1; return R;
|
||||||
|
}
|
||||||
|
// Étape 1 : mel via kazeia_mel (Whisper config). Implémentée en S1.3 pour exercer
|
||||||
|
// la lib partagée ; le encoder/decoder ORT viennent en S2.
|
||||||
|
std::vector<float> wav((size_t)cfg.n_samples);
|
||||||
|
for (int i = 0; i < cfg.n_samples; ++i) wav[i] = (float)cfg.pcm16[i] / 32768.0f;
|
||||||
|
|
||||||
|
if (eng->mel_basis.empty()) {
|
||||||
|
fprintf(stderr, "stt_engine_transcribe: mel_basis non chargé (mel_filters.bin manquant)\n");
|
||||||
|
R.err = -2; return R;
|
||||||
|
}
|
||||||
|
auto mel_cfg = kazeia_mel::config_whisper();
|
||||||
|
int T = 0;
|
||||||
|
auto mel = kazeia_mel::compute(wav, eng->mel_basis, mel_cfg, T);
|
||||||
|
if (mel.empty()) { R.err = -3; return R; }
|
||||||
|
R.mel_ms = 0; // TODO: timer
|
||||||
|
|
||||||
|
// Étape 2..N : encoder NPU + decoder loop + tokenizer BPE -> S2.
|
||||||
|
fprintf(stderr, "stt_engine_transcribe: stubs (port S2 requis). mel %d frames OK.\n", T);
|
||||||
|
R.err = -99; // not implemented yet
|
||||||
|
R.detected_language = cfg.language;
|
||||||
|
return R;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stt_engine_free(SttEngine * eng) {
|
||||||
|
if (!eng) return;
|
||||||
|
delete eng;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
// Engine STT Whisper-Small Qualcomm AI Hub (HfWhisper KV-cache) in-process.
|
||||||
|
// Load une fois (encoder QAIRT context + decoder QAIRT context + mel_filters +
|
||||||
|
// vocab), transcribe N fois (PCM 16kHz mono -> texte FR/EN).
|
||||||
|
//
|
||||||
|
// Backend : ONNX Runtime + QNN ExecutionProvider (HTP V79 sur SM8750).
|
||||||
|
// Empreinte RAM : ~545 MB (encoder ctx 201 MB + decoder ctx 345 MB).
|
||||||
|
//
|
||||||
|
// Bench de référence prod (Whisper-Small, FR, audio 1.6s) :
|
||||||
|
// mel 189 ms (CPU C++) + encoder 125 ms (NPU) + decoder 510 ms (NPU, 22 tokens)
|
||||||
|
// total 825 ms, RTF 0.51, RAM peak 545 MB.
|
||||||
|
//
|
||||||
|
// Construit pour remplacer WhisperHybridEngine.kt + libmel_extractor.so + VadStage.kt
|
||||||
|
// derrière une API unifiée Kazeia-Engine. La sélection de backend (HTP vs CPU) est
|
||||||
|
// automatique via QNN EP : si le contexte QAIRT est trouvé, HTP ; sinon CPU fallback.
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct SttEngine; // opaque
|
||||||
|
|
||||||
|
struct SttEngineLoadCfg {
|
||||||
|
// Dir contenant : HfWhisperEncoder.onnx + HfWhisperEncoder_qairt_context.bin,
|
||||||
|
// HfWhisperDecoder.onnx + HfWhisperDecoder_qairt_context.bin,
|
||||||
|
// mel_filters.json (ou .bin), vocab.json (BPE byte-level).
|
||||||
|
const char * model_dir = nullptr;
|
||||||
|
|
||||||
|
// 1 = QNN HTP (NPU, défaut prod). 0 = CPU fallback (ONNX Runtime CPU EP).
|
||||||
|
// QNN EP requiert que libQnnHtp.so + libQnnHtpV79Skel.so + libcdsprpc.so soient
|
||||||
|
// dans LD_LIBRARY_PATH (côté tablette : push depuis dist/lib/).
|
||||||
|
bool use_htp = true;
|
||||||
|
|
||||||
|
int n_threads = 6;
|
||||||
|
int max_decode_steps = 200; // borne hard du loop decoder autorégressif
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SttTranscribeCfg {
|
||||||
|
// PCM mono 16-bit signed, n'importe quelle longueur (le mel pad fait à 30s).
|
||||||
|
// Si vous avez du PCM 24kHz, resamplez externe avant (ou utilisez SttTranscribeWaveform).
|
||||||
|
const int16_t * pcm16 = nullptr;
|
||||||
|
int n_samples = 0;
|
||||||
|
int sample_rate = 16000;
|
||||||
|
|
||||||
|
// "fr", "en", "auto", etc. Whisper-Small supporte 99 langues. "auto" = détection
|
||||||
|
// via la langue token de Whisper.
|
||||||
|
const char * language = "fr";
|
||||||
|
|
||||||
|
// 1 = force le mode "transcribe" (FR -> texte FR). 0 = laisse Whisper choisir
|
||||||
|
// entre transcribe et translate (translate force la sortie en EN, pas voulu pour
|
||||||
|
// Kazeia FR). Défaut : 1 (cf override prod KazeiaService).
|
||||||
|
bool force_transcribe = true;
|
||||||
|
|
||||||
|
// Inclure les timestamps (segments + offsets) dans la sortie.
|
||||||
|
bool with_timestamps = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SttSegment {
|
||||||
|
int t0_ms; // début segment (ms relatif au début audio)
|
||||||
|
int t1_ms; // fin segment
|
||||||
|
std::string text;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SttTranscribeResult {
|
||||||
|
int err = 0; // 0 = ok, <0 = err
|
||||||
|
std::string text; // texte concaténé (sans timestamps)
|
||||||
|
std::vector<SttSegment> segments; // vide si !with_timestamps
|
||||||
|
std::string detected_language; // "fr", "en", ...
|
||||||
|
|
||||||
|
// Métriques perf (ms)
|
||||||
|
int mel_ms = 0;
|
||||||
|
int encoder_ms = 0;
|
||||||
|
int decoder_ms = 0;
|
||||||
|
int total_ms = 0;
|
||||||
|
float rtf = 0.0f; // total_ms / (audio_duration_ms)
|
||||||
|
int n_tokens = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Charge encoder + decoder + mel basis + vocab. Retourne nullptr si échec.
|
||||||
|
// Idempotent vis-à-vis de llama_backend_init() (cohabite avec tts_engine).
|
||||||
|
SttEngine * stt_engine_load(const SttEngineLoadCfg & cfg);
|
||||||
|
|
||||||
|
// Transcrit une fenêtre audio. Stateful pour le warm path NPU (sessions ORT
|
||||||
|
// gardées en RAM entre appels).
|
||||||
|
SttTranscribeResult stt_engine_transcribe(SttEngine * eng, const SttTranscribeCfg & cfg);
|
||||||
|
|
||||||
|
// Libère tout (encoder ctx, decoder ctx, sessions ORT, vocab).
|
||||||
|
void stt_engine_free(SttEngine * eng);
|
||||||
|
|
||||||
|
// Helper VAD RMS énergie : renvoie true si le buffer PCM contient de la parole
|
||||||
|
// (énergie > seuil). Compatible drop-in avec VadStage.kt prod (frame=1600 @ 16kHz,
|
||||||
|
// seuil 150 PCM, ≥3 frames consécutives parole, ≥8 frames silence).
|
||||||
|
// Tu peux passer le PCM en streaming par chunks de 100ms et lire is_speech / is_end_of_speech.
|
||||||
|
struct SttVadState;
|
||||||
|
SttVadState * stt_vad_new(int sample_rate = 16000,
|
||||||
|
int frame_size = 1600,
|
||||||
|
int rms_threshold = 150,
|
||||||
|
int min_speech_frames = 3,
|
||||||
|
int silence_end_frames = 8);
|
||||||
|
bool stt_vad_push(SttVadState * s, const int16_t * pcm, int n_samples);
|
||||||
|
bool stt_vad_is_speech(const SttVadState * s);
|
||||||
|
bool stt_vad_is_end_of_speech(const SttVadState * s);
|
||||||
|
void stt_vad_reset(SttVadState * s);
|
||||||
|
void stt_vad_free(SttVadState * s);
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
// Test standalone S1.3 : exercer kazeia_mel (config_whisper) + VAD RMS.
|
||||||
|
// Usage : kazeia_stt_test <mel_filters.bin> <audio16k_mono.wav>
|
||||||
|
//
|
||||||
|
// Vérifie que le mel produit a la bonne shape [80 x 3000] et que les valeurs sont
|
||||||
|
// dans la plage Whisper (~[-1, 0.5] après norm). Permet de valider le mel partagé
|
||||||
|
// AVANT de brancher ORT/QNN en S2.
|
||||||
|
#include "kazeia_mel.h"
|
||||||
|
#include "stt_engine.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <vector>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
static std::vector<int16_t> read_wav_pcm16(const char* path, int & sr_out, int & channels_out) {
|
||||||
|
FILE* f = fopen(path, "rb");
|
||||||
|
if (!f) { fprintf(stderr, "open %s FAIL\n", path); return {}; }
|
||||||
|
char riff[12];
|
||||||
|
if (fread(riff, 1, 12, f) != 12 || memcmp(riff, "RIFF", 4) != 0 || memcmp(riff+8, "WAVE", 4) != 0) {
|
||||||
|
fprintf(stderr, "not RIFF/WAVE\n"); fclose(f); return {};
|
||||||
|
}
|
||||||
|
uint16_t channels = 0, bps = 0;
|
||||||
|
uint32_t sr = 0, data_sz = 0;
|
||||||
|
long data_off = -1;
|
||||||
|
while (!feof(f)) {
|
||||||
|
char cid[4]; uint32_t csz;
|
||||||
|
if (fread(cid, 1, 4, f) != 4) break;
|
||||||
|
if (fread(&csz, 4, 1, f) != 1) break;
|
||||||
|
if (memcmp(cid, "fmt ", 4) == 0) {
|
||||||
|
std::vector<uint8_t> buf(csz);
|
||||||
|
fread(buf.data(), 1, csz, f);
|
||||||
|
channels = *(uint16_t*)&buf[2];
|
||||||
|
sr = *(uint32_t*)&buf[4];
|
||||||
|
bps = *(uint16_t*)&buf[14];
|
||||||
|
} else if (memcmp(cid, "data", 4) == 0) {
|
||||||
|
data_sz = csz; data_off = ftell(f); break;
|
||||||
|
} else {
|
||||||
|
fseek(f, csz, SEEK_CUR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data_off < 0 || channels != 1 || bps != 16) {
|
||||||
|
fprintf(stderr, "WAV mono 16-bit attendu (got ch=%d bps=%d)\n", channels, bps);
|
||||||
|
fclose(f); return {};
|
||||||
|
}
|
||||||
|
sr_out = (int)sr; channels_out = channels;
|
||||||
|
fseek(f, data_off, SEEK_SET);
|
||||||
|
std::vector<int16_t> pcm(data_sz / 2);
|
||||||
|
fread(pcm.data(), 2, pcm.size(), f);
|
||||||
|
fclose(f);
|
||||||
|
return pcm;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
setvbuf(stderr, nullptr, _IONBF, 0);
|
||||||
|
setvbuf(stdout, nullptr, _IONBF, 0);
|
||||||
|
if (argc < 3) {
|
||||||
|
fprintf(stderr, "usage: %s <mel_filters.bin> <audio16k_mono.wav>\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) Mel basis 80x201 (Whisper)
|
||||||
|
auto cfg = kazeia_mel::config_whisper();
|
||||||
|
std::vector<float> mel_basis;
|
||||||
|
if (!kazeia_mel::load_mel_basis(argv[1], cfg, mel_basis)) return 2;
|
||||||
|
fprintf(stderr, "mel_basis Whisper [%d, %d] OK (%zu f32)\n",
|
||||||
|
cfg.n_mels, cfg.n_fft/2+1, mel_basis.size());
|
||||||
|
|
||||||
|
// 2) WAV
|
||||||
|
int sr = 0, ch = 0;
|
||||||
|
auto pcm = read_wav_pcm16(argv[2], sr, ch);
|
||||||
|
if (pcm.empty()) return 3;
|
||||||
|
fprintf(stderr, "wav : %zu samples @ %d Hz = %.2f s\n",
|
||||||
|
pcm.size(), sr, (double)pcm.size() / sr);
|
||||||
|
if (sr != 16000) fprintf(stderr, "WARNING: sr=%d != 16000 (resample externe requis)\n", sr);
|
||||||
|
|
||||||
|
// 3) Convert to f32
|
||||||
|
std::vector<float> wav(pcm.size());
|
||||||
|
for (size_t i = 0; i < pcm.size(); ++i) wav[i] = (float)pcm[i] / 32768.0f;
|
||||||
|
|
||||||
|
// 4) Mel via kazeia_mel
|
||||||
|
int T = 0;
|
||||||
|
auto mel = kazeia_mel::compute(wav, mel_basis, cfg, T);
|
||||||
|
fprintf(stderr, "mel computed : [%d mels, %d frames] = %zu f32 (cible 80 x 3000 Whisper)\n",
|
||||||
|
cfg.n_mels, T, mel.size());
|
||||||
|
|
||||||
|
float mn = 1e9f, mx = -1e9f; double sum = 0;
|
||||||
|
for (float v : mel) { if (v < mn) mn = v; if (v > mx) mx = v; sum += v; }
|
||||||
|
fprintf(stderr, "mel stats : min=%.4f max=%.4f mean=%.4f\n",
|
||||||
|
mn, mx, (float)(sum / mel.size()));
|
||||||
|
fprintf(stderr, "(plage Whisper attendue ~[-1.0, 0.5])\n");
|
||||||
|
|
||||||
|
// 5) VAD test : push tout le PCM par chunks de 1600 samples
|
||||||
|
fprintf(stderr, "\n=== VAD RMS test (frame=1600=100ms, seuil=150) ===\n");
|
||||||
|
SttVadState * vad = stt_vad_new(16000, 1600, 150, 3, 8);
|
||||||
|
int speech_seen = 0, eos_count = 0;
|
||||||
|
for (size_t off = 0; off + 1600 <= pcm.size(); off += 1600) {
|
||||||
|
stt_vad_push(vad, pcm.data() + off, 1600);
|
||||||
|
if (stt_vad_is_speech(vad)) speech_seen += 1;
|
||||||
|
if (stt_vad_is_end_of_speech(vad)) eos_count += 1;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "VAD : %d frames de parole / %zu total, %d end-of-speech\n",
|
||||||
|
speech_seen, pcm.size() / 1600, eos_count);
|
||||||
|
stt_vad_free(vad);
|
||||||
|
|
||||||
|
// 6) Dump mel pour comparaison externe si KZSTT_DUMP_MEL=path
|
||||||
|
if (const char * p = std::getenv("KZSTT_DUMP_MEL")) {
|
||||||
|
std::ofstream f(p, std::ios::binary);
|
||||||
|
f.write((char*)mel.data(), mel.size() * sizeof(float));
|
||||||
|
fprintf(stderr, "mel dumped -> %s\n", p);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <ctime>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
@ -67,11 +68,16 @@ int main(int argc, char** argv) {
|
||||||
// l'utilise comme override pour ce synth (clonage vocal in-process).
|
// l'utilise comme override pour ce synth (clonage vocal in-process).
|
||||||
std::vector<float> ref_xvec;
|
std::vector<float> ref_xvec;
|
||||||
if (const char * ref_wav = getenv("KZTTS_REF_WAV")) {
|
if (const char * ref_wav = getenv("KZTTS_REF_WAV")) {
|
||||||
|
struct timespec t0, t1;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &t0);
|
||||||
ref_xvec = tts_engine_encode_speaker_wav(eng, ref_wav);
|
ref_xvec = tts_engine_encode_speaker_wav(eng, ref_wav);
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &t1);
|
||||||
|
double enc_s = (t1.tv_sec - t0.tv_sec) + (t1.tv_nsec - t0.tv_nsec) * 1e-9;
|
||||||
if (ref_xvec.empty()) {
|
if (ref_xvec.empty()) {
|
||||||
fprintf(stderr, "tts_pipeline: KZTTS_REF_WAV=%s FAIL (encoder missing or read FAIL)\n", ref_wav);
|
fprintf(stderr, "tts_pipeline: KZTTS_REF_WAV=%s FAIL (encoder missing or read FAIL)\n", ref_wav);
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "tts_pipeline: KZTTS_REF_WAV=%s -> x_vector[%zu] OK\n", ref_wav, ref_xvec.size());
|
fprintf(stderr, "tts_pipeline: KZTTS_REF_WAV=%s -> x_vector[%zu] in %.3fs\n",
|
||||||
|
ref_wav, ref_xvec.size(), enc_s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
# libonnxruntime.so (Microsoft ONNX Runtime + QNN EP)
|
||||||
|
|
||||||
|
Cette lib est requise par stt_engine.cpp pour exécuter Whisper-Small sur HTP V79.
|
||||||
|
Non versionnée dans git à cause de sa taille (20 MB).
|
||||||
|
|
||||||
|
Source : Maven `com.microsoft.onnxruntime:onnxruntime-android-qnn:1.24.3`.
|
||||||
|
|
||||||
|
Pour récupérer : extraire de l'AAR Gradle cache :
|
||||||
|
```bash
|
||||||
|
AAR=~/.gradle/caches/8.12/transforms/*/transformed/onnxruntime-android-qnn-1.24.3
|
||||||
|
cp $AAR/jni/arm64-v8a/libonnxruntime.so /opt/Kazeia-engine/dist/lib/
|
||||||
|
cp $AAR/headers/*.h /opt/Kazeia-engine/dist/include/onnxruntime/
|
||||||
|
```
|
||||||
|
|
||||||
|
Headers C++ : dans `dist/include/onnxruntime/` (versionnés, ~1 MB).
|
||||||
Loading…
Reference in New Issue