Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 22 additions & 26 deletions src/maxtext/layers/quantizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -859,32 +859,28 @@ def maybe_quantize_model(model, config):
if config.use_qwix_quantization and not config.use_batch_split_schedule:
quantization_provider = get_qt_provider(config)
if quantization_provider:
if config.pure_nnx:
input_shape = (config.micro_batch_size_to_train_on, config.max_target_length)
dummy_tokens = jnp.ones(input_shape, dtype=jnp.int32)
dummy_positions = jnp.ones(input_shape, dtype=jnp.int32)
dummy_segment_ids = jnp.ones(input_shape, dtype=jnp.int32)
# The MTP block reads the decoder targets, so the qwix forward pass needs them.
# The Linen path supplies them from the is_initializing() guard in Transformer.
dummy_targets = {}
if config.mtp_num_layers > 0:
dummy_targets["decoder_target_tokens"] = jnp.ones(input_shape, dtype=jnp.int32)
dummy_targets["decoder_target_mask"] = jnp.ones(input_shape, dtype=jnp.int32)
model = qwix.quantize_model(
model,
quantization_provider,
dummy_tokens,
dummy_positions,
dummy_segment_ids,
enable_dropout=False,
**dummy_targets,
)
# Qwix quantization runs a forward pass during tracing, which sows transient nnx.Intermediate variables
# (e.g. max_logits from QK-Clip, MTP losses) into the model. Popping them here prevents structural mismatches
# between the initial setup GraphDef/state_mesh_shardings and the stripped states during train steps.
nnx.pop(model, nnx.Intermediate)
else:
model = qwix.quantize_model(model, quantization_provider)
input_shape = (config.micro_batch_size_to_train_on, config.max_target_length)
dummy_tokens = jnp.ones(input_shape, dtype=jnp.int32)
dummy_positions = jnp.ones(input_shape, dtype=jnp.int32)
dummy_segment_ids = jnp.ones(input_shape, dtype=jnp.int32)
# The MTP block reads the decoder targets, so the qwix forward pass needs them.
dummy_targets = {}
if config.mtp_num_layers > 0:
dummy_targets["decoder_target_tokens"] = jnp.ones(input_shape, dtype=jnp.int32)
dummy_targets["decoder_target_mask"] = jnp.ones(input_shape, dtype=jnp.int32)
model = qwix.quantize_model(
model,
quantization_provider,
dummy_tokens,
dummy_positions,
dummy_segment_ids,
enable_dropout=False,
**dummy_targets,
)
# Qwix quantization runs a forward pass during tracing, which sows transient nnx.Intermediate variables
# (e.g. max_logits from QK-Clip, MTP losses) into the model. Popping them here prevents structural mismatches
# between the initial setup GraphDef/state_mesh_shardings and the stripped states during train steps.
nnx.pop(model, nnx.Intermediate)
return model


Expand Down
63 changes: 5 additions & 58 deletions src/maxtext/utils/maxtext_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,7 @@ def get_functional_train_with_signature(
"""Get the shardings (both state and data) for `train_step`."""
functional_train = functools.partial(train_step, model, config, state_mesh_shardings, params_shardings)
functional_train.__name__ = "train_step" # pyrefly: ignore[missing-attribute]
if config.pure_nnx:
in_shardings = (state_mesh_shardings, data_sharding) # State, batch
else:
in_shardings = (state_mesh_shardings, data_sharding, None) # State, batch, rng
in_shardings = (state_mesh_shardings, data_sharding) # State, batch
out_shardings = (state_mesh_shardings, None) # State, metrics
static_argnums = () # We partial out the static argnums of model and config
donate_argnums = 0 # This is the index of the state - we allow the compiler to make use of this memory.
Expand All @@ -110,10 +107,7 @@ def get_functional_eval_with_signature(eval_step, data_sharding, state_mesh_shar
"""Get the shardings (both state and data) for `eval_step`."""
functional_eval = functools.partial(eval_step, model, config)
functional_eval.__name__ = "eval_step" # pyrefly: ignore[missing-attribute]
if config.pure_nnx:
in_shardings = (state_mesh_shardings, data_sharding) # State, batch (NNX: no rng)
else:
in_shardings = (state_mesh_shardings, data_sharding, None) # State, batch, rng
in_shardings = (state_mesh_shardings, data_sharding) # State, batch
out_shardings = None # metrics
static_argnums = () # We partial out the static argnums of model, config
donate_argnums = () # state will be kept instead of being donated in eval_step
Expand Down Expand Up @@ -254,11 +248,7 @@ def get_train_input_output_trees(func, input_args, input_kwargs):

serialized_compiled = load_serialized_compiled(config.compiled_trainstep_file)
shaped_batch = get_shaped_batch(config)
if config.pure_nnx:
shaped_input_args = (state, shaped_batch)
else:
example_rng = jax.random.PRNGKey(0)
shaped_input_args = (state, shaped_batch, example_rng)
shaped_input_args = (state, shaped_batch)
shaped_input_kwargs = {}
in_tree, out_tree = get_train_input_output_trees(partial_train, shaped_input_args, shaped_input_kwargs)
p_train_step = deserialize_and_load(serialized_compiled, in_tree, out_tree, execution_devices=execution_devices)
Expand Down Expand Up @@ -1794,51 +1784,8 @@ def get_logical_annotations(config, mesh, init_state_fn):


def get_abstract_state(config, mesh, init_state_fn, is_training=True):
"""Get a shaped abstraction of the state (including optimizer)"""
if config.pure_nnx:
return get_abstract_state_nnx(config, mesh, init_state_fn, is_training)

init_state_partial = init_state_fn

with nn_partitioning.axis_rules(config.logical_axis_rules):
abstract_state = jax.eval_shape(init_state_partial)

state_logical_annotations = nn.get_partition_spec(abstract_state)

state_mesh_shardings = nn.logical_to_mesh_sharding(state_logical_annotations, mesh, config.logical_axis_rules)
if is_training and config.shard_optimizer_over_data:
# Add data to sharding for optimizer state
state_mesh_shardings = state_mesh_shardings.replace(
opt_state=jax.tree.map_with_path(
functools.partial(sharding.add_data_to_sharding, mesh),
max_utils.unbox_logicallypartioned(abstract_state).opt_state,
state_mesh_shardings.opt_state,
)
)
if is_training and config.optimizer_memory_host_offload:
opt_state = jax.tree_util.tree_map(lambda x: x.with_memory_kind(kind="pinned_host"), state_mesh_shardings.opt_state)
state_mesh_shardings = state_mesh_shardings.replace(opt_state=opt_state)
if is_training and config.parameter_memory_host_offload:
assert config.param_scan_axis == 0, "You must set the scan axis 0 to enable parameter offloading."

def move(path, x):
max_logging.log(f"max_utils.py: Moving {path} to host")
return x.with_memory_kind(kind="pinned_host")

params = jax.tree_util.tree_map_with_path(move, state_mesh_shardings.params)
state_mesh_shardings = state_mesh_shardings.replace(params=params)

abstract_sharded_state = jax.jit(init_state_partial, in_shardings=None, out_shardings=state_mesh_shardings).eval_shape()

unboxed_abstract_sharded_state = max_utils.unbox_logicallypartioned(abstract_sharded_state)
# Initialization
with jax.set_mesh(mesh), nn_partitioning.axis_rules(config.logical_axis_rules):
state_mesh_annotations = nn.logical_to_mesh(state_logical_annotations)
return (
unboxed_abstract_sharded_state,
state_mesh_annotations,
state_mesh_shardings,
)
"""Get a shaped abstraction of the state (including optimizer)."""
return get_abstract_state_nnx(config, mesh, init_state_fn, is_training)


def get_abstract_state_nnx(config, mesh, nnx_init_trainstate_fn, is_training=True):
Expand Down
7 changes: 2 additions & 5 deletions src/maxtext/utils/model_creation_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -927,11 +927,8 @@ def from_pretrained(
_, _abs_state_for_specs = nnx.split(abstract_model)
specs = nnx.get_partition_spec(_abs_state_for_specs)

if config.pure_nnx:
model = maxtext_utils_nnx.create_nnx_sharded_model(abstract_model, _create_model, mesh=mesh)
# TODO: print debug_sharding info
else:
model = create_nnx_sharded_model_hybrid(config, mesh, devices, model_mode, rng_key)
model = maxtext_utils_nnx.create_nnx_sharded_model(abstract_model, _create_model, mesh=mesh)
# TODO: print debug_sharding info

sharded_state = nnx.state(model)

Expand Down
51 changes: 13 additions & 38 deletions src/maxtext/utils/muon_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@
import jax
from maxtext.configs import pyconfig
from maxtext.utils.globals import MAXTEXT_PKG_DIR
from maxtext.layers import quantizations
from maxtext.models import models
from maxtext.utils import maxtext_utils, model_creation_utils
from optax.contrib._muon import MuonDimensionNumbers as mdn

Expand Down Expand Up @@ -134,26 +132,17 @@ def get_transform_tree(tree, path=()):
def get_muon_weight_dimension_numbers(model, config, verbose=False):
"""Extract muon dimension number from model structure."""

if isinstance(model, nnx.Module):
_, abstract_param, _ = nnx.split(model, nnx.Param, ...)
_, abstract_param, _ = nnx.split(model, nnx.Param, ...)

def apply_transform_nnx(path: Tuple[jax.tree_util.KeyEntry, ...], leaf):
# Convert jax.tree_util.KeyEntry path to Tuple[str, ...]
path_strings = tuple(p.key for p in path if isinstance(p, jax.tree_util.DictKey))
return transform_logic(path_strings)
def apply_transform_nnx(path: Tuple[jax.tree_util.KeyEntry, ...], leaf):
# Convert jax.tree_util.KeyEntry path to Tuple[str, ...]
path_strings = tuple(p.key for p in path if isinstance(p, jax.tree_util.DictKey))
return transform_logic(path_strings)

# NNX abstract_param is an nnx.State (not Linen's dict of LogicallyPartitioned leaves);
# tree_map_with_path round-trips that structure so each Param.value holds the mdn result.
muon_weight_dimension_numbers = jax.tree_util.tree_map_with_path(
apply_transform_nnx, nnx.to_pure_dict(abstract_param)
)
muon_weight_dimension_numbers = nnx.State(muon_weight_dimension_numbers)

else: # Linen
# quickly get param structure without materialization
abstract_param = maxtext_utils.get_abstract_param(model, config)
# get muon dimension number from param
muon_weight_dimension_numbers = get_transform_tree(abstract_param)
# tree_map_with_path handles NNX's PyTree structure; result is an nnx.State with the
# same structure, where each Param's value holds the mdn result.
muon_weight_dimension_numbers = jax.tree_util.tree_map_with_path(apply_transform_nnx, nnx.to_pure_dict(abstract_param))
muon_weight_dimension_numbers = nnx.State(muon_weight_dimension_numbers)

if verbose:
_print_structure_debug(abstract_param, muon_weight_dimension_numbers)
Expand Down Expand Up @@ -185,7 +174,7 @@ def get_leaf_info(leaf):
print("\nIs this reasonable?")


def get_model_mdn(model_name, scan_layers=True, verbose=False, pure_nnx=False):
def get_model_mdn(model_name, scan_layers=True, verbose=False):
"""Initializes a model and retrieves its Muon dimension numbers.

This function sets up the configuration for a given model, initializes the
Expand All @@ -209,30 +198,16 @@ def get_model_mdn(model_name, scan_layers=True, verbose=False, pure_nnx=False):
f"model_name={model_name}",
f"scan_layers={scan_layers}",
"attention=dot_product",
f"pure_nnx={pure_nnx}",
"skip_jax_distributed_system=True",
]
if not pure_nnx:
argv.extend(
[
"enable_nnx=False",
"pure_nnx_decoder=False",
]
)
config = pyconfig.initialize(argv)
# Setup model
devices_array = maxtext_utils.create_device_mesh(config)
mesh = jax.sharding.Mesh(devices_array, config.mesh_axes)
quant = quantizations.configure_quantization(config)
if pure_nnx:
_, model = model_creation_utils.create_nnx_abstract_model(config, mesh)
else:
model = models.transformer_as_linen(config, mesh=mesh, quant=quant)
_, model = model_creation_utils.create_nnx_abstract_model(config, mesh)
# Get dimension number
muon_weight_dimension_numbers = get_muon_weight_dimension_numbers(model, config, verbose=verbose)
if pure_nnx:
muon_weight_dimension_numbers = {"params": nnx.to_pure_dict(muon_weight_dimension_numbers)}
return muon_weight_dimension_numbers
return {"params": nnx.to_pure_dict(muon_weight_dimension_numbers)}


if __name__ == "__main__":
Expand All @@ -241,4 +216,4 @@ def get_model_mdn(model_name, scan_layers=True, verbose=False, pure_nnx=False):
sys.exit(1)
model_name_arg = sys.argv[1]
scan_layers_arg = sys.argv[2].lower() == "true"
get_model_mdn(model_name_arg, scan_layers_arg, verbose=True, pure_nnx=False)
get_model_mdn(model_name_arg, scan_layers_arg, verbose=True)
23 changes: 1 addition & 22 deletions src/maxtext/utils/sharding.py
Original file line number Diff line number Diff line change
Expand Up @@ -557,26 +557,7 @@ def maybe_update_params_sharding_with_opt(config, state_mesh_shardings):
- updated_state_mesh_shardings: State mesh shardings with updated params field
(unchanged if shard_optimizer_over_data is False)
"""
if config.pure_nnx:
return maybe_update_params_sharding_with_opt_nnx(config, state_mesh_shardings)
prev_params_shardings = state_mesh_shardings.params
if config.shard_optimizer_over_data:
if isinstance(state_mesh_shardings.opt_state, optax.ScaleByAdamState):
sharded_fp32_params = state_mesh_shardings.opt_state.mu
elif isinstance(state_mesh_shardings.opt_state, tuple) and isinstance(
state_mesh_shardings.opt_state[0], optax.ScaleByAdamState
):
sharded_fp32_params = state_mesh_shardings.opt_state[0].mu
else:
raise NotImplementedError(f"Could not find optimizer state shardings from {type(state_mesh_shardings.opt_state)}")
if "params" not in sharded_fp32_params.keys():
# When quantization=fp8 is enabled the sharded_fp32_params
# are not wrapped in `params`. Here we wrap them back.
sharded_fp32_params = {"params": sharded_fp32_params}
state_mesh_shardings = state_mesh_shardings.replace(
params=dict(prev_params_shardings, **sharded_fp32_params)
) # pyrefly: ignore[bad-unpacking]
return prev_params_shardings, state_mesh_shardings
return maybe_update_params_sharding_with_opt_nnx(config, state_mesh_shardings)


def maybe_update_params_sharding_with_opt_nnx(
Expand Down Expand Up @@ -708,8 +689,6 @@ def build_zero1_input_state_mesh_shardings(config, state_mesh_shardings, params_
"""
if not config.shard_optimizer_over_data:
return state_mesh_shardings
if not config.pure_nnx:
return state_mesh_shardings.replace(params=params_shardings)
# nnx.State has no .replace: shallow-copy via tree_map (preserves nested container
# types) and overlay params_shardings under input_state.model.
input_state = jax.tree_util.tree_map(
Expand Down
Loading
Loading