-
Notifications
You must be signed in to change notification settings - Fork 647
Add AST (Audio Spectrogram Transformer) Adapter #1484
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
43d9ff4
3b9db10
2820b13
3a2a164
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import torch | ||
| from transformers import ASTConfig, ASTForAudioClassification | ||
|
|
||
| from transformer_lens.model_bridge import TransformerBridge | ||
|
|
||
|
|
||
| def test_ast_parity(): | ||
| # 1. setup a tiny HF AST config for instantaneous local testing | ||
| hf_config = ASTConfig( | ||
| hidden_size=12, | ||
| num_hidden_layers=2, | ||
| num_attention_heads=2, | ||
| intermediate_size=24, | ||
| patch_size=16, | ||
| ) | ||
|
|
||
| # explicitly define the architecture list for the local config | ||
| hf_config.architectures = ["ASTForAudioClassification"] | ||
|
|
||
| hf_model = ASTForAudioClassification(hf_config) | ||
| hf_model.eval() | ||
|
|
||
| # 2. boot the v3 transformerbridge natively | ||
| # bridge auotmatically detecty ASTForAudioClassification and use new ASTArchitectureAdapater | ||
| tl_model = TransformerBridge.boot_transformers("ast", hf_model=hf_model) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because the adapter sets |
||
| tl_model.eval() | ||
|
|
||
| # 3. create dummy spectrogram input [batch, freq, time] | ||
| dummy_spectrogram = torch.randn(1, 1024, 128) | ||
|
|
||
| # 4. compare forward passes | ||
| with torch.no_grad(): | ||
| hf_logits = hf_model(dummy_spectrogram).logits | ||
|
|
||
| # bridge forward pass (extract sequence before HF's pooling) | ||
| _, cache = tl_model.run_with_cache(dummy_spectrogram) | ||
| resid = cache["ln_final.hook_normalized"] | ||
|
|
||
| # apply AST pooling logic to the bridges residual stream | ||
| cls_token = resid[:, 0, :] | ||
| dist_token = resid[:, 1, :] | ||
| pooled_out = (cls_token + dist_token) / 2.0 | ||
|
|
||
| # apply the final classifier (which holds 2nd layernorm + dense) | ||
| 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}") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Drop the |
||
|
|
||
| # 5. assert parity | ||
| assert diff < 1e-4, "Parity failed: Bridge tensors do not match HuggingFace." | ||
| print("Parity dub. V3 Bridge connected excellent") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| test_ast_parity() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| from typing import Any | ||
|
|
||
| from transformer_lens.conversion_utils.conversion_steps import RearrangeTensorConversion | ||
| from transformer_lens.conversion_utils.param_processing_conversion import ( | ||
| ParamProcessingConversion, | ||
| ) | ||
| from transformer_lens.model_bridge.architecture_adapter import ArchitectureAdapter | ||
| from transformer_lens.model_bridge.generalized_components import ( | ||
| AttentionBridge, | ||
| BlockBridge, | ||
| LinearBridge, | ||
| MLPBridge, | ||
| NormalizationBridge, | ||
| UnembeddingBridge, | ||
| ) | ||
| from transformer_lens.model_bridge.generalized_components.base import ( | ||
| GeneralizedComponent, | ||
| ) | ||
|
|
||
|
|
||
| class ASTArchitectureAdapter(ArchitectureAdapter): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the only adapter class in It also needs the encoder-only class attribute: Without it, AST inherits |
||
| def __init__(self, cfg: Any) -> None: | ||
| super().__init__(cfg) | ||
|
|
||
| # essential flag for audio models in V3 | ||
| self.cfg.is_audio_model = True | ||
| self.cfg.normalization_type = "LN" | ||
|
|
||
| n_heads = self.cfg.n_heads | ||
|
|
||
| # Q/K/V/O rearrangement: splits hidden dims into (heads, head_dim) | ||
| self.weight_processing_conversions = { | ||
| "blocks.{i}.attn.q.weight": ParamProcessingConversion( | ||
| tensor_conversion=RearrangeTensorConversion( | ||
| "(h d_head) d_model -> h d_model d_head", h=n_heads | ||
| ), | ||
| ), | ||
| "blocks.{i}.attn.k.weight": ParamProcessingConversion( | ||
| tensor_conversion=RearrangeTensorConversion( | ||
| "(h d_head) d_model -> h d_model d_head", h=n_heads | ||
| ), | ||
| ), | ||
| "blocks.{i}.attn.v.weight": ParamProcessingConversion( | ||
| tensor_conversion=RearrangeTensorConversion( | ||
| "(h d_head) d_model -> h d_model d_head", h=n_heads | ||
| ), | ||
| ), | ||
| "blocks.{i}.attn.q.bias": ParamProcessingConversion( | ||
| tensor_conversion=RearrangeTensorConversion("(h d_head) -> h d_head", h=n_heads), | ||
| ), | ||
| "blocks.{i}.attn.k.bias": ParamProcessingConversion( | ||
| tensor_conversion=RearrangeTensorConversion("(h d_head) -> h d_head", h=n_heads), | ||
| ), | ||
| "blocks.{i}.attn.v.bias": ParamProcessingConversion( | ||
| tensor_conversion=RearrangeTensorConversion("(h d_head) -> h d_head", h=n_heads), | ||
| ), | ||
| "blocks.{i}.attn.o.weight": ParamProcessingConversion( | ||
| tensor_conversion=RearrangeTensorConversion( | ||
| "d_model (h d_head) -> h d_head d_model", h=n_heads | ||
| ), | ||
| ), | ||
| } | ||
|
|
||
| # V3 bridge pattern: hierarchical mapping using bridge components | ||
| self.component_mapping = { | ||
| "embed": GeneralizedComponent(name="audio_spectrogram_transformer.embeddings"), | ||
| "ln_final": NormalizationBridge( | ||
| name="audio_spectrogram_transformer.layernorm", config=self.cfg | ||
| ), | ||
| "unembed": UnembeddingBridge(name="classifier.dense"), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. HuggingFace's classification path after
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:
|
||
| "blocks": BlockBridge( | ||
| name="audio_spectrogram_transformer.layers", | ||
| submodules={ | ||
| "ln1": NormalizationBridge(name="layernorm_before", config=self.cfg), | ||
| "ln2": NormalizationBridge(name="layernorm_after", config=self.cfg), | ||
| "attn": AttentionBridge( | ||
| name="attention", | ||
| config=self.cfg, | ||
| submodules={ | ||
| "q": LinearBridge(name="q_proj"), | ||
| "k": LinearBridge(name="k_proj"), | ||
| "v": LinearBridge(name="v_proj"), | ||
| "o": LinearBridge(name="o_proj"), | ||
| }, | ||
| ), | ||
| "mlp": MLPBridge( | ||
| name="mlp", | ||
| config=self.cfg, | ||
| submodules={ | ||
| "in": LinearBridge(name="fc1"), | ||
| "out": LinearBridge(name="fc2"), | ||
| }, | ||
| ), | ||
| }, | ||
| ), | ||
| } | ||
|
|
||
| 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 = ( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The dynamic Please make this and the component_mapping prefix-aware following |
||
| hf_model.audio_spectrogram_transformer.embeddings.position_embeddings.shape[1] | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -48,6 +48,7 @@ | |
| "AfmoeForCausalLM", | ||
| "ApertusForCausalLM", | ||
| "ArceeForCausalLM", | ||
| "ASTForAudioClassification", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are a couple other locations you'll need to register this:
|
||
| "BaiChuanForCausalLM", | ||
| "BaichuanForCausalLM", | ||
| "BambaForCausalLM", | ||
|
|
@@ -200,6 +201,7 @@ | |
| "AfmoeForCausalLM": ["arcee-ai"], | ||
| "ApertusForCausalLM": ["swiss-ai"], | ||
| "ArceeForCausalLM": ["arcee-ai"], | ||
| "ASTForAudioClassification": ["MIT"], | ||
| "BaiChuanForCausalLM": ["baichuan-inc"], | ||
| "BaichuanForCausalLM": ["baichuan-inc"], | ||
| "BambaForCausalLM": ["ibm-ai-platform"], | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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!