Skip to content

Qwen3-Coder-Next long-context LoRA fine-tuning example - #86

Closed
ervinwang-jpg wants to merge 11 commits into
mainfrom
qwen80b
Closed

Qwen3-Coder-Next long-context LoRA fine-tuning example#86
ervinwang-jpg wants to merge 11 commits into
mainfrom
qwen80b

Conversation

@ervinwang-jpg

@ervinwang-jpg ervinwang-jpg commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Replaces qwen3-next-80b-megatron with a tested qwen3-80b-msswift example for Qwen3-Coder-Next (80B MoE) LoRA fine-tuning
  • Default config: 1 node, 8x H200, 32K seq length, ep=8, LoRA rank 8
  • README includes tested configurations table covering 16K–64K sequence lengths across 1–4 node setups
  • Uses MS-Swift + MegatronLM with flash-linear-attention, optimizer CPU offload, and activation recomputation

Tested Configurations

Nodes GPUs Seq Length TP PP EP Peak Memory Status
1 8 16K 8 105 GiB
1 8 32K 8 121 GiB
4 32 48K 2 16 98 GiB
4 32 64K 2 2 4 118 GiB

Test plan

  • 1-node 32K config verified end-to-end (training + checkpoint save)
  • 4-node 48K and 64K configs verified end-to-end
  • Verify truss train init --examples qwen3-80b-msswift works after merge

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Baseten training example for fine-tuning Qwen3-Next-80B with Megatron (ms-swift), targeting 16k context length, and includes a helper to upload resulting checkpoints to the Hugging Face Hub.

Changes:

  • Introduces a Megatron SFT launcher script for Qwen3-Next-80B with 16k max length and LoRA settings.
  • Adds a Baseten truss_train job config to run the launcher in a prebuilt Megatron image.
  • Adds a small Python utility to upload the checkpoint directory to the Hugging Face Hub, plus README instructions.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
examples/qwen3-next-80b-megatron/upload_checkpoint.py New HF Hub upload helper for checkpoint folders.
examples/qwen3-next-80b-megatron/run_megatron.sh New Megatron/ms-swift training launcher + post-training upload coordination.
examples/qwen3-next-80b-megatron/config.py New Baseten training job configuration targeting H200s.
examples/qwen3-next-80b-megatron/README.md New README describing how to run the example.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +23 to +30
# Multi-node distributed job for large Qwen80B tuning.
training_compute = definitions.Compute(
node_count=1,
accelerator=truss_config.AcceleratorSpec(
accelerator=truss_config.Accelerator.H200,
count=8,
),
)

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

training_compute is configured for node_count=1, but run_megatron.sh assumes multi-node (BT_NODE_RANK==1 performs the upload and BT_NODE_RANK==0 waits). With a single node, rank 0 will wait for an upload marker that can never be created and the job will timeout/fail. Align the config with the script by either setting node_count >= 2 or updating the upload logic to handle the single-node case (e.g., rank 0 uploads when BT_GROUP_SIZE==1).

Copilot uses AI. Check for mistakes.
Comment on lines +91 to +103
# Upload checkpoint (node 1 uploads, node 0 waits)
hub_repo="baseten-admin/qwen80b-instruct-megatron-lora"
upload_marker="$checkpoint_dir/.upload_done"
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

if [[ "$BT_NODE_RANK" == "1" ]]; then
python "$script_dir/upload_checkpoint.py" "$checkpoint_dir" "$hub_repo"
touch "$upload_marker"
elif [[ "$BT_NODE_RANK" == "0" ]]; then
waited=0
while [[ ! -f "$upload_marker" && $waited -lt 3600 ]]; do sleep 5; ((waited+=5)); done
[[ -f "$upload_marker" ]] || { echo "Upload timeout"; exit 1; }
fi

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The upload/wait logic hard-codes uploads to BT_NODE_RANK==1. If this script is run with a single node (or any setup without a rank 1), rank 0 will always hit the wait loop and eventually error with "Upload timeout". Consider making the uploader conditional on world size (e.g., upload on rank 0 when BT_GROUP_SIZE==1, otherwise upload on rank 1) or selecting the uploader as BT_NODE_RANK==$((BT_GROUP_SIZE-1)).

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +41
echo "Starting training: model=Qwen/Qwen3-Next-80B-A3B-Instruct nodes=${BT_GROUP_SIZE}x${BT_NUM_GPUS}gpu"
train_exit=0
NPROC_PER_NODE="$BT_NUM_GPUS" \
NNODES="$BT_GROUP_SIZE" \
NODE_RANK="$BT_NODE_RANK" \
MASTER_ADDR="$BT_LEADER_ADDR" \

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With set -u, referencing ${BT_GROUP_SIZE} / ${BT_NUM_GPUS} / ${BT_NODE_RANK} / ${BT_LEADER_ADDR} will immediately error if the script is run outside the Baseten runtime (or if any of these env vars are missing). Other example scripts in this repo use default fallbacks (e.g., ${BT_GROUP_SIZE:-1}, ${BT_NODE_RANK:-0}, ${BT_LEADER_ADDR:-localhost}) to avoid unbound-variable exits while still working on-platform.

Suggested change
echo "Starting training: model=Qwen/Qwen3-Next-80B-A3B-Instruct nodes=${BT_GROUP_SIZE}x${BT_NUM_GPUS}gpu"
train_exit=0
NPROC_PER_NODE="$BT_NUM_GPUS" \
NNODES="$BT_GROUP_SIZE" \
NODE_RANK="$BT_NODE_RANK" \
MASTER_ADDR="$BT_LEADER_ADDR" \
echo "Starting training: model=Qwen/Qwen3-Next-80B-A3B-Instruct nodes=${BT_GROUP_SIZE:-1}x${BT_NUM_GPUS:-1}gpu"
train_exit=0
NPROC_PER_NODE="${BT_NUM_GPUS:-1}" \
NNODES="${BT_GROUP_SIZE:-1}" \
NODE_RANK="${BT_NODE_RANK:-0}" \
MASTER_ADDR="${BT_LEADER_ADDR:-localhost}" \

Copilot uses AI. Check for mistakes.
done
sleep 1
done) &
trap "kill $! 2>/dev/null" EXIT

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The background args.json sync loop is terminated via trap "kill $!" EXIT, which relies on $! still referring to the intended background PID at exit time. This is brittle if another background job is added later. Capture the PID into a dedicated variable right after starting the loop and kill that variable in the trap (similar to other scripts in this repo).

Suggested change
trap "kill $! 2>/dev/null" EXIT
args_sync_pid=$!
trap 'kill "$args_sync_pid" 2>/dev/null' EXIT

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +20
api = HfApi()
api.create_repo(args.repo_id, repo_type="model", private=True, exist_ok=True)
api.upload_folder(
repo_id=args.repo_id,
folder_path=args.checkpoint_dir,
commit_message=f"Checkpoint {datetime.datetime.utcnow().isoformat()}Z",
)

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

upload_folder is called without repo_type. Elsewhere in the repo, uploads explicitly pass repo_type="model" to avoid ambiguity. Consider also explicitly passing the token to HfApi(token=...) (from HF_TOKEN / HUGGING_FACE_HUB_TOKEN / HUGGINGFACE_HUB_TOKEN) so the script is deterministic even if a different cached token exists on disk.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +17 to +24
Edit variables directly in the script you use:
- `run_megatron.sh`:
- `MODEL_ID`, `DATASET_ID`, `DATASET_SPLIT`
- `LORA_RANK`, `LORA_ALPHA`
- `TENSOR_PARALLEL_SIZE`, `PIPELINE_PARALLEL_SIZE`, `CONTEXT_PARALLEL_SIZE`, `EXPERT_PARALLEL_SIZE`
- `MICRO_BATCH_SIZE`, `GLOBAL_BATCH_SIZE`, `MAX_LENGTH`
- `LR_DECAY_STYLE`, `MIN_LR`, `SAVE_INTERVAL`, `LOG_INTERVAL`, `SAVE_FULL_MODEL`

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README's title and "Runtime knobs" list appear copied from the MiniMax example and do not match this example: the script hard-codes the model/dataset/parallelism flags and does not define variables like MODEL_ID, DATASET_SPLIT, etc. Update the README to reflect the actual knobs that can be edited (or refactor run_megatron.sh to use the documented variables).

Suggested change
Edit variables directly in the script you use:
- `run_megatron.sh`:
- `MODEL_ID`, `DATASET_ID`, `DATASET_SPLIT`
- `LORA_RANK`, `LORA_ALPHA`
- `TENSOR_PARALLEL_SIZE`, `PIPELINE_PARALLEL_SIZE`, `CONTEXT_PARALLEL_SIZE`, `EXPERT_PARALLEL_SIZE`
- `MICRO_BATCH_SIZE`, `GLOBAL_BATCH_SIZE`, `MAX_LENGTH`
- `LR_DECAY_STYLE`, `MIN_LR`, `SAVE_INTERVAL`, `LOG_INTERVAL`, `SAVE_FULL_MODEL`
The training configuration is currently hard-coded inside `run_megatron.sh`.
To change how training runs, edit that script directly. Common things to adjust include:
- model and checkpoint paths
- dataset path / loader configuration
- tensor / pipeline / context / expert parallelism settings
- batch sizes and sequence length
- learning rate schedule and optimizer settings
- logging, checkpointing, and save-format options

Copilot uses AI. Check for mistakes.

Copilot AI commented Mar 11, 2026

Copy link
Copy Markdown

@ervinwang-jpg I've opened a new pull request, #87, to work on those changes. Once the pull request is ready, I'll request review from you.

printf '{}' > "$checkpoint_dir/args.json"

# Workaround: sync args.json to timestamped subdirs created by ms-swift
(while true; do

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this?

--model_type qwen3_next \
--save "$checkpoint_dir" \
--dataset winglian/pirate-ultrachat-10k \
--template minimax_m2 \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this the right template?

--use_distributed_optimizer false \
--use_hf 1 || train_exit=$?

# Upload checkpoint (node 1 uploads, node 0 waits)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's avoid uploading checkpoints to HF - i think we can just write this to the baseten checkpoint dir

--use_hf 1 || train_exit=$?

# Upload checkpoint (node 1 uploads, node 0 waits)
hub_repo="baseten-admin/qwen80b-instruct-megatron-lora"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we just have megatron write the checkpoint to our shared checkpoint dir, none of this is necessary

# Install dependencies
pip install -q --upgrade pip
pip install -q "ms-swift[llm]==3.12.5" datasets huggingface_hub "transformers==4.57.1"

@rcano-baseten rcano-baseten Mar 11, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to install flash-linear-attention

FLA_PKG_DIR=$BT_RW_CACHE_DIR/fla_packages
export PYTHONPATH=$FLA_PKG_DIR:$PYTHONPATH
# Remove old polluted cache if it exists
rm -rf $BT_RW_CACHE_DIR/pip_packages
if python -c "import fla" 2>/dev/null; then
    echo "flash-linear-attention already installed in cache, skipping"
else
    echo "Installing flash-linear-attention to cache"
    pip install --target=$FLA_PKG_DIR --no-deps flash-linear-attention fla-core
fi

--tensor_model_parallel_size 1 \
--pipeline_model_parallel_size 1 \
--context_parallel_size 1 \
--expert_model_parallel_size 8 \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we set --recompute_num_layers 2 we can get FLA + 32k Seq Len

ervinwang-jpg and others added 2 commits March 11, 2026 14:38
Remove the previous qwen3-next-80b-megatron example and replace with
qwen3-80b-msswift, a tested Qwen3-Coder-Next LoRA fine-tuning example
using MS-Swift + MegatronLM. Default config runs on 1x H200 node at 32K
sequence length. README includes a tested configurations table showing
working setups from 16K (1 node) to 64K (4 nodes).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rcano-baseten rcano-baseten changed the title Qwen80b Qwen3-Coder-Next long-context LoRA fine-tuning example Mar 12, 2026
rcano-baseten and others added 2 commits March 12, 2026 12:09
Verified tp=2, pp=2, ep=4 works at 64K sequence length on just 2 H200
nodes (106 GiB peak memory). Updated README table and scaling example
to show 2-node as the primary 64K option. Restored default code to
1-node 32K config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verified tp=2, pp=4, ep=4 works at 128K sequence length on 4 H200 nodes
(134 GiB peak memory, 7 GiB headroom). tp=4 is not viable due to
Qwen3-Coder-Next having only 2 KV heads (GQA). Code restored to
1-node 32K defaults.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants