Skip to content

Commit e628164

Browse files
committed
Add jupyter notebook tests to Github CI tests
1 parent 724b115 commit e628164

File tree

7 files changed

+681
-286
lines changed

7 files changed

+681
-286
lines changed

.github/workflows/build_and_test_maxtext.yml

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ jobs:
4141
name: Check for Documentation-Only Changes
4242
runs-on: ubuntu-latest
4343
outputs:
44-
skip_tests: ${{ steps.check.outputs.skip_tests }}
44+
run_tests: ${{ steps.check.outputs.run_tests }}
45+
run_notebooks: ${{ steps.check.outputs.run_notebooks }}
4546
steps:
4647
- uses: actions/checkout@v4
4748
with:
@@ -51,7 +52,8 @@ jobs:
5152
run: |
5253
if [ "${{ github.event_name }}" != "pull_request" ]; then
5354
echo "Not a pull request, running all tests"
54-
echo "skip_tests=false" >> $GITHUB_OUTPUT
55+
echo "run_tests=true" >> $GITHUB_OUTPUT
56+
echo "run_notebooks=true" >> $GITHUB_OUTPUT
5557
exit 0
5658
fi
5759
@@ -63,26 +65,59 @@ jobs:
6365
6466
if [ -z "$CHANGED_FILES" ]; then
6567
echo "No files changed"
66-
echo "skip_tests=false" >> $GITHUB_OUTPUT
67-
elif echo "$CHANGED_FILES" | grep -v -E '\.(md|ipynb)$' > /dev/null; then
68-
echo "Non-documentation files changed, running tests"
69-
echo "skip_tests=false" >> $GITHUB_OUTPUT
68+
echo "run_tests=true" >> $GITHUB_OUTPUT
69+
echo "run_notebooks=true" >> $GITHUB_OUTPUT
70+
fi
71+
72+
# Check for source code changes (anything not .md and not .ipynb)
73+
if echo "$CHANGED_FILES" | grep -v -E '\.(md|ipynb)$' > /dev/null; then
74+
echo "Source code files changed, enabling unit tests."
75+
echo "run_tests=true" >> $GITHUB_OUTPUT
76+
else
77+
echo "No source code changes, skipping unit tests."
78+
echo "run_tests=false" >> $GITHUB_OUTPUT
79+
fi
80+
81+
# Check for notebook (.ipynb) changes specifically
82+
if echo "$CHANGED_FILES" | grep '\.ipynb$' > /dev/null; then
83+
echo "Notebook files changed, enabling notebook run."
84+
echo "run_notebooks=true" >> $GITHUB_OUTPUT
7085
else
71-
echo "Only documentation (.md|ipynb) files changed, skipping tests"
72-
echo "skip_tests=true" >> $GITHUB_OUTPUT
86+
echo "No notebook changes, skipping notebook run."
87+
echo "run_notebooks=false" >> $GITHUB_OUTPUT
7388
fi
7489
7590
build_and_upload_maxtext_package:
7691
needs: doc_only_check
77-
if: needs.doc_only_check.outputs.skip_tests != 'true'
92+
# Run if either tests or notebooks need to run
93+
if: |
94+
needs.doc_only_check.outputs.run_tests == 'true' ||
95+
needs.doc_only_check.outputs.run_notebooks == 'true'
7896
uses: ./.github/workflows/build_package.yml
7997
with:
8098
device_type: tpu
8199
device_name: v4-8
82100
cloud_runner: linux-x86-n2-16-buildkit
83101

102+
maxtext_jupyter_notebooks:
103+
needs: build_and_upload_maxtext_package
104+
if: needs.doc_only_check.outputs.run_notebooks == 'true'
105+
uses: ./.github/workflows/run_jupyter_notebooks.yml
106+
strategy:
107+
fail-fast: false
108+
matrix:
109+
image_type: ["py312"]
110+
with:
111+
device_type: tpu
112+
device_name: v6e-4
113+
image_type: ${{ matrix.image_type }}
114+
cloud_runner: linux-x86-ct6e-180-4tpu
115+
secrets:
116+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
117+
84118
maxtext_cpu_unit_tests:
85119
needs: build_and_upload_maxtext_package
120+
if: needs.doc_only_check.outputs.run_tests == 'true'
86121
uses: ./.github/workflows/run_tests_against_package.yml
87122
strategy:
88123
fail-fast: false # don't cancel all jobs on failure
@@ -104,6 +139,7 @@ jobs:
104139

105140
maxtext_tpu_unit_tests:
106141
needs: build_and_upload_maxtext_package
142+
if: needs.doc_only_check.outputs.run_tests == 'true'
107143
uses: ./.github/workflows/run_tests_against_package.yml
108144
strategy:
109145
fail-fast: false
@@ -122,6 +158,7 @@ jobs:
122158

123159
maxtext_tpu_integration_tests:
124160
needs: build_and_upload_maxtext_package
161+
if: needs.doc_only_check.outputs.run_tests == 'true'
125162
uses: ./.github/workflows/run_tests_against_package.yml
126163
strategy:
127164
fail-fast: false
@@ -140,6 +177,7 @@ jobs:
140177

141178
maxtext_tpu_pathways_unit_tests:
142179
needs: build_and_upload_maxtext_package
180+
if: needs.doc_only_check.outputs.run_tests == 'true'
143181
uses: ./.github/workflows/run_pathways_tests.yml
144182
strategy:
145183
fail-fast: false
@@ -158,6 +196,7 @@ jobs:
158196

159197
maxtext_tpu_pathways_integration_tests:
160198
needs: build_and_upload_maxtext_package
199+
if: needs.doc_only_check.outputs.run_tests == 'true'
161200
uses: ./.github/workflows/run_pathways_tests.yml
162201
strategy:
163202
fail-fast: false
@@ -176,6 +215,7 @@ jobs:
176215

177216
maxtext_gpu_unit_tests:
178217
needs: build_and_upload_maxtext_package
218+
if: needs.doc_only_check.outputs.run_tests == 'true'
179219
uses: ./.github/workflows/run_tests_against_package.yml
180220
strategy:
181221
fail-fast: false
@@ -196,6 +236,7 @@ jobs:
196236

197237
maxtext_gpu_integration_tests:
198238
needs: build_and_upload_maxtext_package
239+
if: needs.doc_only_check.outputs.run_tests == 'true'
199240
uses: ./.github/workflows/run_tests_against_package.yml
200241
strategy:
201242
fail-fast: false
@@ -223,7 +264,7 @@ jobs:
223264
- name: Check test results
224265
run: |
225266
# If doc-only, all tests should be skipped
226-
if [ "${{ needs.doc_only_check.outputs.skip_tests }}" == "true" ]; then
267+
if [ "${{ needs.doc_only_check.outputs.run_tests }}" == "false" ]; then
227268
echo "Documentation-only changes detected, tests were skipped"
228269
exit 0
229270
fi
@@ -246,9 +287,34 @@ jobs:
246287
247288
echo "All required tests passed successfully"
248289
290+
all_notebooks_passed:
291+
name: All Notebooks Passed
292+
needs: [doc_only_check, build_and_upload_maxtext_package, maxtext_jupyter_notebooks]
293+
if: always()
294+
runs-on: ubuntu-latest
295+
steps:
296+
- name: Check notebooks results
297+
run: |
298+
if [ "${{ needs.doc_only_check.outputs.run_notebooks }}" == "false" ]; then
299+
echo "Non-notebook changes detected, runs were skipped"
300+
exit 0
301+
fi
302+
303+
# Otherwise, check that build and notebooks run passed or were skipped
304+
echo "Build result: ${{ needs.build_and_upload_maxtext_package.result }}"
305+
echo "Jupyter Notebooks result: ${{ needs.maxtext_jupyter_notebooks.result }}"
306+
307+
# Fail only if any job failed or was cancelled (skipped is OK)
308+
if [ "${{ contains(needs.*.result, 'failure') }}" == "true" ] || [ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]; then
309+
echo "One or more jobs failed or were cancelled"
310+
exit 1
311+
fi
312+
313+
echo "All required notebooks passed successfully"
314+
249315
notify_failure:
250316
name: Notify failed build # creates an issue or modifies last open existing issue for failed build
251-
needs: [maxtext_cpu_unit_tests, maxtext_tpu_unit_tests, maxtext_tpu_integration_tests, maxtext_tpu_pathways_unit_tests, maxtext_tpu_pathways_integration_tests, maxtext_gpu_unit_tests, maxtext_gpu_integration_tests]
317+
needs: [maxtext_jupyter_notebooks, maxtext_cpu_unit_tests, maxtext_tpu_unit_tests, maxtext_tpu_integration_tests, maxtext_tpu_pathways_unit_tests, maxtext_tpu_pathways_integration_tests, maxtext_gpu_unit_tests, maxtext_gpu_integration_tests]
252318
if: ${{ always() }}
253319
runs-on: ubuntu-latest
254320
permissions:
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Copyright 2026 Google LLC
2+
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# This file defines a module for running jupyter notebooks against the built maxtext package.
16+
17+
name: Run Jupyter Notebooks
18+
19+
on:
20+
workflow_call:
21+
inputs:
22+
device_type:
23+
required: true
24+
type: string
25+
device_name:
26+
required: true
27+
type: string
28+
image_type:
29+
required: false
30+
type: string
31+
cloud_runner:
32+
required: false
33+
type: string
34+
secrets:
35+
HF_TOKEN:
36+
required: true
37+
38+
permissions:
39+
contents: read
40+
jobs:
41+
run:
42+
runs-on: ${{ inputs.cloud_runner != '' && inputs.cloud_runner || fromJson(format('["self-hosted", "{0}", "{1}"]', inputs.device_type, inputs.device_name)) }}
43+
container:
44+
image: gcr.io/tpu-prod-env-multipod/maxtext-unit-test-${{ inputs.device_type == 'cpu' && 'tpu' || inputs.device_type }}:${{ inputs.image_type != '' && inputs.image_type }}
45+
steps:
46+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
47+
- name: Download the MaxText wheel
48+
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
49+
with:
50+
name: maxtext-wheel
51+
- name: Install MaxText and Dependencies
52+
shell: bash
53+
run: |
54+
python3 -m uv venv --seed
55+
source .venv/bin/activate
56+
57+
# Install MaxText package
58+
maxtext_wheel=$(ls maxtext-*-py3-none-any.whl 2>/dev/null)
59+
uv pip install ${maxtext_wheel}[${MAXTEXT_PACKAGE_EXTRA}] --resolution=lowest
60+
uv pip install -r src/install_maxtext_extra_deps/extra_deps_from_github.txt
61+
62+
# Install dependencies for running notebooks
63+
uv pip install papermill ipykernel ipywidgets
64+
.venv/bin/python3 -m ipykernel install --user --name maxtext_venv
65+
66+
# Install Tunix for post-training notebooks
67+
uv pip install git+https://github.com/google/tunix
68+
69+
# Install vllm for post-training notebooks
70+
git clone https://github.com/vllm-project/vllm.git
71+
VLLM_TARGET_DEVICE="tpu" uv pip install ./vllm
72+
73+
# Install tpu-inference for post-training notebooks
74+
git clone https://github.com/vllm-project/tpu-inference.git
75+
uv pip install ./tpu-inference
76+
77+
uv pip install --no-deps qwix==0.1.4
78+
uv pip install --no-deps protobuf==5.29.5
79+
python3 -m pip freeze
80+
- name: Run Post-Training Notebooks
81+
shell: bash
82+
env:
83+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
84+
run: |
85+
MAXTEXT_REPO_ROOT=$(pwd)
86+
MAXTEXT_NOTEBOOKS_ROOT="$MAXTEXT_REPO_ROOT/src/MaxText/examples"
87+
88+
for notebook in "$MAXTEXT_NOTEBOOKS_ROOT"/{sft,rl}*.ipynb; do
89+
filename=$(basename "$notebook")
90+
output_name="${filename%.ipynb}_output.ipynb"
91+
92+
echo "------------------------------------------------------"
93+
echo "Running $filename ..."
94+
echo "------------------------------------------------------"
95+
96+
.venv/bin/papermill "$notebook" "$output_name" -k maxtext_venv
97+
done
98+
- name: Upload Outputs
99+
if: always()
100+
uses: actions/upload-artifact@v4
101+
with:
102+
name: notebook-outputs-${{ inputs.device_name }}
103+
path: ./*_output.ipynb

docs/guides/run_python_notebook.md

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ gcloud compute tpus tpu-vm ssh maxtext-tpu-node --zone=YOUR_ZONE -- -L 8888:loca
8989

9090
> **Note**: If you get a "bind: Address already in use" error, it means port 8888 is busy on your local computer. Change the first number to a different port, e.g., -L 9999:localhost:8888. You will then access Jupyter at localhost:9999.
9191
92-
### Step 3: Install Dependencies
92+
### Step 3: Install JupyterLab
9393

9494
Run the following commands on your TPU-VM:
9595

@@ -99,21 +99,40 @@ sudo apt install python3-pip python3-dev git -y
9999
pip3 install jupyterlab
100100
```
101101

102-
### Step 4: Start Jupyter Lab
102+
### Step 4: Install MaxText and Dependencies
103+
104+
To execute post-training notebooks on your TPU-VM, follow the official [MaxText installation guides](https://maxtext.readthedocs.io/en/latest/tutorials/posttraining/rl.html#create-virtual-environment-and-install-maxtext-dependencies) to install MaxText and its dependencies inside a dedicated virtual environment.
105+
106+
### Step 5: Register virtual environment as a Jupyter Kernel
107+
108+
Once the environment is set up, you need to register it so JupyterLab can see it as an available kernel. Run the following command on your TPU-VM, replacing <virtual env name> with the actual name to your virtual environment:
109+
110+
```bash
111+
python3 -m ipykernel install --user --name <virtual env name> --display-name "Python3 (MaxText Venv)"
112+
```
113+
114+
### Step 6: Start JupyterLab
115+
116+
Start the Jupyter server on your TPU-VM by running:
103117

104118
```bash
105119
jupyter lab --ip=0.0.0.0 --port=8888 --no-browser --allow-root
106120
```
107121

108-
### Step 5: Access the Notebook
109-
5.a. Look at the terminal output for a URL that looks like: `http://127.0.0.1:8888/lab?token=...`
122+
### Step 7: Access the Notebook
123+
7.a. Look at the terminal output for a URL that looks like: `http://127.0.0.1:8888/lab?token=...`.
110124

111-
5.b. Copy that URL.
125+
7.b. Copy that URL.
112126

113-
5.c. Paste it into your **local computer's browser**.
127+
7.c. Paste it into your **local computer's browser**.
114128
* **Important:** If you changed the port in Step 2 (e.g., to `9999`), you must manually replace `8888` in the URL with `9999`.
115129
* *Example:* `http://127.0.0.1:9999/lab?token=...`
116130

131+
7.d. Once the interface opens in your browser, Click on the current kernel name (e.g., `Python 3 (ipykernel)`).
132+
133+
7.e. In the dropdown menu, select the new kernel you just created: `Python3 (MaxText Venv)`.
134+
135+
7.f. Run the jupyter notebook cells.
117136

118137
## Available Examples
119138

0 commit comments

Comments
 (0)