2121
2222import pytest
2323from megatron .core .models .common .language_module .language_module import LanguageModule
24+ from megatron .core .transformer import attention as attention_module
2425from megatron .core .transformer import utils as transformer_utils
2526from megatron .core .transformer .enums import AttnBackend
2627
3334
3435CONFIG_TYPES = (TransformerConfig , MLATransformerConfig , HeterogeneousTransformerConfig )
3536BACKEND_ENV = ("NVTE_FLASH_ATTN" , "NVTE_FUSED_ATTN" , "NVTE_UNFUSED_ATTN" )
37+ FLASH_VERSION_ENV = ("NVTE_FLASH_ATTN_V2" , "NVTE_FLASH_ATTN_V3" , "NVTE_FLASH_ATTN_V4" )
3638pytestmark = pytest .mark .unit
3739
3840
3941@pytest .fixture (autouse = True )
4042def isolated_attention_environment ():
4143 with patch .dict (os .environ ):
42- for name in BACKEND_ENV :
44+ for name in ( * BACKEND_ENV , * FLASH_VERSION_ENV ) :
4345 os .environ .pop (name , None )
4446 yield
4547
4648
47- def make_config (config_type , backend ):
49+ def make_config (config_type , backend , ** overrides ):
4850 kwargs = dict (num_layers = 2 , hidden_size = 64 , num_attention_heads = 4 , attention_backend = backend )
51+ kwargs .update (overrides )
4952 if config_type is HeterogeneousTransformerConfig :
5053 block = {
5154 "attention" : {"no_op" : False , "replace_with_linear" : False , "num_query_groups" : 4 },
@@ -63,6 +66,63 @@ def configure_mcore_attention(config):
6366 LanguageModule ._set_attention_backend (SimpleNamespace (config = config ))
6467
6568
69+ @pytest .mark .parametrize ("config_type" , CONFIG_TYPES )
70+ @pytest .mark .parametrize ("backend" , list (AttnBackend ))
71+ @pytest .mark .skipif (
72+ not hasattr (attention_module .Attention , "_resolve_flash_version" ),
73+ reason = "The frozen MCore dev pin predates FlashAttention version controls" ,
74+ )
75+ def test_explicit_fa2_disables_fa4_without_changing_backend (config_type , backend , monkeypatch ):
76+ monkeypatch .setenv ("NVTE_FLASH_ATTN_V4" , "0" )
77+ config = make_config (config_type , backend , flash_attention_version = 2 )
78+
79+ config .finalize ()
80+ configure_mcore_attention (config )
81+
82+ assert config .flash_attention_version == 2
83+ assert config .attention_backend is backend
84+ assert tuple (os .environ [name ] for name in FLASH_VERSION_ENV ) == ("1" , "0" , "0" )
85+
86+
87+ @pytest .mark .parametrize ("config_type" , CONFIG_TYPES )
88+ @pytest .mark .skipif (
89+ not hasattr (attention_module .Attention , "_resolve_flash_version" ),
90+ reason = "The frozen MCore dev pin predates FlashAttention version controls" ,
91+ )
92+ def test_direct_attention_uses_explicit_fa2_even_when_fa4_is_installed (config_type , monkeypatch ):
93+ monkeypatch .setattr (attention_module , "HAVE_FA4" , True )
94+ monkeypatch .setattr (attention_module , "HAVE_FA3" , True )
95+ config = make_config (config_type , AttnBackend .auto , flash_attention_version = 2 )
96+ config .finalize ()
97+
98+ attention = SimpleNamespace (flash_attention_version = config .flash_attention_version )
99+ assert attention_module .Attention ._resolve_flash_version (attention ) == (False , False )
100+
101+
102+ @pytest .mark .parametrize ("config_type" , CONFIG_TYPES )
103+ @pytest .mark .parametrize ("version" , [None , 2 , 3 , 4 ])
104+ @pytest .mark .skipif (
105+ not hasattr (attention_module .Attention , "_resolve_flash_version" ),
106+ reason = "The frozen MCore dev pin predates FlashAttention version controls" ,
107+ )
108+ def test_explicit_flash_version_is_preserved (config_type , version ):
109+ config = make_config (config_type , AttnBackend .auto , flash_attention_version = version )
110+
111+ config .finalize ()
112+
113+ assert config .flash_attention_version == version
114+
115+
116+ @pytest .mark .parametrize ("config_type" , CONFIG_TYPES )
117+ def test_default_flash_version_is_inherited_from_mcore (config_type ):
118+ config = make_config (config_type , AttnBackend .auto )
119+
120+ config .finalize ()
121+
122+ assert getattr (config , "flash_attention_version" , None ) is None
123+ assert all (name not in os .environ for name in FLASH_VERSION_ENV )
124+
125+
66126@pytest .mark .parametrize ("config_type" , CONFIG_TYPES )
67127def test_unset_backend_finalizes_to_mcore_auto (config_type ):
68128 config = make_config (config_type , None )
0 commit comments