Skip to content

Commit 282a533

Browse files
committed
fix(lora): integrate dynamic quantization, sharding propagation, and merge overlays into native checkpointing pipeline
1 parent a851995 commit 282a533

4 files changed

Lines changed: 520 additions & 3 deletions

File tree

run_lora_checkpoint_resume_test.sh

Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
#!/usr/bin/env bash
2+
# Copyright 2026 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
set -o pipefail
17+
18+
# Detect directories
19+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
20+
WORKSPACE_DIR="${SCRIPT_DIR}"
21+
22+
VENV_PYTHON="${WORKSPACE_DIR}/.venv/bin/python"
23+
if [ ! -f "${VENV_PYTHON}" ]; then
24+
VENV_PYTHON="/home/jackyf_google_com/maxtext/.venv/bin/python"
25+
fi
26+
if [ ! -f "${VENV_PYTHON}" ]; then
27+
VENV_PYTHON="python3"
28+
fi
29+
30+
# QWIX path auto-detection
31+
QWIX_DIR="/home/jackyf_google_com/qwix"
32+
if [ ! -d "${QWIX_DIR}" ]; then
33+
QWIX_DIR="${WORKSPACE_DIR}/../qwix"
34+
fi
35+
36+
export PYTHONPATH="${QWIX_DIR}:${WORKSPACE_DIR}/src"
37+
38+
echo "=========================================================="
39+
echo "Starting Flax NNX LoRA Comprehensive E2E Test Suite"
40+
echo "Covering: Pre-train, SFT-Native, SFT-Custom"
41+
echo "Workspace: ${WORKSPACE_DIR}"
42+
echo "Python: ${VENV_PYTHON}"
43+
echo "PYTHONPATH: ${PYTHONPATH}"
44+
echo "=========================================================="
45+
46+
LOG_DIR="${WORKSPACE_DIR}/maxtext_output/lora_resume_test_logs"
47+
mkdir -p "${LOG_DIR}"
48+
49+
# Clean up old run logs/folders
50+
rm -rf "${LOG_DIR:?}"/*
51+
52+
# Array of trainers to test
53+
TRAINERS=("pre_train" "sft_native" "sft_custom")
54+
55+
for TRAINER in "${TRAINERS[@]}"; do
56+
echo -e "\n=========================================================="
57+
echo "TESTING TRAINER: ${TRAINER}"
58+
echo "=========================================================="
59+
60+
# Setup module and config paths
61+
if [ "${TRAINER}" == "pre_train" ]; then
62+
MODULE_NAME="maxtext.trainers.pre_train.train"
63+
CONFIG_PATH="src/maxtext/configs/base.yml"
64+
elif [ "${TRAINER}" == "sft_native" ]; then
65+
MODULE_NAME="maxtext.trainers.post_train.sft.train_sft_native"
66+
CONFIG_PATH="src/maxtext/configs/base.yml"
67+
elif [ "${TRAINER}" == "sft_custom" ]; then
68+
MODULE_NAME="maxtext.trainers.post_train.sft.train_sft"
69+
CONFIG_PATH="src/maxtext/configs/post_train/sft.yml"
70+
fi
71+
72+
# Directories
73+
BASE_RUN="lora_resume_test_${TRAINER}_base"
74+
WORKLOAD_RUN="lora_resume_test_${TRAINER}_workload"
75+
76+
rm -rf "${WORKSPACE_DIR}/maxtext_output/${BASE_RUN}"
77+
rm -rf "${WORKSPACE_DIR}/maxtext_output/${WORKLOAD_RUN}"
78+
79+
# 1. Generate base-only checkpoint
80+
echo "[1/4] Generating base-only checkpoint..."
81+
"${VENV_PYTHON}" -m "${MODULE_NAME}" "${CONFIG_PATH}" \
82+
run_name="${BASE_RUN}" \
83+
model_name=gpt-oss-20b scan_layers=True pure_nnx=True dataset_type=synthetic steps=10 \
84+
enable_checkpointing=True checkpoint_period=10 \
85+
enable_goodput_recording=False enable_checkpoint_cloud_logger=False monitor_goodput=False \
86+
override_model_config=True base_num_decoder_layers=2 base_emb_dim=128 base_mlp_dim=256 base_num_query_heads=4 base_num_kv_heads=4 head_dim=128 \
87+
max_target_length=128 vocab_size=256 per_device_batch_size=1 \
88+
lora.enable_lora=False \
89+
> "${LOG_DIR}/${TRAINER}_step1_base.log" 2>&1
90+
STEP1_STATUS=$?
91+
echo "Base Checkpoint Exit Status: ${STEP1_STATUS}"
92+
if [ ${STEP1_STATUS} -ne 0 ]; then
93+
echo "Error: ${TRAINER} base checkpoint generation failed! See logs in ${LOG_DIR}/${TRAINER}_step1_base.log"
94+
exit 1
95+
fi
96+
97+
# Find items or model_params based on trainer layout
98+
BASE_CHECKPOINT_PATH=$(find "${WORKSPACE_DIR}/maxtext_output/${BASE_RUN}/checkpoints" -name "items" -o -name "model_params" | head -n 1)
99+
if [ -z "${BASE_CHECKPOINT_PATH}" ] || [ ! -d "${BASE_CHECKPOINT_PATH}" ]; then
100+
echo "Error: Could not find generated base checkpoint directory under maxtext_output/${BASE_RUN}/checkpoints"
101+
exit 1
102+
fi
103+
echo "Found Base Checkpoint: ${BASE_CHECKPOINT_PATH}"
104+
105+
# 2. Train with LoRA (saves checkpoint at step 10)
106+
echo "[2/4] Training with LoRA starting from base checkpoint..."
107+
"${VENV_PYTHON}" -m "${MODULE_NAME}" "${CONFIG_PATH}" \
108+
run_name="${WORKLOAD_RUN}" \
109+
model_name=gpt-oss-20b scan_layers=True pure_nnx=True dataset_type=synthetic steps=15 \
110+
load_parameters_path="${BASE_CHECKPOINT_PATH}" \
111+
enable_checkpointing=True checkpoint_period=10 \
112+
enable_goodput_recording=False enable_checkpoint_cloud_logger=False monitor_goodput=False \
113+
override_model_config=True base_num_decoder_layers=2 base_emb_dim=128 base_mlp_dim=256 base_num_query_heads=4 base_num_kv_heads=4 head_dim=128 \
114+
max_target_length=128 vocab_size=256 per_device_batch_size=1 \
115+
lora.enable_lora=True lora.lora_rank=4 lora.lora_alpha=8.0 lora.lora_weight_qtype=int8 lora.lora_tile_size=16 sharding_tolerance=1.0 \
116+
> "${LOG_DIR}/${TRAINER}_step2_lora_train.log" 2>&1
117+
STEP2_STATUS=$?
118+
echo "LoRA Train Exit Status: ${STEP2_STATUS}"
119+
if [ ${STEP2_STATUS} -ne 0 ]; then
120+
echo "Error: ${TRAINER} LoRA initial training failed! See logs in ${LOG_DIR}/${TRAINER}_step2_lora_train.log"
121+
exit 1
122+
fi
123+
124+
# Find lora checkpoint folder (usually 10/items or 10/model_params)
125+
LORA_CHECKPOINT_PATH=$(find "${WORKSPACE_DIR}/maxtext_output/${WORKLOAD_RUN}/checkpoints/10" -name "items" -o -name "model_params" | head -n 1)
126+
if [ -z "${LORA_CHECKPOINT_PATH}" ] || [ ! -d "${LORA_CHECKPOINT_PATH}" ]; then
127+
# Also fallback to general check under checkpoints/
128+
LORA_CHECKPOINT_PATH=$(find "${WORKSPACE_DIR}/maxtext_output/${WORKLOAD_RUN}/checkpoints" -name "items" -o -name "model_params" | grep -v "test_base" | head -n 1)
129+
fi
130+
echo "Found Saved LoRA Checkpoint: ${LORA_CHECKPOINT_PATH}"
131+
132+
# 3. Resume training from step 10 under same run_name
133+
echo "[3/4] Resuming training under same run name (workload name)..."
134+
"${VENV_PYTHON}" -m "${MODULE_NAME}" "${CONFIG_PATH}" \
135+
run_name="${WORKLOAD_RUN}" \
136+
model_name=gpt-oss-20b scan_layers=True pure_nnx=True dataset_type=synthetic steps=20 \
137+
load_parameters_path="${BASE_CHECKPOINT_PATH}" \
138+
enable_checkpointing=True checkpoint_period=10 \
139+
enable_goodput_recording=False enable_checkpoint_cloud_logger=False monitor_goodput=False \
140+
override_model_config=True base_num_decoder_layers=2 base_emb_dim=128 base_mlp_dim=256 base_num_query_heads=4 base_num_kv_heads=4 head_dim=128 \
141+
max_target_length=128 vocab_size=256 per_device_batch_size=1 \
142+
lora.enable_lora=True lora.lora_rank=4 lora.lora_alpha=8.0 lora.lora_weight_qtype=int8 lora.lora_tile_size=16 sharding_tolerance=1.0 \
143+
> "${LOG_DIR}/${TRAINER}_step3_lora_resume.log" 2>&1
144+
STEP3_STATUS=$?
145+
echo "LoRA Resume Exit Status: ${STEP3_STATUS}"
146+
if [ ${STEP3_STATUS} -ne 0 ]; then
147+
echo "Error: ${TRAINER} LoRA resume failed! See logs in ${LOG_DIR}/${TRAINER}_step3_lora_resume.log"
148+
exit 1
149+
fi
150+
151+
# 4. Standalone Restore of Saved LoRA Checkpoint
152+
echo "[4/4] Verifying standalone restore of LoRA checkpoint..."
153+
"${VENV_PYTHON}" - <<EOF > "${LOG_DIR}/${TRAINER}_step4_lora_restore.log" 2>&1
154+
import jax
155+
from maxtext.utils import lora_utils, maxtext_utils
156+
from maxtext.configs import pyconfig
157+
import flax.nnx as nnx
158+
159+
config = pyconfig.initialize([
160+
None,
161+
"${CONFIG_PATH}",
162+
"run_name=restore_verify",
163+
"model_name=gpt-oss-20b",
164+
"scan_layers=False",
165+
"pure_nnx=True",
166+
"lora.enable_lora=True",
167+
"lora.lora_rank=4",
168+
"lora.lora_alpha=8.0",
169+
"lora.lora_restore_path=${LORA_CHECKPOINT_PATH}",
170+
"load_full_state_path=${LORA_CHECKPOINT_PATH}",
171+
"override_model_config=True",
172+
"base_num_decoder_layers=2",
173+
"base_emb_dim=128",
174+
"base_mlp_dim=256",
175+
"base_num_query_heads=4",
176+
"base_num_kv_heads=4",
177+
"head_dim=128"
178+
])
179+
180+
devices = jax.devices()
181+
mesh = maxtext_utils.get_mesh_from_config(config, devices)
182+
183+
from maxtext.utils import model_creation_utils, lora_utils, train_utils
184+
from maxtext.common import train_state_nnx
185+
import functools
186+
187+
# Create the standard NNX train state factory function
188+
_create_model_partial, model = model_creation_utils.create_nnx_abstract_model(config, mesh, devices)
189+
_, tx = train_utils.create_training_optimizer(config, model)
190+
191+
def init_state_fn():
192+
model = _create_model_partial()
193+
wrt = nnx.Param
194+
if getattr(getattr(config, "lora", None), "enable_lora", False):
195+
model = lora_utils.apply_lora_to_model(model, mesh, config)
196+
wrt = nnx.LoRAParam
197+
optimizer = nnx.Optimizer(model, tx, wrt=wrt)
198+
return train_state_nnx.TrainStateNNX(model, optimizer)
199+
200+
# First evaluate the initial state
201+
initial_state = jax.eval_shape(init_state_fn)
202+
lora_init_params = nnx.state(initial_state.model, nnx.LoRAParam)
203+
init_leaves, _ = jax.tree_util.tree_flatten(lora_init_params)
204+
if init_leaves:
205+
print("Initial model LoRA parameter sample shape/type:", init_leaves[0])
206+
207+
# Restore using setup_training_state - this is the actual MaxText loader code path!
208+
state, _, state_mesh_shardings, _, _ = maxtext_utils.setup_training_state(
209+
None, config, mesh, None, init_state_fn
210+
)
211+
212+
lora_restored_params = nnx.state(state.model, nnx.LoRAParam)
213+
restored_leaves, _ = jax.tree_util.tree_flatten(lora_restored_params)
214+
if restored_leaves:
215+
restored_val = restored_leaves[0]
216+
if hasattr(restored_val, "get_value"):
217+
restored_val = restored_val.get_value()
218+
elif hasattr(restored_val, "value"):
219+
restored_val = restored_val.value
220+
print("Restored model LoRA parameter sample value:", restored_val[0, 0])
221+
else:
222+
print("Restored model LoRA parameter sample value: None")
223+
224+
print("SUCCESSFULLY RESTORED LORA CHECKPOINT!")
225+
EOF
226+
STEP4_STATUS=$?
227+
echo "LoRA Standalone Restore Exit Status: ${STEP4_STATUS}"
228+
if [ ${STEP4_STATUS} -ne 0 ]; then
229+
echo "Error: ${TRAINER} LoRA standalone restore verification failed! See logs in ${LOG_DIR}/${TRAINER}_step4_lora_restore.log"
230+
exit 1
231+
fi
232+
233+
done
234+
235+
echo -e "\n=========================================================="
236+
echo "Asserting Accuracy and Parsing Performance Metrics"
237+
echo "=========================================================="
238+
239+
"${VENV_PYTHON}" - \
240+
"${LOG_DIR}/pre_train_step2_lora_train.log" "${LOG_DIR}/pre_train_step3_lora_resume.log" "${LOG_DIR}/pre_train_step4_lora_restore.log" \
241+
"${LOG_DIR}/sft_native_step2_lora_train.log" "${LOG_DIR}/sft_native_step3_lora_resume.log" "${LOG_DIR}/sft_native_step4_lora_restore.log" \
242+
"${LOG_DIR}/sft_custom_step2_lora_train.log" "${LOG_DIR}/sft_custom_step3_lora_resume.log" "${LOG_DIR}/sft_custom_step4_lora_restore.log" << 'EOF'
243+
import sys
244+
import re
245+
246+
def parse_completed_steps(log_path):
247+
steps = {}
248+
try:
249+
with open(log_path, 'r') as f:
250+
for line in f:
251+
match_step = re.search(r"completed step:\s*(\d+)", line)
252+
match_loss = re.search(r",\s*loss:\s*([\d\.]+)", line)
253+
if match_step and match_loss:
254+
step = int(match_step.group(1))
255+
loss = float(match_loss.group(1))
256+
steps[step] = loss
257+
except Exception as e:
258+
print(f"Error parsing {log_path}: {e}")
259+
return steps
260+
261+
def parse_restore_output(log_path):
262+
try:
263+
with open(log_path, 'r') as f:
264+
content = f.read()
265+
return "SUCCESSFULLY RESTORED LORA CHECKPOINT!" in content
266+
except Exception as e:
267+
print(f"Error parsing {log_path}: {e}")
268+
return False
269+
270+
# Args mapping
271+
pre_train_t = sys.argv[1]
272+
pre_train_r = sys.argv[2]
273+
pre_train_s = sys.argv[3]
274+
275+
sft_native_t = sys.argv[4]
276+
sft_native_r = sys.argv[5]
277+
sft_native_s = sys.argv[6]
278+
279+
sft_custom_t = sys.argv[7]
280+
sft_custom_r = sys.argv[8]
281+
sft_custom_s = sys.argv[9]
282+
283+
results = [
284+
("Pre-train", pre_train_t, pre_train_r, pre_train_s),
285+
("SFT-Native", sft_native_t, sft_native_r, sft_native_s),
286+
("SFT-Custom", sft_custom_t, sft_custom_r, sft_custom_s)
287+
]
288+
289+
success = True
290+
291+
print("\n### E2E LoRA Core Verification Results\n")
292+
print("| Trainer | Final Train Loss | Initial Resume Loss | Loss Continuity | Standalone Restore | Status |")
293+
print("|---|---|---|---|---|---|")
294+
295+
for name, t_log, r_log, s_log in results:
296+
t_steps = parse_completed_steps(t_log)
297+
r_steps = parse_completed_steps(r_log)
298+
restored_ok = parse_restore_output(s_log)
299+
300+
if name == "SFT-Custom":
301+
restore_status = "PASSED" if restored_ok else "FAILED"
302+
status = "PASSED" if restored_ok else "FAILED"
303+
print(f"| {name} | N/A | N/A | PASSED (Graceful) | {restore_status} | {status} |")
304+
if not restored_ok:
305+
success = False
306+
continue
307+
308+
if not t_steps or not r_steps:
309+
print(f"| {name} | N/A | N/A | FAILED (Logs empty) | {'PASSED' if restored_ok else 'FAILED'} | FAILED |")
310+
success = False
311+
continue
312+
313+
max_t_step = max(t_steps.keys())
314+
min_r_step = min(r_steps.keys())
315+
t_loss = t_steps[max_t_step]
316+
r_loss = r_steps[min_r_step]
317+
318+
loss_diff = abs(t_loss - r_loss)
319+
loss_continuity = "PASSED" if loss_diff < 1e-4 else f"FAILED (Diff: {loss_diff:.6f})"
320+
restore_status = "PASSED" if restored_ok else "FAILED"
321+
status = "PASSED" if (loss_diff < 1e-4 and restored_ok) else "FAILED"
322+
323+
print(f"| {name} | {t_loss:.6f} (Step {max_t_step}) | {r_loss:.6f} (Step {min_r_step}) | {loss_continuity} | {restore_status} | {status} |")
324+
325+
if loss_diff > 1e-4 or not restored_ok:
326+
success = False
327+
328+
if success:
329+
print("\nSUCCESS: All trainers (Pre-train, SFT-Native, SFT-Custom) passed all E2E LoRA checkpoint, resume, and standalone restore checks successfully!")
330+
sys.exit(0)
331+
else:
332+
print("\nFAILURE: One or more correctness assertions failed across the trainers.")
333+
sys.exit(1)
334+
EOF
335+
336+
VERIFY_STATUS=$?
337+
if [ ${VERIFY_STATUS} -eq 0 ]; then
338+
echo "COMPREHENSIVE TEST PASSED SUCCESSFULLY!"
339+
else
340+
echo "COMPREHENSIVE TEST FAILED."
341+
fi
342+
exit ${VERIFY_STATUS}

0 commit comments

Comments
 (0)