Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ __pycache__/
/tests/omnivoice/outputs/
/unittests/
/models/
/granite5asr/
/reference/
/resources/
/patches/
Expand Down
42 changes: 42 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,22 @@ audiocpp_add_model(citrinet_asr
engine::models::citrinet_asr::make_citrinet_asr_loader
)

audiocpp_add_model(granite5asr
SOURCES
src/community_models/granite5asr/assets.cpp
src/community_models/granite5asr/frontend.cpp
src/community_models/granite5asr/encoder.cpp
src/community_models/granite5asr/session.cpp
INCLUDES
engine/community_models/granite5asr/session.h
LOADERS
engine::community_models::granite5asr::make_granite5asr_loader
ALIASES
granite_speech5_asr
granite_speech
granite_speech5_ctc
)

audiocpp_add_model(vevo2
SOURCES
src/models/vevo2/ar.cpp
Expand Down Expand Up @@ -1801,6 +1817,31 @@ add_executable(miocodec_wavlm_parity EXCLUDE_FROM_ALL

target_link_libraries(miocodec_wavlm_parity PRIVATE engine_runtime ggml)

add_executable(test_granite5asr_golden_transcription
tests/granite5asr/test_granite5asr_golden_transcription.cpp
)
target_compile_definitions(test_granite5asr_golden_transcription PRIVATE
ENGINE_REPO_ROOT="${CMAKE_CURRENT_SOURCE_DIR}"
)
target_link_libraries(test_granite5asr_golden_transcription PRIVATE engine_runtime ggml)
target_include_directories(test_granite5asr_golden_transcription PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
if (ENGINE_ENABLE_OPENMP)
target_link_libraries(test_granite5asr_golden_transcription PRIVATE OpenMP::OpenMP_CXX)
endif()

add_executable(granite5asr_warm_bench
tests/granite5asr/granite5asr_warm_bench.cpp
)
target_compile_definitions(granite5asr_warm_bench PRIVATE
ENGINE_REPO_ROOT="${CMAKE_CURRENT_SOURCE_DIR}"
)
target_link_libraries(granite5asr_warm_bench PRIVATE engine_runtime ggml)
target_include_directories(granite5asr_warm_bench PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
if (ENGINE_ENABLE_OPENMP)
target_link_libraries(granite5asr_warm_bench PRIVATE OpenMP::OpenMP_CXX)
endif()


# F5/Habibi tests: parity harnesses + e2e sample generator. Only when the
# f5_tts model is linked (they call model-internal symbols) AND test hooks
# are explicitly enabled — F5_MEL_TEST must never leak into production
Expand Down Expand Up @@ -1930,6 +1971,7 @@ if (ENGINE_BUILD_WARMBENCH)
add_engine_warmbench(confucius4_tts_warm_bench tests/confucius4_tts/confucius4_tts_warm_bench.cpp)
add_engine_warmbench(controlfoley_warm_bench tests/controlfoley/controlfoley_warm_bench.cpp)
add_engine_warmbench(fun_asr_nano_warm_bench tests/fun_asr_nano/fun_asr_nano_warm_bench.cpp)
add_engine_warmbench(granite5asr_warm_bench tests/granite5asr/granite5asr_warm_bench.cpp)
add_engine_warmbench(higgs_audio_stt_warm_bench tests/higgs_audio_stt/higgs_audio_stt_warm_bench.cpp)
add_engine_warmbench(higgs_audio_tts_warm_bench tests/higgs_audio_tts/higgs_audio_tts_warm_bench.cpp)
add_engine_warmbench(hviske_asr_warm_bench tests/hviske_asr/hviske_asr_warm_bench.cpp)
Expand Down
38 changes: 38 additions & 0 deletions docs/community_models/granite5asr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# IBM Granite Speech 5.0 470M TurboCTC in audio.cpp

IBM Granite Speech 5.0 TurboCTC is a compact 470-million-parameter English Automatic Speech Recognition (ASR) model delivering state-of-the-art transcription accuracy with ultra-low latency.

## Architecture

- **Audio Frontend**: 16 kHz, 80-bin HTK mel spectrogram with 8.0 dB dynamic flooring, first-order deltas, and 2x frame stacking (320-dim features).
- **Acoustic Conformer**: 16 layers ($d_{model}=1024, d_{ff}=4096, d_{conv}=2048, heads=8, head\_dim=128$) with block self-attention ($context\_size=128$), Shaw relative positional embeddings, depthwise convolution with folded batch-norm, and mid-layer CTC self-conditioning at layer 8.
- **Decoder**: Non-autoregressive Connectionist Temporal Classification (CTC) with greedy decoding and 16,384 BPE vocabulary via fast HuggingFace tokenizer.

## CLI Usage

### Offline Transcription

```bash
# Transcribe audio using native safetensors directory
audiocpp_cli asr --model granite5asr --input sample.wav

# Or explicitly pass the family
audiocpp_cli asr --family granite5asr --model path/to/checkpoint --input sample.wav
```

### Quantized GGUF Loading

```bash
# Transcribe using a converted Q8_0 GGUF package
audiocpp_cli asr --model models/granite-speech-5.0-470m-turboctc-gguf --input sample.wav
```

### Audio Chunking & VAD

```bash
# Long audio with automatic VAD segmentation
audiocpp_cli asr --model granite5asr --input long_audio.wav --option audio_chunk_mode=auto

# Fixed 30-second chunking
audiocpp_cli asr --model granite5asr --input long_audio.wav --option audio_chunk_mode=fixed --option audio_chunk_duration_sec=30
```
1 change: 1 addition & 0 deletions docs/community_models/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Practical expectations:
|---|---|---|---|---|
| **f5_tts** | TTS, voice cloning | en, ar (Habibi) | Community | [F5-TTS](f5_tts.md) flow-matching DiT — M0 scaffolding, aliases `habibi`/`habibi_tts` |
| **glm_tts** | TTS, voice cloning | zh, en | Mirek [@mirek190](https://github.com/mirek190) | [GLM-TTS](glm_tts.md) zero-shot synthesis and voice cloning support |
| **granite5asr** | ASR | en | Community | [IBM Granite Speech 5.0 470M TurboCTC](granite5asr.md) ultra-fast Conformer-CTC ASR with Shaw relative positional embeddings and ByteLevel BPE |
| **inflect_v2** | TTS | en | Community | [Inflect Micro v2 and Nano v2](inflect_v2.md) native FP32 offline synthesis |
| **kroko_asr** | ASR | de, en, es, fr, it, he, nl, pt, sv, tr | Mirek [@mirek190](https://github.com/mirek190) | [Kroko Community ASR](kroko_asr.md) native offline/streaming Zipformer2/RNN-T transcription with word timestamps |
| **mms_forced_aligner** | Align | nl (nld), en (eng); pre-romanized Latin | Community | [MMS-300M-1130 Forced Aligner](mms_forced_aligner.md) word-timestamp alignment from a wav2vec2 CTC checkpoint (safetensors or local GGUF) |
Expand Down
66 changes: 66 additions & 0 deletions include/engine/community_models/granite5asr/assets.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#pragma once

#include "engine/framework/assets/tensor_source.h"
#include "engine/framework/model_spec/package.h"
#include "engine/framework/tokenizers/hf_tokenizer_json.h"

#include <cstdint>
#include <filesystem>
#include <memory>
#include <string>
#include <vector>

namespace engine::community_models::granite5asr {

struct Granite5FrontendConfig {
int64_t sample_rate = 16000;
int64_t n_fft = 512;
int64_t win_length = 400;
int64_t hop_length = 160;
int64_t n_mels = 80;
int64_t stack_factor = 2;
bool deltas = true;
int64_t delta_win_length = 3;
float logmel_floor_db = 8.0f;
};

struct Granite5EncoderConfig {
int64_t hidden_size = 1024;
int64_t intermediate_size = 4096;
int64_t num_layers = 16;
int64_t num_attention_heads = 8;
int64_t num_key_value_heads = 8;
int64_t head_dim = 128;
int64_t context_size = 128;
int64_t conv_kernel_size = 7;
int64_t conv_expansion_factor = 2;
int64_t max_position_embeddings = 512;
int64_t num_mel_bins = 80;
int64_t input_features = 320;
int64_t vocab_size = 16384;
std::vector<int64_t> subsample_layers = {0, 1};
};

struct Granite5ASRConfig {
std::string model_type = "granite_speech5_ctc";
int64_t vocab_size = 16384;
int64_t pad_token_id = 0;
int64_t blank_token_id = 0;
Granite5FrontendConfig frontend;
Granite5EncoderConfig encoder;
};

struct Granite5ASRAssets {
assets::ResourceBundle resources;
std::shared_ptr<const assets::TensorSource> source;
Granite5ASRConfig config;
std::shared_ptr<tokenizers::HuggingFaceTokenizerJson> tokenizer;
std::vector<uint8_t> special_token_ids;
};

Granite5ASRConfig parse_granite5asr_config(const std::string & json_text);

std::shared_ptr<const Granite5ASRAssets> load_granite5asr_assets(
const std::filesystem::path & model_path);

} // namespace engine::community_models::granite5asr
72 changes: 72 additions & 0 deletions include/engine/community_models/granite5asr/encoder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#pragma once

#include "engine/framework/core/backend.h"
#include "engine/framework/core/backend_weight_store.h"
#include "engine/framework/core/execution_context.h"
#include "engine/framework/modules/linear_module.h"
#include "engine/framework/modules/norm_modules.h"
#include "engine/community_models/granite5asr/assets.h"
#include "engine/community_models/granite5asr/frontend.h"

#include <cstdint>
#include <memory>
#include <vector>

namespace engine::community_models::granite5asr {

struct Granite5LayerWeights {
modules::NormWeights ffn1_norm;
modules::LinearWeights ffn1_fc1;
modules::LinearWeights ffn1_fc2;

modules::NormWeights norm_self_att;
core::TensorValue q_proj;
core::TensorValue k_proj;
core::TensorValue v_proj;
modules::LinearWeights o_proj;
core::TensorValue rel_pos_emb;

modules::NormWeights norm_conv;
modules::LinearWeights conv_pw1;
core::TensorValue conv_dw_weight;
core::TensorValue conv_dw_bias;
modules::LinearWeights conv_pw2;

modules::NormWeights ffn2_norm;
modules::LinearWeights ffn2_fc1;
modules::LinearWeights ffn2_fc2;

modules::NormWeights norm_out;

bool is_subsample = false;
};

struct Granite5EncoderWeights {
modules::LinearWeights input_linear;
std::vector<Granite5LayerWeights> layers;
modules::LinearWeights out;
modules::LinearWeights out_mid;
};

class Granite5EncoderRuntime {
public:
Granite5EncoderRuntime(
std::shared_ptr<const Granite5ASRAssets> assets,
engine::core::ExecutionContext & execution_context,
assets::TensorStorageType storage_type,
size_t graph_arena_bytes = 1024ull * 1024ull * 1024ull);

std::vector<int32_t> transcribe_features(
const Granite5FrontendFeatures & features);

const Granite5ASRAssets & assets() const noexcept { return *assets_; }

private:
std::shared_ptr<const Granite5ASRAssets> assets_;
engine::core::ExecutionContext * execution_context_ = nullptr;
engine::core::BackendWeightStore weight_store_;
Granite5EncoderWeights weights_;
size_t graph_arena_bytes_;
};

} // namespace engine::community_models::granite5asr
34 changes: 34 additions & 0 deletions include/engine/community_models/granite5asr/frontend.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once

#include "engine/framework/audio/dsp.h"
#include "engine/framework/runtime/session.h"
#include "engine/community_models/granite5asr/assets.h"

#include <memory>
#include <vector>

namespace engine::community_models::granite5asr {

struct Granite5FrontendFeatures {
std::vector<float> values;
int64_t frames = 0;
int64_t feature_dim = 320;
};

class Granite5Frontend {
public:
explicit Granite5Frontend(std::shared_ptr<const Granite5ASRAssets> assets);

Granite5FrontendFeatures extract(const runtime::AudioBuffer & audio) const;
Granite5FrontendFeatures extract_waveform(const std::vector<float> & waveform) const;
std::vector<float> prepare_waveform(const runtime::AudioBuffer & audio) const;

const Granite5FrontendConfig & config() const noexcept { return assets_->config.frontend; }

private:
std::shared_ptr<const Granite5ASRAssets> assets_;
audio::AudioTensor mel_filterbank_;
std::vector<float> window_;
};

} // namespace engine::community_models::granite5asr
Loading
Loading