Add AST (Audio Spectrogram Transformer) Adapter - #1484
Conversation
|
Thanks for putting this together @dylanberens! The AST domain work is great, the Q/K/V/O and bias reshapes, MLP transposes, the overlapping-patch Conv2d, the cls-distillation-patches token order, and the pooled dual-token + dual-LayerNorm head are all correct. We will need to address & adjust the approach: this is written against the legacy HookedTransformer Primary issues with the current code:
To make this mergeable:
Also, a couple repo items: make sure to drop Happy to re-review once it's on the TransformerBridge pattern. |
bc7afb1 to
3b9db10
Compare
|
thanks @jlarson4 for the feedback and roadmap! I've refactored the AST adapter to follow the V3 TransformerBridge pattern against dev
ready for re-review when you get a chance, thanks jonah! |
…or in model registry
|
Excellent, thank you @dylanberens! I will make sure to let you know when I get around to reviewing |
jlarson4
left a comment
There was a problem hiding this comment.
Hey @dylanberens sorry it took me so long to get around to reviewing this. Full detailed review broken down below. The overall structure and content of your adapter are very good! There are just a couple small items that need addressing before I can merge. Once the comments below are resolved, this should be good to go!
| ) | ||
|
|
||
|
|
||
| class ASTArchitectureAdapter(ArchitectureAdapter): |
There was a problem hiding this comment.
This is the only adapter class in supported_architectures/) without a docstring, and the file also lacks a module docstring. AST has enough quirks that it would really benefit from one: the spectrogram input contract, the cls+distillation token layout, and especially the head structure.
It also needs the encoder-only class attribute:
class ASTArchitectureAdapter(ArchitectureAdapter):
"""Architecture adapter for AST (Audio Spectrogram Transformer) audio classifiers.
Input is a [batch, time=1024, n_mels=128] spectrogram; positions 0/1 are the
CLS and distillation tokens. HF pools (cls+dist)/2 after ln_final and applies
an extra LayerNorm inside the classifier head, see the unembed mapping note.
"""
supports_generation: bool = False
Without it, AST inherits True and every generation entry point will fail with unclear errors instead of the clean NotImplementedError gate at bridge.py:2721. I tested this and received AssertionError: Must pass eos_token_id... from generate(spectrogram), and IndexError at bridge.py:2631 with stop_at_eos=False.
| "ln_final": NormalizationBridge( | ||
| name="audio_spectrogram_transformer.layernorm", config=self.cfg | ||
| ), | ||
| "unembed": UnembeddingBridge(name="classifier.dense"), |
There was a problem hiding this comment.
HuggingFace's classification path after ln_final is: pool (seq[:,0]+seq[:,1])/2 -> classifier.layernorm -> classifier.dense. Mapping unembed to classifier.dense alone means:
unembed.hook_inis the pooled, post-head-LayerNorm[batch, d_model]tensor. There are two unhooked ops downstream ofln_final.hook_out[batch, seq, d_model]. On every text model, unembed input is theln_finaloutput. Users expecting that convention get silently different semantics here.- The pooling step and
classifier.layernormhave no hook points at all, neither is observable or intervenable.
The actual results are unaffected, so I'm fine shipping this mapping if documented in the class docstring, but consider mapping the head LN as a component (or the whole classifier) so the head is hookable.
Either way, a couple related small bugs:
ASTConfig(num_labels=0)makesclassifier.denseannn.Identity, and boot then dies withAssertionError: Component classifier.dense has no weight attribute (bridge.py:667). Guard it inprepare_model(drop the unembed mapping, like HuBERT's conditional unembed injection) or raise a clear unsupported-config error.cfg.d_vocab/d_vocab_outstay at the -1 sentinel after boot even thoughW_Uis a real(d_model, num_labels)head. Setself.cfg.d_vocab_out(and arguablyd_vocab) fromnum_labelsso downstream consumers don't read -1.
| def prepare_model(self, hf_model: Any) -> None: | ||
| # hook to access the live Huggingface model before boot | ||
| # calculate n_ctx dynamically from the instantiated position embeddings | ||
| self.cfg.n_ctx = ( |
There was a problem hiding this comment.
The dynamic n_ctx is the right call, but the hardcoded hf_model.audio_spectrogram_transformer. access is one of the two things that breaks real boots: a bare ASTModel has no such attribute.
Please make this and the component_mapping prefix-aware following hubert.py's _build_component_mapping(prefix=...) pattern. It solves the bare-encoder case and a real boot blocker. This is also the natural place for the num_labels -> d_vocab_out propagation mentioned above.
| from transformer_lens.model_bridge import TransformerBridge | ||
|
|
||
|
|
||
| def test_ast_parity(): |
There was a problem hiding this comment.
We could use a bit more coverage from the testing. We have a guide on what we typically expect from adapter tests here. Let me know if you have any questions!
|
|
||
| # 2. boot the v3 transformerbridge natively | ||
| # bridge auotmatically detecty ASTForAudioClassification and use new ASTArchitectureAdapater | ||
| tl_model = TransformerBridge.boot_transformers("ast", hf_model=hf_model) |
There was a problem hiding this comment.
Because the adapter sets is_audio_model=True, boot reaches the processor block (sources/transformers.py:977-989) and calls AutoFeatureExtractor.from_pretrained("ast"). This attempts to find a HF repo literally named ast on every unit-test run. If anyone ever publishes a repo named ast with a preprocessor_config.json, this test silently loads third-party content. Use an unmistakably-local placeholder and/or monkeypatch the processor load.
| tl_logits = hf_model.classifier(pooled_out) | ||
|
|
||
| diff = (hf_logits - tl_logits).abs().max().item() | ||
| print(f"Bridge vs HF Max Logit Diff: {diff:.6e}") |
There was a problem hiding this comment.
Drop the print()s (pytest reporting covers it) and the `if name == "main": block. The TransformerLens standard are assertions with messages.
| HF_SUPPORTED_ARCHITECTURES: set[str] = { | ||
| "ApertusForCausalLM", | ||
| "ArceeForCausalLM", | ||
| "ASTForAudioClassification", |
There was a problem hiding this comment.
There are a couple other locations you'll need to register this:
AUDIO_ARCHITECTURESintransformer_lens/utilities/architectures.pyneeds "ASTForAudioClassification". This is routesverify_modelsto the audio phase set{1, 8}instead of text phases (see review body) when running benchmarks to confirm individual model support.ARCHITECTURE_DESCRIPTIONSingenerate_report.pythis is a nice-to-have, not essential for PR merge
Description
Refactored the AST architecture adapter to align with the V3 'TransformerBridge' architecture and target 'dev'.
Key Changes
Fixes # 1484
Type of change
Checklist: