@@ -77,9 +77,15 @@ def variable_to_logically_partitioned(variable: nnx.VariableState):
7777 Returns:
7878 The variable's value, potentially wrapped in `nn.LogicallyPartitioned`.
7979 """
80+
8081 if isinstance (variable .value , aqt_tensor .QTensor ):
8182 return variable .value
8283
84+ # If the value is already explicitly partitioned (e.g. from nnx_pipeline),
85+ # return it as-is.
86+ if isinstance (variable .value , nn .spmd .LogicallyPartitioned ):
87+ return variable .value
88+
8389 if variable .type .__name__ == "_overwrite_with_gradient" :
8490 return variable .value
8591
@@ -89,6 +95,27 @@ def variable_to_logically_partitioned(variable: nnx.VariableState):
8995 sharding_names = metadata ["sharding_names" ]
9096 else :
9197 sharding_names = metadata ["sharding" ]
98+
99+ # Auto-Patching for Pipeline Expansion
100+ # If the value rank is greater than the sharding rank, it implies pipeline expansion
101+ # occurred (broadcasting), but the metadata is stale. We patch the spec here.
102+
103+ val = variable .value
104+
105+ if hasattr (val , "ndim" ) and isinstance (sharding_names , tuple ):
106+ val_rank = val .ndim
107+ spec_rank = len (sharding_names )
108+ diff = val_rank - spec_rank
109+
110+ if diff > 0 :
111+ # Prepend axes based on rank difference
112+ # Diff 2: [Repeats, Stage, ...] -> ('circular_repeats', 'layers', ...)
113+ # Diff 1: [Stage, ...] -> ('layers', ...)
114+ if diff == 2 :
115+ sharding_names = ("circular_repeats" , "layers" ) + sharding_names
116+ elif diff == 1 :
117+ sharding_names = ("layers" ,) + sharding_names
118+
92119 return nn .LogicallyPartitioned ( # type: ignore[wrong-keyword-args]
93120 variable .value ,
94121 sharding_names , # type: ignore[arg-type]
0 commit comments