community_models: VibeASR I8_S VAE encoder - #440
Closed
XsquirrelC wants to merge 7 commits into
Closed
Conversation
Owner
@XsquirrelC A dedicated family is the cleaner way to go. |
This was referenced Sep 4, 2026
Contributor
Author
|
Closing in favour of #448. You asked for two PRs in microsoft/VibeASR.cpp#10 — one for the additive ggml changes, one for the model integration — so this four-PR stack has been reorganized into exactly that: #447 (ggml) and #448 (model). Same code, no functional change; sorry for the churn. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #438 (the I8_S/I2_S types and fused ops). This branch contains #438's three commits plus one; please review/merge #438 first — only
community_models: VibeASR I8_S VAE encoder(a631fab) is new here.Ports the audio VAE encoder from Microsoft's VibeASR.cpp, which runs the VibeVoice acoustic/semantic tokenizers on INT8 weights and INT8 activations end to end.
Relation to the existing
vibevoice_asrfamilyCONTRIBUTING asks not to duplicate an already-listed family, so to be explicit: this is the same model audio.cpp already ships as
vibevoice_asr, and this PR does not duplicate that family's loader, session, connector, or decoder. What is new is the numeric pipeline — I8_S weights with INT8 activations through the fused ops from #438, where the core family runs F32/Q8_0 activations in F32. Upstream's decoder is BitNet-style ternary I2_S; that half is not ported here (two follow-up PRs: the I2_S matmul kernel, then the decoder plus loader and session).It stays an additive
community_models/vibeasrentry rather than a weight path insidevibevoice_asr, because the two share no graph code: every activation here is I8_S and every node is one of the fused CPU-only ops, so folding it in would put a second, mutually exclusive graph builder and a second backend policy behind one family's loader. The reuse that is worth having — tokenizer vocabulary, prompt layout, feature-injection order — is data and conventions, and this entry followsvibevoice_asron all of it. The trade-off is written up in docs/community_models/vibeasr.md.What is in this PR
tools/community_models/convert_vibeasr_vae.py— remaps GGUF tensor type ids. Upstream's fork put I2_S/I8_S at 36/37, which upstream ggml already spent on the retiredIQ4_NL_4_4/IQ4_NL_4_8slots; audio.cpp registers them at 42/43. On-disk layout is identical, so the tool rewrites the 4-byte type field per tensor info and copies everything else byte for byte (offsets, data section, and KV block untouched).--list/--checkmodes for inspection.include/engine/community_models/vibeasr/{assets,vae_encoder}.h,src/community_models/vibeasr/{assets,vae_encoder}.cpp— geometry derived from the tensor table (which block tensors exist, what shape each weight is) rather than GGUF metadata, then the encoder graph: 7 stages, strides{1,2,2,4,5,5,8}(3200 samples/frame), depths3-3-3-3-3-3-8, channels 32→2048, causal convs, ConvNeXt blocks with layer scale, latent head (64 acoustic / 128 semantic), connector out at 1536.tests/vibeasr/test_vibeasr_vae_encoder.cpp— parity probe, registered in ctest withSKIP_RETURN_CODE 125so a checkout without the 703 MB package skips instead of failing.docs/community_models/models.md, and a pointer from thevibevoice_asrsection ofdocs/asr.md.No loader and no CLI family: without the decoder there is no session to register, so
check_loader_catalog_sync.pyhas nothing new to advertise (run anyway, clean).Build
cmake -B build -DCMAKE_BUILD_TYPE=Release -DENGINE_BUILD_MODEL_TESTS=ON cmake --build build -j$(nproc)Run
python3 tools/community_models/convert_vibeasr_vae.py \ --input vibeasr-vae-encoder-i8_s.gguf \ --output models/vibeasr/vae_encoder-i8_s.gguf ./build/bin/test_vibeasr_vae_encoder \ --model models/vibeasr/vae_encoder-i8_s.gguf \ --audio assets/asr_validation/librispeech/librispeech_test_clean_6930-75918-0000.wav \ --reference-acoustic ref_acoustic.f32 \ --reference-semantic ref_semantic.f32 \ --threads 8Model path:
models/vibeasr/vae_encoder-i8_s.gguf(703 MB, both branches). No model-manager package id — the upstream weights are not published as an audio.cpp package. Reference dumps are raw F32[frames][dim]row-major from upstream's ownvae_encode_acoustic/vae_encode_semantic.Parity
3.505 s LibriSpeech clip, 17 frames × 1536 per branch, CPU backend:
Layer by layer, stage 0 is bit-exact — every int8 byte and every scale — which is what actually pins the layouts, causal padding, kernel padding, and weight mapping. The first divergence is 5 of 1,794,560 elements one int8 step apart at an identical scale, entering stage 1, and it compounds because every later stage requantizes.
Bit-exactness is not reachable: audio.cpp stores each per-tensor scale as a multiplier (
amax/127, dequantize by multiplying) and upstream stores its reciprocal (127/amax, dequantize by dividing) — equal to within the last float bit, which is enough to flip a value sitting on a rounding boundary. Upstream also rounds ties to even in its vector body but away from zero in its scalar tail, so no single convention reproduces it.For calibration, nudging one input sample by one int8 step and re-running upstream against itself moves its own output by cosine 0.99592 (acoustic) / 0.98700 (semantic) — the graph amplifies a single LSB about as far as the two implementations differ. So the probe gates on mean-abs-relative ≤ 2% and cosine ≥ 0.98; tighter would be testing rounding luck.
Also in this branch: #438's
i8_s_fused_ops_testgains a case for the in-band scale survivingggml_cont(ggml_permute(...)). The encoder flips activations between channel-major and length-major constantly, andggml_compute_forward_dupwas copying the payload without the scale — values stayed right while everything downstream was off by an arbitrary factor (that was the difference between cosine 0.26 and 0.99 end to end).Backend and performance
CPU only — the fused I8_S ops have no CUDA or Metal kernels, and the probe pins the backend to CPU. Release build, 24-core AMD EPYC 7V13, 3.505 s clip, both branches:
Peak RSS 1.31 GB against 703 MB of weights:
BackendWeightStorestages each tensor before upload, so load briefly holds roughly two copies. Graph arena 64 MB by default. No VRAM.Tests
test_vibeasr_vae_encoderpasses with the package present (2.41 s) and skips without it.i8_s_fused_ops_testpasses at 1 and 4 threads; I confirmed the newcont(permute)case fails if the scale propagation is removed.Known limitations
Upstream: https://github.com/microsoft/VibeASR.cpp