2828import jax .numpy as jnp
2929from orbax import checkpoint as ocp
3030import qwix
31+ from qwix ._src .core .qarray import QArray
32+ from qwix ._src .providers .ptq import WithAux
3133
3234from maxtext .common import checkpointing
3335from maxtext .configs import pyconfig
@@ -584,7 +586,7 @@ def apply_lora_to_model(
584586 max_logging .log ("MaxText LoRA adapters loaded, skipping Qwix LoRA application" )
585587 return model
586588
587- if not mt_config . lora . enable_lora :
589+ if not getattr ( getattr ( mt_config , " lora" , None ), " enable_lora" , False ) :
588590 return model
589591
590592 # Dynamically detect and set LoRA rank before model creation if restoring
@@ -607,37 +609,36 @@ def apply_lora_to_model(
607609 )
608610
609611 if mesh is not None :
610- with jax .set_mesh (mesh ), nn_partitioning .axis_rules (mt_config .logical_axis_rules ):
611- graph_def , state = nnx .split (lora_model )
612-
613- # We handle explicit replication for LoRA to ensure safety and efficiency.
614- state = jax .tree_util .tree_map (
615- lambda x : x .replace (sharding = jax .sharding .PartitionSpec (), out_sharding = None , sharding_names = None )
616- if isinstance (x , nnx .LoRAParam )
617- else x ,
618- state ,
619- is_leaf = lambda x : isinstance (x , nnx .Variable ),
620- )
612+ graph_def , state = nnx .split (lora_model )
613+
614+ # We handle explicit replication for LoRA to ensure safety and efficiency.
615+ state = jax .tree_util .tree_map (
616+ lambda x : x .replace (sharding = jax .sharding .PartitionSpec (), out_sharding = None , sharding_names = None )
617+ if isinstance (x , nnx .LoRAParam )
618+ else x ,
619+ state ,
620+ is_leaf = lambda x : isinstance (x , nnx .Variable ),
621+ )
621622
622- # Use logical_to_mesh_sharding to correctly map logical axes like 'embed'
623- # to physical mesh axes.
624- dst_shardings = nn .logical_to_mesh_sharding (nnx .get_partition_spec (state ), mesh , mt_config .logical_axis_rules )
623+ # Use logical_to_mesh_sharding to correctly map logical axes like 'embed'
624+ # to physical mesh axes.
625+ dst_shardings = nn .logical_to_mesh_sharding (nnx .get_partition_spec (state ), mesh , mt_config .logical_axis_rules )
625626
626- def _safe_reshard (var , sharding_spec ):
627- if not isinstance (var , nnx .Variable ) or not isinstance (sharding_spec , jax .sharding .Sharding ):
628- return var
629- val = var .get_value ()
630- if not isinstance (val , jax .Array ):
631- return var
632- # make_array_from_callback natively constructs a globally sharded array
633- # from the local host arrays, bypassing backend-specific device_put issues
634- # on both Pathways and McJAX.
635- resharded_val = jax .make_array_from_callback (val .shape , sharding_spec , lambda idx : val [idx ])
636- return var .replace (value = resharded_val )
627+ def _safe_reshard (var , sharding_spec ):
628+ if not isinstance (var , nnx .Variable ) or not isinstance (sharding_spec , jax .sharding .Sharding ):
629+ return var
630+ val = var .get_value ()
631+ if not isinstance (val , jax .Array ):
632+ return var
633+ # make_array_from_callback natively constructs a globally sharded array
634+ # from the local host arrays, bypassing backend-specific device_put issues
635+ # on both Pathways and McJAX.
636+ resharded_val = jax .make_array_from_callback (val .shape , sharding_spec , lambda idx : val [idx ])
637+ return var .replace (value = resharded_val )
637638
638- state = jax .tree_util .tree_map (_safe_reshard , state , dst_shardings , is_leaf = lambda x : isinstance (x , nnx .Variable ))
639+ state = jax .tree_util .tree_map (_safe_reshard , state , dst_shardings , is_leaf = lambda x : isinstance (x , nnx .Variable ))
639640
640- lora_model = nnx .merge (graph_def , state )
641+ lora_model = nnx .merge (graph_def , state )
641642
642643 _verify_lora_parameters (lora_model , mt_config )
643644
@@ -666,7 +667,7 @@ def restore_lora_from_path(model: nnx.Module, mt_config: pyconfig.HyperParameter
666667
667668 if not is_lora_enabled (model ):
668669 lora_module_path = _get_lora_module_path (mt_config )
669- if not mt_config . lora . enable_lora :
670+ if not getattr ( getattr ( mt_config , " lora" , None ), " enable_lora" , False ) :
670671 raise ValueError (
671672 "lora_restore_path is set but LoRA is not enabled on the model. "
672673 f"Set lora.enable_lora=True and verify lora_module_path ('{ lora_module_path } ') matches model modules."
@@ -699,37 +700,54 @@ def restore_lora_from_path(model: nnx.Module, mt_config: pyconfig.HyperParameter
699700 max_logging .log (f"Guided restore failed: { e } . Falling back to basic restore." )
700701 restored_lora_params = ocp .PyTreeCheckpointer ().restore (lora_restore_path )
701702
703+ # If restoring from a full TrainState checkpoint, navigate into the model sub-tree
704+ if isinstance (restored_lora_params , dict ) and "model" in restored_lora_params :
705+ restored_lora_params = restored_lora_params ["model" ]
706+ elif hasattr (restored_lora_params , "model" ):
707+ restored_lora_params = getattr (restored_lora_params , "model" )
708+ restored_count = 0
709+
702710 # Post processing
703711 def _map_to_state (path , variable ):
712+ nonlocal restored_count
704713 if not isinstance (variable , nnx .Variable ):
705714 return
706715
707716 str_path = [str (k .key if hasattr (k , "key" ) else (k .name if hasattr (k , "name" ) else k )) for k in path ]
708717
709718 curr = restored_lora_params
710719 for p in str_path :
711- if isinstance (curr , dict ) and p in curr :
712- curr = curr [p ]
720+ if isinstance (curr , Mapping ):
721+ if p in curr :
722+ curr = curr [p ]
723+ elif p .isdigit () and int (p ) in curr :
724+ curr = curr [int (p )]
725+ else :
726+ return
713727 elif hasattr (curr , p ):
714728 curr = getattr (curr , p )
715729 else :
716730 return
717731
718- if isinstance (curr , dict ) and "value" in curr :
732+ if isinstance (curr , Mapping ) and "value" in curr :
719733 matched_val = curr ["value" ]
720734 elif hasattr (curr , "value" ):
721735 matched_val = getattr (curr , "value" )
722736 else :
723737 matched_val = curr
724738
725739 variable .value = matched_val
740+ restored_count += 1
726741
727742 jax .tree_util .tree_map_with_path (
728743 _map_to_state ,
729744 abstract_lora_params ,
730745 is_leaf = lambda n : isinstance (n , nnx .Variable ),
731746 )
732747
748+ if restored_count == 0 :
749+ raise ValueError (f"No LoRA/adapter parameters were successfully restored from checkpoint at '{ lora_restore_path } '." )
750+
733751 nnx .update (model , abstract_lora_params )
734752 max_logging .log (f"LoRA restore complete from '{ lora_restore_path } '." )
735753 return model
@@ -918,3 +936,26 @@ def add_lora(out_node, base_node, path):
918936 opt_state = {},
919937 )
920938 return unboxed_abstract_lora_state , lora_state_mesh_annotations
939+
940+
941+ def restore_qlora_base_weights (val ):
942+ """Restores qwix custom quantized types from nnx.State representation."""
943+ if isinstance (val , nnx .State ):
944+ pure_dict = {k : restore_qlora_base_weights (v ) for k , v in val .items ()}
945+ if "array" in pure_dict :
946+ return WithAux (array = pure_dict ["array" ], how = None )
947+ elif "qvalue" in pure_dict and "scale" in pure_dict :
948+ return QArray (
949+ qvalue = pure_dict ["qvalue" ],
950+ scale = pure_dict ["scale" ],
951+ zero_point = pure_dict .get ("zero_point" , None ),
952+ qtype = None ,
953+ )
954+ else :
955+ return pure_dict
956+ elif isinstance (val , nnx .Variable ):
957+ return restore_qlora_base_weights (val .get_value ())
958+ elif isinstance (val , dict ):
959+ return {k : restore_qlora_base_weights (v ) for k , v in val .items ()}
960+ else :
961+ return val
0 commit comments