@@ -1763,3 +1763,87 @@ def test_producer_matches_the_verified_fixture_layout(self):
17631763 assert maps [pattern ] == want , (f"emitted map for { pattern } differs from the layout the resume "
17641764 f"fixture verifies:\n emitted { maps [pattern ]} \n expected { want } " )
17651765 engine .destroy ()
1766+
1767+
1768+ class AffineCoverageModel (torch .nn .Module ):
1769+ """Covers each conversion category: column split, row split, replicated, untouched."""
1770+
1771+ def __init__ (self , hidden_dim = 16 , vocab_size = 26 ):
1772+ super ().__init__ ()
1773+ self .embed = torch .nn .Embedding (vocab_size , hidden_dim ) # AutoTP leaves this alone
1774+ self .norm = torch .nn .LayerNorm (hidden_dim ) # untouched, and not 2-D
1775+ self .fc1 = torch .nn .Linear (hidden_dim , hidden_dim ) # column
1776+ self .fc2 = torch .nn .Linear (hidden_dim , hidden_dim ) # row
1777+ self .lm_head = torch .nn .Linear (hidden_dim , vocab_size ) # vocabulary
1778+
1779+ def forward (self , x ):
1780+ h = self .norm (self .embed (x ))
1781+ return self .lm_head (self .fc2 (self .fc1 (h ))).sum ()
1782+
1783+
1784+ class TestAffineMapCoverage (DistributedTest ):
1785+ """Every parameter the converter can place must carry an affine map.
1786+
1787+ A parameter with no map falls back to its name category, which is the behaviour the IR
1788+ exists to replace. The only parameters allowed to have no map are the ones AutoTP itself
1789+ refuses to describe, which conversion rejects anyway.
1790+ """
1791+
1792+ world_size = 2
1793+
1794+ def test_every_convertible_parameter_has_a_map (self ):
1795+ from deepspeed .checkpoint .constants import (AFFINE_MAP , AFFINE_MAP_PARAMS ,
1796+ AUTOTP_UNSUPPORTED_PARAMETER_PATTERNS ,
1797+ PARAMETER_WITH_ROW_PARALLELISM_PATTERNS , PARAMETER_WITH_SUB_PARAMS ,
1798+ TP_REPLICATED_PARAMETER_PATTERNS , VOCABULARY_PARAMETER_PATTERNS )
1799+ from deepspeed .module_inject .layers import collect_autotp_universal_checkpoint_info
1800+
1801+ config = {
1802+ "train_micro_batch_size_per_gpu" : 1 ,
1803+ "zero_allow_untested_optimizer" : True ,
1804+ "zero_optimization" : {
1805+ "stage" : 1
1806+ },
1807+ "tensor_parallel" : {
1808+ "autotp_size" : self .world_size ,
1809+ "partition_config" : {
1810+ "use_default_specs" :
1811+ False ,
1812+ "layer_specs" : [
1813+ {
1814+ "patterns" : [r".*fc1\.weight$" ],
1815+ "partition_type" : "column"
1816+ },
1817+ {
1818+ "patterns" : [r".*fc2\.weight$" ],
1819+ "partition_type" : "row"
1820+ },
1821+ {
1822+ "patterns" : [r".*lm_head\.weight$" ],
1823+ "partition_type" : "column" ,
1824+ "gather_output" : True
1825+ },
1826+ ],
1827+ },
1828+ },
1829+ }
1830+ model = AffineCoverageModel ()
1831+ optimizer = torch .optim .Adam (model .parameters (), lr = 1e-3 )
1832+ engine , _ , _ , _ = deepspeed .initialize (model = model , optimizer = optimizer , config = config )
1833+ info = collect_autotp_universal_checkpoint_info (engine .module )
1834+
1835+ mapped = set (info .get (AFFINE_MAP , {}).get (AFFINE_MAP_PARAMS , {}))
1836+ unsupported = set (info .get (AUTOTP_UNSUPPORTED_PARAMETER_PATTERNS , {}))
1837+
1838+ categorised = set ()
1839+ for key in (TP_REPLICATED_PARAMETER_PATTERNS , PARAMETER_WITH_ROW_PARALLELISM_PATTERNS ,
1840+ VOCABULARY_PARAMETER_PATTERNS ):
1841+ categorised .update (info .get (key , []))
1842+ for entry in info .get (PARAMETER_WITH_SUB_PARAMS , []):
1843+ categorised .update (entry ["patterns" ])
1844+
1845+ assert categorised , "model exercised no conversion category, so this proves nothing"
1846+ missing = categorised - mapped - unsupported
1847+ assert not missing , (f"these parameters are placed by a name category but carry no affine map, "
1848+ f"so conversion still depends on the category: { sorted (missing )} " )
1849+ engine .destroy ()
0 commit comments