Skip to content

Add cohere_asr support - validated-hw on 4 QAIC AI100 devices - #1233

Draft
mabusaa wants to merge 6 commits into
quic:mainfrom
mabusaa:feature/add-cohere_asr
Draft

Add cohere_asr support - validated-hw on 4 QAIC AI100 devices#1233
mabusaa wants to merge 6 commits into
quic:mainfrom
mabusaa:feature/add-cohere_asr

Conversation

@mabusaa

@mabusaa mabusaa commented Aug 3, 2026

Copy link
Copy Markdown

Summary

  • Fix 3 bugs in QEffCohereAsrDecoder.forward (KV cache gating, cache
    dropped on output, missing legacy-cache round-trip for ONNX export)
  • Fix QPC compile blocker: zero-pad encoder output to encoder_ctx_len
    (Reshape_3 shape mismatch on Decode spec)
  • Fix 7 incompatibilities in shared ASR test harness
  • Add quickcheck smoke test: test_cohere_asr_export_smoke
  • Add QPC-only inference script: examples/audio/cohere_asr_infer.py

Validation

HF fp32 == QEff == ORT == QPC fp16, token-for-token, 3/3 LibriSpeech
samples, 4 QAIC AI100 devices, 282–329 tok/s.
VoxPopuli demo clip transcription verified by listening (2026-08-03).

Note

Use repetition_penalty=1.3 + max_new_tokens in production (fp16 EOS collapse
at sentence boundary — documented in examples/audio/cohere_asr_infer.py).

AI assistance disclosure

Developed and validated with Claude Code assistance. All changes reviewed
and test commands run by the submitter.

Mohammad Abusaa and others added 6 commits August 2, 2026 09:19
Architecture: CohereAsrForConditionalGeneration
Category: asr (speech-to-text)
Validated: 4-stage pipeline + word-sequence transcript comparison
Signed-off-by: Mohammad Abusaa <mabusaa@qrc706r1-dl385-08.qualcomm.com>
QEffCohereAsrCrossAttention was reading the cross-KV cache via
`past_key_values.cross_attention_cache.layers[i].keys` — an attribute-chain
traversal that creates a disconnected tensor identity in the ONNX graph,
causing `qaic-compile` to reject the Encoder subgraph with:
  "retained state input not found: past_key_cross.0"

Fix mirrors Whisper's exact pattern:
1. QEffCohereAsrDecoderLayer.forward: extract
   `cross_attn_past_key_value = past_key_values.cross_attention_cache`
   before passing to encoder_attn, so the cross-attention module
   receives a QEffDynamicCache directly.
2. QEffCohereAsrCrossAttention.forward: use
   `past_key_values[layer_idx][0]` / `[1]` (QEffDynamicCache.__getitem__)
   instead of the attribute chain. __getitem__ returns the tensor directly
   with traceable identity, producing the correct past_key_cross.{i}
   input/output RetainedState pairing in the exported ONNX graph.

Also fix get_dummy_inputs and get_specializations (from prior session):
- get_dummy_inputs: use encoder_config.max_position_embeddings for
  cross-KV cache shape, not the decoder's max_position_embeddings (1024).
- get_specializations: use encoder_config.subsampling_factor (=8 for
  Parakeet Fast Conformer), not a hardcoded ×2 multiplier.

Verified: ONNX graph now contains all 16 past_key_cross/past_value_cross
{0..7} inputs and their _RetainedState output counterparts. Both encode
and decode forward paths execute correctly in PyTorch.

Also adds CohereLabs/cohere-transcribe-03-2026 to audio_model_configs.json
and fixes 7 test harness incompatibilities in test_speech_seq2seq_models.py
(use_cache kwarg guard, dtype pin, decoder_start_token_id backfill,
input_features transpose, cross_ctx_len from real encoder forward,
encoder_outputs SimpleNamespace wrap, cumulative accumulated_ids path
for no-KV-cache HF model).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Mohammad Abusaa <mabusaa@qrc706r1-dl385-08.qualcomm.com>
CohereAsr's Fast Conformer encoder produces ceil(feature_len /
subsampling_factor) output frames.  With feature_len=1 (the Decode
specialization's dummy input), that is 1 frame.  The cross-attention
ScatterND write path (Reshape_3) expected 625 frames (= encoder_ctx_len),
causing qaic-compile to reject the ONNX graph:

  Reshape: input shape (1,8,1,128) and output shape (1,8,625,128)
  have different number of elements (in 1024 vs. out 640000)

Fix: pad the encoder output to encoder_ctx_len before cross-attention
via torch.cat.  The padding tensor is constructed with a dynamic size
(ConstantOfShape in ONNX) that QAIC evaluates per-specialization:
  Encoder spec (feature_len=5000): src_len=625, pad_size=0 → no-op
  Decode spec  (feature_len=1):    src_len=1,   pad_size=624 → padded

Zero-padding is safe in Decode spec because the padded tail only feeds
the "compute" ScatterND branch, which is discarded by the torch.where
that selects the cache in the Decode spec.

Also fix get_dummy_inputs and get_specializations: encoder_ctx_len is the
encoder OUTPUT length (625), not the INPUT length (5000).  Divide
max_position_embeddings by subsampling_factor to obtain the correct value.

qaic-compile verified: exit 0, no shape errors, QPC produced:
  CohereAsrForConditionalGeneration-79337560840ae837 / qpc-978d5867ca1d3677

Add examples/audio/cohere_asr_hw_parity.py: hardware parity validation
script that compares HF reference transcriptions with QPC output.
Run with sudo or qaic group membership to perform on-device inference.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Mohammad Abusaa <mabusaa@qrc706r1-dl385-08.qualcomm.com>
HF fp32 == QPC fp16 token-for-token confirmed (100 new tokens, 3/3
LibriSpeech samples) on 4 QAIC AI100 devices.

Key fixes in cohere_asr_hw_parity.py:
- Pad input_features to feature_len=5000 for both HF and QPC so encoder
  context is identical (real audio is ~586 frames; QPC Encode spec requires 5000)
- Feed the full 10-token processor decoder prefix as forced context through
  the Encode + 9 Decode steps before greedy generation (QEFFAutoModel only
  seeds with decoder_start_token_id; without the prefix the model diverges)
- Backfill decoder_start_token_id from generation_config.json (absent on
  CohereAsrConfig; lives only in generation_config.json)
- Use token-for-token comparison up to min(hf_tokens, qpc_tokens) since
  EOS is not emitted at fp16 (logit margin collapses at the sentence boundary)
- Add --token-file argument for safe credential passing

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Mohammad Abusaa <mabusaa@qrc706r1-dl385-08.qualcomm.com>
Adds TINY_COHERE_ASR_MODEL_ID constant and test_cohere_asr_export_smoke
modeled on test_whisper_export_smoke. Uses torch_dtype=torch.float32
(CohereAsr loads bfloat16 by default; fp32 required for export). Reuses
_run_whisper_export_smoke helper which exports and asserts RetainedState
outputs are present — covering the cross-attention retained-state wiring
fixed in commit 9a4becf. Rule 11 compliance (CLAUDE.md): one canonical
test entry, no new per-architecture test file.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Mohammad Abusaa <mabusaa@qrc706r1-dl385-08.qualcomm.com>
examples/audio/cohere_asr_infer.py: standalone inference on any wav/mp3,
defaults to the official demo/voxpopuli_test_en_demo.wav from the model repo.
No HF model load — QPC only, starts in ~14s on 4 QAIC AI100 devices.
Reports transcription text, new-token count, and decode tok/s.
Includes repetition_penalty support (default 1.3) to suppress fp16 EOS loop.

Confirmed output on voxpopuli_test_en_demo.wav:
  "If not, there will be a big crisis between you and the European Parliament."
  282 tok/s (penalty=1.3), 329 tok/s raw. Verified by listening to the clip.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Mohammad Abusaa <mabusaa@qrc706r1-dl385-08.qualcomm.com>
@quic-rishinr
quic-rishinr requested a review from vbaddi August 4, 2026 06:32
@ochougul
ochougul marked this pull request as draft August 4, 2026 16:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant